You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by na...@apache.org on 2021/11/09 09:28:50 UTC

[ignite] branch master updated: IGNITE-15745 Add docs for IndexQuery (#9545)

This is an automated email from the ASF dual-hosted git repository.

namelchev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 025cc9f  IGNITE-15745 Add docs for IndexQuery (#9545)
025cc9f is described below

commit 025cc9fbf5553e7fbc81f8e94326a34743ad73fc
Author: Maksim Timonin <ti...@gmail.com>
AuthorDate: Tue Nov 9 12:28:35 2021 +0300

    IGNITE-15745 Add docs for IndexQuery (#9545)
---
 docs/_data/toc.yaml                                |  4 +-
 docs/_docs/code-deployment/peer-class-loading.adoc |  4 +-
 .../apache/ignite/snippets/UsingScanQueries.java   | 75 ++++++++++++++++++++++
 .../collocated-computations.adoc                   |  2 +-
 docs/_docs/events/events.adoc                      |  6 +-
 ...-scan-queries.adoc => using-cache-queries.adoc} | 60 ++++++++++++++++-
 docs/_docs/thin-clients/java-thin-client.adoc      |  6 +-
 .../examples/datagrid/CacheQueryExample.java       | 50 ++++++++++++++-
 .../org/apache/ignite/examples/model/Person.java   |  9 ++-
 .../org/apache/ignite/cache/query/IndexQuery.java  |  9 ++-
 10 files changed, 206 insertions(+), 19 deletions(-)

diff --git a/docs/_data/toc.yaml b/docs/_data/toc.yaml
index a4881c7..e57c9fb 100644
--- a/docs/_data/toc.yaml
+++ b/docs/_data/toc.yaml
@@ -177,8 +177,8 @@
       url: key-value-api/basic-cache-operations
     - title: Working with Binary Objects
       url: key-value-api/binary-objects
-    - title: Using Scan Queries
-      url: key-value-api/using-scan-queries
+    - title: Using Cache Queries
+      url: key-value-api/using-cache-queries
     - title: Read Repair
       url: read-repair
 - title: Performing Transactions
diff --git a/docs/_docs/code-deployment/peer-class-loading.adoc b/docs/_docs/code-deployment/peer-class-loading.adoc
index 0dd7d18..3781faa 100644
--- a/docs/_docs/code-deployment/peer-class-loading.adoc
+++ b/docs/_docs/code-deployment/peer-class-loading.adoc
@@ -28,12 +28,12 @@ If you develop C# and .NET applications, then refer to the link:net-specific/net
 page for details on how to set up and use the peer-class-loading feature with that type of applications.
 ====
 
-For example, when link:key-value-api/using-scan-queries[querying data] with a custom transformer, you only need to define your tasks on the client node that initiates the computation, and Ignite loads the classes to the server nodes.
+For example, when link:key-value-api/using-cache-queries[querying data] with a custom transformer, you just need to define your tasks on the client node that initiates the computation, and Ignite will upload the classes to the server nodes.
 
 When enabled, peer class loading is used to deploy the following classes:
 
 * Tasks and jobs submitted via the link:distributed-computing/distributed-computing[compute interface].
-* Transformers and filters used with link:key-value-api/using-scan-queries[scan queries] and link:key-value-api/continuous-queries[continuous queries].
+* Transformers and filters used with link:key-value-api/using-cache-queries[cache queries] and link:key-value-api/continuous-queries[continuous queries].
 * Stream transformers, receivers and visitors used with link:data-streaming#data-streamers[data streamers].
 * link:distributed-computing/collocated-computations#entry-processor[Entry processors].
 
diff --git a/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/UsingScanQueries.java b/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/UsingScanQueries.java
index 49a9f53..e14668b 100644
--- a/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/UsingScanQueries.java
+++ b/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/UsingScanQueries.java
@@ -16,17 +16,28 @@
  */
 package org.apache.ignite.snippets;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import javax.cache.Cache;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.QueryIndexType;
+import org.apache.ignite.cache.query.IndexQuery;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.ScanQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.lang.IgniteBiPredicate;
 import org.apache.ignite.lang.IgniteClosure;
 import org.junit.jupiter.api.Test;
 
+import static org.apache.ignite.cache.query.IndexQueryCriteriaBuilder.eq;
+import static org.apache.ignite.cache.query.IndexQueryCriteriaBuilder.gt;
+
 public class UsingScanQueries {
 
     @Test
@@ -84,4 +95,68 @@ public class UsingScanQueries {
             System.out.println("Transformer example output:" + keys.get(0));
         }
     }
+
+    @Test
+    void executingIndexQueriesExample() {
+        try (Ignite ignite = Ignition.start()) {
+            //tag::idxQry[]
+            // Create index by 2 fields (orgId, salary).
+            QueryEntity personEntity = new QueryEntity(Integer.class, Person.class)
+                .setFields(new LinkedHashMap<String, String>() {{
+                    put("orgId", Integer.class.getName());
+                    put("salary", Integer.class.getName());
+                }})
+                .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);
+
+            //end::idxQry[]
+            {
+            //tag::idxQry[]
+            // 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))
+            );
+            //end::idxQry[]
+            }
+
+            {
+                //tag::idxQryMultipleCriteria[]
+                // 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))
+                );
+                //end::idxQryMultipleCriteria[]
+            }
+
+            {
+                //tag::idxQryNoIdxName[]
+                // 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))
+                );
+                //end::idxQryNoIdxName[]
+            }
+
+            {
+                //tag::idxQryFilter[]
+                // 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"))
+                );
+                //end::idxQryFilter[]
+            }
+        }
+    }
 }
