Using Cache Queries | Ignite Documentation

Ignite Summit 2024 — Call For Speakers Now Open — Learn more

Edit

Using Cache Queries

Overview

IgniteCache has several query methods, all of which receive a subclass of the Query class and return a QueryCursor. Available types of queries: ScanQuery, IndexQuery, TextQuery.

A Query represents an abstract paginated query to be executed on a cache. The page size is configurable via the Query.setPageSize(…​) method (default is 1024).

QueryCursor represents the query result set and allows for transparent page-by-page iteration. When a user starts iterating over the last page, QueryCursos automatically requests the next page in the background. For cases when pagination is not needed, you can use the QueryCursor.getAll() method, which fetches the entries and stores them in a collection.

Note

Closing Cursors

Cursors close automatically when you call the QueryCursor.getAll() method. If you are iterating over the cursor in a for loop or explicitly getting an Iterator, you must close the cursor explicitly or use a try-with-resources statement.

Executing Scan Queries

A scan query is a simple search query used to retrieve data from a cache in a distributed manner. When executed without parameters, a scan query returns all entries from the cache.

IgniteCache<Integer, Person> cache = ignite.getOrCreateCache("myCache");

QueryCursor<Cache.Entry<Integer, Person>> cursor = cache.query(new ScanQuery<>());
var cursor = cache.Query(new ScanQuery<int, Person>());
Cache<int64_t, Person> cache = ignite.GetOrCreateCache<int64_t, ignite::Person>("personCache");

QueryCursor<int64_t, Person> cursor = cache.Query(ScanQuery());

Scan queries return entries that match a predicate, if specified. The predicate is applied on the remote nodes.

IgniteCache<Integer, Person> cache = ignite.getOrCreateCache("myCache");

// Find the persons who earn more than 1,000.
IgniteBiPredicate<Integer, Person> filter = (key, p) -> p.getSalary() > 1000;

try (QueryCursor<Cache.Entry<Integer, Person>> qryCursor = cache.query(new ScanQuery<>(filter))) {
    qryCursor.forEach(
            entry -> System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()));
}
class SalaryFilter : ICacheEntryFilter<int, Person>
{
    public bool Invoke(ICacheEntry<int, Person> entry)
    {
        return entry.Value.Salary > 1000;
    }
}

public static void ScanQueryFilterDemo()
{
    var ignite = Ignition.Start();
    var cache = ignite.GetOrCreateCache<int, Person>("person_cache");

    cache.Put(1, new Person {Name = "person1", Salary = 1001});
    cache.Put(2, new Person {Name = "person2", Salary = 999});

    using (var cursor = cache.Query(new ScanQuery<int, Person>(new SalaryFilter())))
    {
        foreach (var entry in cursor)
        {
            Console.WriteLine("Key = " + entry.Key + ", Value = " + entry.Value);
        }
    }
}
This API is not presently available for C++.

Scan queries also support an optional transformer closure which lets you convert the entry on the server node before sending it back. This is useful, for example, when you want to fetch only several fields of a large object and want to minimize the network traffic. The example below shows how to fetch only the keys without sending the values.

IgniteCache<Integer, Person> cache = ignite.getOrCreateCache("myCache");

// Get only keys for persons earning more than 1,000.
List<Integer> keys = cache.query(new ScanQuery<>(
        // Remote filter
        (IgniteBiPredicate<Integer, Person>) (k, p) -> p.getSalary() > 1000),
        // Transformer
        (IgniteClosure<Cache.Entry<Integer, Person>, Integer>) Cache.Entry::getKey).getAll();
This API is not presently available for C#/.NET.
This API is not presently available for C++.

Local Scan Query

By default, a scan query is distributed to all nodes. However, you can execute the query locally, in which case the query runs against the data stored on the local node (i.e. the node where the query is executed).

QueryCursor<Cache.Entry<Integer, Person>> cursor = cache
        .query(new ScanQuery<Integer, Person>().setLocal(true));
var query = new ScanQuery<int, Person> {Local = true};
var cursor = cache.Query(query);
ScanQuery sq;
sq.SetLocal(true);

QueryCursor<int64_t, Person> cursor = cache.Query(sq);

Executing Index Queries

Warning

Experimental API. Introduced since Apache Ignite 2.12. Only Java API is supported. Please send your questions and bug reports to user@ignite.apache.org.

Index queries work over distributed indexes and retrieve cache entries that match the specified query. QueryCursor delivers sorted cache entries by the order defined for queried index. IndexQuery can be used if a low amount of data matches filtering criteria. For such cases, ScanQuery usage is not optimal: it firstly extracts all cache entries and then applies a filter to them. IndexQuery relies on index tree structure and filters most of the entries without extracting.

      // Create index by 2 fields (orgId, salary).
      LinkedHashMap<String,String> fields = new LinkedHashMap<>();
          fields.put("orgId", Integer.class.getName());
          fields.put("salary", Integer.class.getName());

      QueryEntity personEntity = new QueryEntity(Integer.class, Person.class)
          .setFields(fields)
          .setIndexes(Collections.singletonList(
              new QueryIndex(Arrays.asList("orgId", "salary"), QueryIndexType.SORTED)
                  .setName("ORG_SALARY_IDX")
          ));

      CacheConfiguration<Integer, Person> ccfg = new CacheConfiguration<Integer, Person>("entityCache")
          .setQueryEntities(Collections.singletonList(personEntity));

      IgniteCache<Integer, Person> cache = ignite.getOrCreateCache(ccfg);

      // Find the persons who work in Organization 1.
      QueryCursor<Cache.Entry<Integer, Person>> cursor = cache.query(
          new IndexQuery<Integer, Person>(Person.class, "ORG_SALARY_IDX")
              .setCriteria(eq("orgId", 1))
      );

Index query criteria are defined in IndexQueryCriteriaBuilder. The goal of the criteria is to build a valid range to traverse the index tree. For this reason, criteria fields have to match the specified index. For example, if there is an index defined with (A, B) set, then valid criteria sets are (A) and (A, B). Criteria with the single (B) field are invalid because the field (B) is not a prefix set of the specified index fields, and it’s impossible to build a narrow index range with it.

Note

Criteria are joined by the AND operator. It is also possible to use multiple criteria for the same field.

// Find the persons who work in Organization 1 and have salary more than 1,000.
QueryCursor<Cache.Entry<Integer, Person>> cursor = cache.query(
    new IndexQuery<Integer, Person>(Person.class, "ORG_SALARY_IDX")
        .setCriteria(eq("orgId", 1), gt("salary", 1000))
);

The index name is an optional parameter. In this case, Ignite tries to figure out the index by itself using specified criteria fields.

// Ignite finds suitable index "ORG_SALARY_IDX" by specified criterion field "orgId".
QueryCursor<Cache.Entry<Integer, Person>> cursor = cache.query(
    new IndexQuery<Integer, Person>(Person.class)
        .setCriteria(eq("orgId", 1))
);

For the empty criteria list, a full scan of the specified index is performed. If index name is also not specified, then the PrimaryKey index is used.

Additional filtering

IndexQuery also supports an optional predicate, the same as ScanQuery has. It’s suitable for additional cache entry filtering in cases when a filter doesn’t match an index tree range. For example, it contains some logic, the "OR" operations, or fields that are not the part of the index.

// Find the persons who work in Organization 1 and whose name contains 'Vasya'.
QueryCursor<Cache.Entry<Integer, Person>> cursor = cache.query(
    new IndexQuery<Integer, Person>(Person.class)
        .setCriteria(eq("orgId", 1))
        .setFilter((k, v) -> v.getName().contains("Vasya"))
);