-
Notifications
You must be signed in to change notification settings - Fork 1
CLIENT-4226 Support selecting secondary index by name hint #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1770f4d
dd09ab1
3edb2de
34da072
8c827d8
fef2324
1e80db2
0ff1f3e
e9e25e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,75 @@ The parser then does the following: | |
|
|
||
| > **Note:** Only one secondary index can be used per query. The index will be chosen based on cardinality (preferring indexes with a higher `binValuesRatio`), otherwise alphabetically. | ||
|
|
||
| ## Choosing a Specific Index (Index Hint) | ||
|
|
||
| When multiple indexes could satisfy your query, the parser selects one automatically. | ||
| You can override this by providing an explicit hint in two ways: | ||
|
|
||
| ### Hint by Index Name | ||
|
|
||
| If you know the exact name of the index you want to use, pass it as the third argument to | ||
| the three-parameter `IndexContext.of` overload: | ||
|
|
||
| ```java | ||
| Index cityIndex = Index.builder() | ||
| .namespace("test") // is mandatory | ||
| .bin("city") // is mandatory | ||
| .indexType(IndexType.STRING) // is mandatory | ||
| .binValuesRatio(1) // is mandatory and must be non-negative | ||
| .name("idx_users_city") | ||
| .build(); | ||
|
|
||
| Index ageIndex = Index.builder() | ||
| .namespace("test") | ||
| .bin("age") | ||
| .indexType(IndexType.NUMERIC) | ||
| .binValuesRatio(10) | ||
| .name("idx_users_age") | ||
| .build(); | ||
|
|
||
| // Force use of the city index even though age has higher cardinality | ||
| IndexContext indexContext = IndexContext.of("test", List.of(cityIndex, ageIndex), "idx_users_city"); | ||
| ``` | ||
|
|
||
| If the named index is not found in the collection or does not match the namespace, | ||
| the parser falls back to automatic selection (cardinality, then alphabetically). | ||
| Passing `null` or an empty string as the third parameter also triggers automatic selection. | ||
|
|
||
| ### Hint by Bin Name | ||
|
|
||
| If you know which bin should be used for the secondary index filter but not the specific index name, | ||
| use `IndexContext.withBinHint`: | ||
|
|
||
| ```java | ||
| Index cityIndex = Index.builder() | ||
| .namespace("test") | ||
| .bin("city") | ||
| .indexType(IndexType.STRING) | ||
| .binValuesRatio(1) | ||
| .name("idx_users_city") | ||
| .build(); | ||
|
|
||
| Index ageIndex = Index.builder() | ||
| .namespace("test") | ||
| .bin("age") | ||
| .indexType(IndexType.NUMERIC) | ||
| .binValuesRatio(10) | ||
| .name("idx_users_age") | ||
| .build(); | ||
|
|
||
| // Force use of an index on the "city" bin even though age has higher cardinality | ||
| IndexContext indexContext = IndexContext.withBinHint("test", List.of(cityIndex, ageIndex), "city"); | ||
| ``` | ||
|
|
||
| The bin hint behaves as follows: | ||
|
|
||
| - If exactly one index in the indexes collection matches the given bin name and namespace, that index is used. | ||
| - If the bin name matches multiple indexes (for example, indexes of different types on the same bin), | ||
| or if there is no match, the hint is ignored and the parser falls back to fully automatic selection | ||
| (by cardinality, then alphabetically). | ||
| - Passing `null` or a blank string bin name hint also triggers fallback to automatic selection. | ||
|
|
||
| ## Usage Example | ||
|
|
||
| Let's assume you have a secondary index named `idx_users_city` on the `city` bin in the `users` set. | ||
|
|
@@ -39,17 +108,17 @@ import com.aerospike.dsl.Index; | |
| import com.aerospike.dsl.IndexContext; | ||
| import java.util.List; | ||
|
|
||
| // Describe the available secondary index | ||
| // Describe the available secondary index (namespace, bin, indexType, binValuesRatio are required) | ||
| Index cityIndex = Index.builder() | ||
| .name("idx_users_city") | ||
| .namespace("test") | ||
| .bin("city") | ||
| .indexType(IndexType.STRING) | ||
| .binValuesRatio(1) // Cardinality can be retrieved from the Aerospike DB or set manually | ||
| .binValuesRatio(1) // Cardinality from Aerospike sindex-stat or set manually | ||
| .name("idx_users_city") | ||
| .build(); | ||
|
|
||
| // Create index context | ||
| IndexContext indexContext = IndexContext.of("namespace", List.of(cityIndex)); | ||
| // Create index context (namespace must not be null or blank) | ||
| IndexContext indexContext = IndexContext.of("test", List.of(cityIndex)); | ||
|
||
| ``` | ||
|
|
||
| ### 2. Parse the Expression using IndexContext | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,122 @@ | ||
| package com.aerospike.dsl; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * This class stores namespace and indexes required to build secondary index Filter | ||
| */ | ||
| @AllArgsConstructor(staticName = "of") | ||
| @Getter | ||
| public class IndexContext { | ||
|
|
||
| /** | ||
| * Namespace to be used for creating secondary index Filter. Is matched with namespace of indexes | ||
| */ | ||
| private String namespace; | ||
| private final String namespace; | ||
| /** | ||
| * Collection of {@link Index} objects to be used for creating secondary index Filter. | ||
| * Namespace of indexes is matched with the given {@link #namespace}, bin name and index type are matched | ||
| * with bins in DSL String | ||
| */ | ||
| private Collection<Index> indexes; | ||
| private final Collection<Index> indexes; | ||
|
|
||
| private IndexContext(String namespace, Collection<Index> indexes) { | ||
| this.namespace = namespace; | ||
| this.indexes = indexes; | ||
| } | ||
|
|
||
| /** | ||
| * Create index context with namespace and indexes. | ||
| * | ||
| * @param namespace Namespace to be used for creating {@link com.aerospike.dsl.client.query.Filter}. | ||
| * Must not be null or blank | ||
| * @param indexes Collection of {@link Index} objects to be used for creating Filter | ||
| * @return A new instance of {@code IndexContext} | ||
| */ | ||
| public static IndexContext of(String namespace, Collection<Index> indexes) { | ||
| validateNamespace(namespace); | ||
| return new IndexContext(namespace, indexes); | ||
| } | ||
|
|
||
| /** | ||
| * Create index context specifying the index to be used | ||
| * | ||
| * @param namespace Namespace to be used for creating {@link com.aerospike.dsl.client.query.Filter}. | ||
| * Must not be null or blank | ||
| * @param indexes Collection of {@link Index} objects to be used for creating Filter | ||
| * @param indexToUse The name of an index to use. If not found, null, or empty, index is chosen | ||
| * by cardinality or alphabetically | ||
| * @return A new instance of {@code IndexContext} | ||
| */ | ||
| public static IndexContext of(String namespace, Collection<Index> indexes, String indexToUse) { | ||
| validateNamespace(namespace); | ||
| if (indexes == null || indexToUse == null || indexToUse.isBlank()) { | ||
| return new IndexContext(namespace, indexes); | ||
| } | ||
| List<Index> matchingIndexes = indexes.stream() | ||
| .filter(idx -> indexMatches(idx, namespace, indexToUse)) | ||
| .toList(); | ||
| return new IndexContext(namespace, matchingIndexes.isEmpty() ? indexes : matchingIndexes); | ||
|
Comment on lines
53
to
61
|
||
| } | ||
|
|
||
| /** | ||
| * Create index context with a bin name hint specifying which bin's index to use. | ||
| * If exactly one index in the collection matches the given bin name and namespace, | ||
| * that index is used. Otherwise, all indexes are kept and selection falls back to | ||
| * the automatic cardinality / alphabetical strategy. | ||
| * | ||
| * @param namespace Namespace to be used for creating {@link com.aerospike.dsl.client.query.Filter}. | ||
| * Must not be null or blank | ||
| * @param indexes Collection of {@link Index} objects to be used for creating Filter | ||
| * @param binToUse The name of the bin whose index should be used. If not found, null, | ||
| * blank, or matches multiple indexes, index is chosen automatically | ||
| * @return A new instance of {@code IndexContext} | ||
| */ | ||
| public static IndexContext withBinHint(String namespace, Collection<Index> indexes, String binToUse) { | ||
| validateNamespace(namespace); | ||
| if (indexes == null || binToUse == null || binToUse.isBlank()) { | ||
| return new IndexContext(namespace, indexes); | ||
| } | ||
| List<Index> matchingIndexes = indexes.stream() | ||
| .filter(idx -> binMatches(idx, namespace, binToUse)) | ||
| .toList(); | ||
| return new IndexContext(namespace, matchingIndexes.size() == 1 ? matchingIndexes : indexes); | ||
|
Comment on lines
77
to
85
|
||
| } | ||
|
|
||
| private static void validateNamespace(String namespace) { | ||
| if (namespace == null) { | ||
| throw new IllegalArgumentException("namespace must not be null"); | ||
| } | ||
| if (namespace.isBlank()) { | ||
| throw new IllegalArgumentException("namespace must not be blank"); | ||
| } | ||
| } | ||
|
|
||
| private static boolean binMatches(Index idx, String namespace, String binToUse) { | ||
| if (idx == null || binToUse == null) { | ||
| return false; | ||
| } | ||
|
|
||
| String binName = idx.getBin(); | ||
| if (binName == null || !binName.equals(binToUse)) { | ||
| return false; | ||
| } | ||
|
|
||
| return namespace.equals(idx.getNamespace()); | ||
| } | ||
|
|
||
| private static boolean indexMatches(Index idx, String namespace, String indexToUse) { | ||
| if (idx == null || indexToUse == null) { | ||
| return false; | ||
| } | ||
|
|
||
| String indexName = idx.getName(); | ||
| if (indexName == null || !indexName.equals(indexToUse)) { | ||
| return false; | ||
| } | ||
|
|
||
| return namespace.equals(idx.getNamespace()); | ||
| } | ||
|
Comment on lines
+110
to
+121
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new 3-parameter IndexContext.of method with indexToUse parameter should be documented here. This is a public API addition that allows users to specify an index hint, which is a key feature mentioned in the PR title CLIENT-4226.