diff --git a/docs/_docs/distributed-computing/collocated-computations.adoc b/docs/_docs/distributed-computing/collocated-computations.adoc
index 47bd72f..03daeb6 100644
--- a/docs/_docs/distributed-computing/collocated-computations.adoc
+++ b/docs/_docs/distributed-computing/collocated-computations.adoc
@@ -105,7 +105,7 @@ tab:C++[unsupported]
 ====
 [discrete]
 === Performance Considerations
-Colocated computations yield performance benefits when the amount of the data you want to process is sufficiently large. In some cases, when the amount of data is small, a link:key-value-api/using-scan-queries[scan query] may perform better.
+Colocated computations yield performance benefits when the amount of the data you want to process is sufficiently large. In some cases, when the amount of data is small, a link:key-value-api/using-cache-queries[scan query] may perform better.
 
 ====
 
diff --git a/docs/_docs/events/events.adoc b/docs/_docs/events/events.adoc
index 3f7da30..cc08413 100644
--- a/docs/_docs/events/events.adoc
+++ b/docs/_docs/events/events.adoc
@@ -128,9 +128,9 @@ Cache events are also generated when you use DML commands.
 
 | EVT_CACHE_OBJECT_READ
 | An object is read from a cache.
-This event is not emitted when you use link:key-value-api/using-scan-queries[scan queries] (use <<Cache Query Events>> to monitor scan queries).
+This event is not emitted when you use link:key-value-api/using-cache-queries[scan queries] (use <<Cache Query Events>> to monitor scan queries).
 | The node where read operation is executed.
-It can be either the primary or backup node (the latter case is only possible when link:configuring-caches/configuration-overview#readfrombackup[reading from backups is enabled]).
+It can be either the primary or backup node (the latter case is only possible when link:configuring-caches/configuration-overview#readfrombackup[reading from backups] is enabled).
 In transactional caches, the event can be generated on both the primary and backup nodes depending on the concurrency and isolation levels.
 
 | EVT_CACHE_OBJECT_REMOVED | An object is removed from a cache. |The primary and backup nodes for the entry.
@@ -171,7 +171,7 @@ There are two types of events that are related to cache queries:
 [cols="2,5,3",opts="header"]
 |===
 | Event Type | Event Description | Where Event Is Fired
-| EVT_CACHE_QUERY_OBJECT_READ | An object is read as part of a query execution. This event is generated for every object that matches the link:key-value-api/using-scan-queries#executing-scan-queries[query filter]. | The primary node of the object that is read.
+| EVT_CACHE_QUERY_OBJECT_READ | An object is read as part of a query execution. This event is generated for every object that matches the link:key-value-api/using-cache-queries#executing-scan-queries[query filter]. | The primary node of the object that is read.
 | EVT_CACHE_QUERY_EXECUTED  |  This event is generated when a query is executed. | All server nodes that host the cache.
 |===
 
diff --git a/docs/_docs/key-value-api/using-scan-queries.adoc b/docs/_docs/key-value-api/using-cache-queries.adoc
similarity index 62%
rename from docs/_docs/key-value-api/using-scan-queries.adoc
rename to docs/_docs/key-value-api/using-cache-queries.adoc
index 5463e6f..f7f8035 100644
--- a/docs/_docs/key-value-api/using-scan-queries.adoc
+++ b/docs/_docs/key-value-api/using-cache-queries.adoc
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-= Using Scan Queries
+= Using Cache Queries
 
 :javaFile: {javaCodeDir}/UsingScanQueries.java
 :dotnetFile: code-snippets/dotnet/UsingScanQueries.cs
@@ -20,6 +20,7 @@
 
 == 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).
@@ -118,7 +119,64 @@ include::code-snippets/cpp/src/scan_query.cpp[tag=set-local,indent=0]
 ----
 --
 
+== Executing Index Queries
+
+[WARNING]
+====
+[discrete]
+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.
+
+[source,java]
+----
+include::{javaFile}[tag=idxQry,indent=0]
+----
+
+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.
+====
+
+[source,java]
+----
+include::{javaFile}[tag=idxQryMultipleCriteria,indent=0]
+----
+
+The index name is an optional parameter. In this case, Ignite tries to figure out the index by itself using specified criteria fields.
+
+[source,java]
+----
+include::{javaFile}[tag=idxQryNoIdxName,indent=0]
+----
+
+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.
+
+[source,java]
+----
+include::{javaFile}[tag=idxQryFilter,indent=0]
+----
+
 == Related Topics
 
 * link:restapi#sql-scan-query-execute[Execute scan query via REST API]
 * link:events/events#cache-query-events[Cache Query Events]
+* link:SQL/indexes[Defining Indexes]
diff --git a/docs/_docs/thin-clients/java-thin-client.adoc b/docs/_docs/thin-clients/java-thin-client.adoc
index b71d15a..2f71720 100644
--- a/docs/_docs/thin-clients/java-thin-client.adoc
+++ b/docs/_docs/thin-clients/java-thin-client.adoc
@@ -132,9 +132,9 @@ include::{sourceCodeFile}[tag=key-value-operations,indent=0]
 -------------------------------------------------------------------------------
 
 === Executing Scan Queries
-Use the `ScanQuery<K, V>` class to get a set of entries that satisfy a given condition. The thin client sends the query to the cluster node where it is executed as a normal link:key-value-api/using-scan-queries[scan query].
+Use the `ScanQuery<K, V>` class to get a set of entries that satisfy a given condition. The thin client sends the query to the cluster node where it is executed as a regular link:key-value-api/using-cache-queries[scan query].
 
-The query condition is specified by an `IgniteBiPredicate<K, V>` object that is passed to the query constructor as an argument. The predicate is applied on the server side. If you don't provide any predicate, the query returns all cache entries.
+The query condition is specified by an `IgniteBiPredicate<K, V>` object that is passed to the query constructor as an argument. The predicate is applied on the server side. If there is no predicate provided, the query returns all cache entries.
 
 NOTE: The classes of the predicates must be available on the server nodes of the cluster.
 
@@ -379,4 +379,4 @@ include::{sourceCodeFile}[tag=async-api,indent=0]
 
 * Async methods do not block the calling thread
 * Async methods return `IgniteClientFuture<T>` which is a combination of `Future<T>` and `CompletionStage<T>`.
-* Async continuations are executed using `ClientConfiguration.AsyncContinuationExecutor`, which defaults to `ForkJoinPool#commonPool()`. For example, `cache.getAsync(1).thenAccept(val -> System.out.println(val))` will execute the `println` call using a thread from the `commonPool`.
\ No newline at end of file
+* Async continuations are executed using `ClientConfiguration.AsyncContinuationExecutor`, which defaults to `ForkJoinPool#commonPool()`. For example, `cache.getAsync(1).thenAccept(val -> System.out.println(val))` will execute the `println` call using a thread from the `commonPool`.
diff --git a/examples/src/main/java/org/apache/ignite/examples/datagrid/CacheQueryExample.java b/examples/src/main/java/org/apache/ignite/examples/datagrid/CacheQueryExample.java
index c486a5c..0a9838a 100644
--- a/examples/src/main/java/org/apache/ignite/examples/datagrid/CacheQueryExample.java
+++ b/examples/src/main/java/org/apache/ignite/examples/datagrid/CacheQueryExample.java
@@ -24,6 +24,7 @@ import org.apache.ignite.Ignition;
 import org.apache.ignite.binary.BinaryObject;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.affinity.AffinityKey;
+import org.apache.ignite.cache.query.IndexQuery;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
@@ -34,8 +35,11 @@ import org.apache.ignite.examples.model.Organization;
 import org.apache.ignite.examples.model.Person;
 import org.apache.ignite.lang.IgniteBiPredicate;
 
+import static org.apache.ignite.cache.query.IndexQueryCriteriaBuilder.eq;
+import static org.apache.ignite.cache.query.IndexQueryCriteriaBuilder.gt;
+
 /**
- * Cache queries example. This example demonstrates TEXT and FULL SCAN
+ * Cache queries example. This example demonstrates TEXT, FULL SCAN and INDEX
  * queries over cache.
  * <p>
  * Example also demonstrates usage of fields queries that return only required
@@ -104,6 +108,9 @@ public class CacheQueryExample {
 
                 // Example for TEXT-based querying for a given string in peoples resumes.
                 textQuery();
+
+                // Example for INDEX-based query with index criteria.
+                indexQuery();
             }
             finally {
                 // Distributed cache could be removed from cluster only by Ignite.destroyCache() call.
@@ -153,6 +160,47 @@ public class CacheQueryExample {
     }
 
     /**
+     * Example for query indexes with criteria and binary objects.
+     */
+    private static void indexQuery() {
+        IgniteCache<Long, Person> cache = Ignition.ignite().cache(PERSON_CACHE);
+
+        // Query for all people who work in the organization "ApacheIgnite".
+        QueryCursor<Cache.Entry<Long, Person>> igniters = cache.query(
+            new IndexQuery<Long, Person>(Person.class)
+                .setCriteria(eq("orgId", 1L))
+        );
+
+        print("Following people work in the 'ApacheIgnite' organization (queried with INDEX query): ",
+            igniters.getAll());
+
+        // Query for all people who work in the organization "Other" and have salary more than 1,500.
+        QueryCursor<Cache.Entry<Long, Person>> others = cache.query(
+            new IndexQuery<Long, Person>(Person.class)  // Index name {@link Person#ORG_SALARY_IDX} is optional.
+                .setCriteria(eq("orgId", 2L), gt("salary", 1500.0)));
+
+        print("Following people work in the 'Other' organizations and have salary more than 1500 (queried with INDEX query): ",
+            others.getAll());
+
+        // Query for all people who have salary more than 1,500 using BinaryObject.
+        QueryCursor<Cache.Entry<BinaryObject, BinaryObject>> rich = cache.withKeepBinary().query(
+            new IndexQuery<BinaryObject, BinaryObject>(Person.class.getName())
+                .setCriteria(gt("salary", 1500.0)));
+
+        print("Following people have salary more than 1500 (queried with INDEX query and using binary objects): ",
+            rich.getAll());
+
+        // Query for all people who have salary more than 1,500 and have 'Master Degree' in their resumes.
+        QueryCursor<Cache.Entry<BinaryObject, BinaryObject>> richMasters = cache.withKeepBinary().query(
+            new IndexQuery<BinaryObject, BinaryObject>(Person.class.getName())
+                .setCriteria(gt("salary", 1500.0))
+                .setFilter((k, v) -> v.<String>field("resume").contains("Master")));
+
+        print("Following people have salary more than 1500 and Master degree (queried with INDEX query): ",
+            richMasters.getAll());
+    }
+
+    /**
      * Populate cache with test data.
      */
     private static void initialize() {
diff --git a/examples/src/main/java/org/apache/ignite/examples/model/Person.java b/examples/src/main/java/org/apache/ignite/examples/model/Person.java
index 6d3a6df..29a2d9d 100644
--- a/examples/src/main/java/org/apache/ignite/examples/model/Person.java
+++ b/examples/src/main/java/org/apache/ignite/examples/model/Person.java
@@ -30,12 +30,15 @@ public class Person implements Serializable {
     /** */
     private static final AtomicLong ID_GEN = new AtomicLong();
 
+    /** Name of index by two fields (orgId, salary). */
+    public static final String ORG_SALARY_IDX = "ORG_SALARY_IDX";
+
     /** Person ID (indexed). */
     @QuerySqlField(index = true)
     public Long id;
 
     /** Organization ID (indexed). */
-    @QuerySqlField(index = true)
+    @QuerySqlField(index = true, orderedGroups = @QuerySqlField.Group(name = ORG_SALARY_IDX, order = 0))
     public Long orgId;
 
     /** First name (not-indexed). */
@@ -51,10 +54,10 @@ public class Person implements Serializable {
     public String resume;
 
     /** Salary (indexed). */
-    @QuerySqlField(index = true)
+    @QuerySqlField(index = true, orderedGroups = @QuerySqlField.Group(name = ORG_SALARY_IDX, order = 1))
     public double salary;
 
-    /** Custom cache key to guarantee that person is always collocated with its organization. */
+    /** Custom cache key to guarantee that person is always colocated with its organization. */
     private transient AffinityKey<Long> key;
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/query/IndexQuery.java b/modules/core/src/main/java/org/apache/ignite/cache/query/IndexQuery.java
index 25f7807..1c059eb 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/query/IndexQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/query/IndexQuery.java
@@ -29,12 +29,15 @@ import org.apache.ignite.lang.IgniteExperimental;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Index query runs over internal index structure and returns cache entries for index rows.
+ * Index queries work over distributed indexes and retrieve cache entries that match the specified criteria.
+ * {@code QueryCursor} delivers sorted cache entries by the order defined for queried index.
  *
- * {@code IndexQuery} has to be initialized with cache value class or type. The algorithm of discovering index is as following:
+ * {@code IndexQuery} has to be initialized with cache value class or type. The algorithm of discovering index is as follows:
  * 1. If {@link #idxName} is set, then use it.
  * 2. If {@link #idxName} is not set, then find an index that matches criteria fields.
- * 3. If neither of {@link #idxName} or {@link #setCriteria(List)} used, then perform index scan over PK index for specified Value type.
+ * 3. If neither {@link #idxName}, nor {@link #setCriteria(List)} is used, then perform index scan over PK index for specified Value type.
+ *
+ * Conjuction of items in {@link #criteria} has to represent a valid range to traverse the index tree.
  */
 @IgniteExperimental
 public final class IndexQuery<K, V> extends Query<Cache.Entry<K, V>> {