From c2fe9ec21a25c317a7343ef99895171724bae815 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Wed, 25 Jan 2023 17:00:02 +0200 Subject: [PATCH] FMWK-125 Rollback for query support (#90) --- .../documentapi/AerospikeDocumentClient.java | 93 ---- .../AerospikeDocumentRepository.java | 12 - .../documentapi/IAerospikeDocumentClient.java | 23 - .../IAerospikeDocumentRepository.java | 5 - .../documentapi/data/DocFilterExp.java | 46 -- .../documentapi/data/DocFilterSecIndex.java | 45 -- .../documentapi/data/DocumentFilter.java | 7 - .../documentapi/data/DocumentFilterExp.java | 24 - .../data/DocumentFilterSecIndex.java | 23 - .../data/DocumentQueryStatement.java | 30 -- .../aerospike/documentapi/data/KeyResult.java | 10 - .../aerospike/documentapi/data/Operator.java | 31 -- .../documentapi/util/ExpConverter.java | 257 ---------- .../documentapi/util/FilterConverter.java | 170 ------- .../documentapi/DocumentQueryTests.java | 454 ------------------ 15 files changed, 1230 deletions(-) delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocFilterExp.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocFilterSecIndex.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocumentFilter.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocumentFilterExp.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocumentFilterSecIndex.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/DocumentQueryStatement.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/KeyResult.java delete mode 100644 src/main/java/com/aerospike/documentapi/data/Operator.java delete mode 100644 src/main/java/com/aerospike/documentapi/util/ExpConverter.java delete mode 100644 src/main/java/com/aerospike/documentapi/util/FilterConverter.java delete mode 100644 src/test/java/com/aerospike/documentapi/DocumentQueryTests.java diff --git a/src/main/java/com/aerospike/documentapi/AerospikeDocumentClient.java b/src/main/java/com/aerospike/documentapi/AerospikeDocumentClient.java index 1c7de6d..5bd0c3d 100644 --- a/src/main/java/com/aerospike/documentapi/AerospikeDocumentClient.java +++ b/src/main/java/com/aerospike/documentapi/AerospikeDocumentClient.java @@ -3,40 +3,24 @@ import com.aerospike.client.BatchRecord; import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; -import com.aerospike.client.exp.Exp; -import com.aerospike.client.exp.Expression; import com.aerospike.client.policy.BatchPolicy; import com.aerospike.client.policy.Policy; -import com.aerospike.client.policy.QueryPolicy; import com.aerospike.client.policy.WritePolicy; -import com.aerospike.client.query.Filter; -import com.aerospike.client.query.KeyRecord; import com.aerospike.documentapi.batch.BatchOperation; -import com.aerospike.documentapi.data.DocumentFilter; -import com.aerospike.documentapi.data.DocumentFilterExp; -import com.aerospike.documentapi.data.DocumentQueryStatement; -import com.aerospike.documentapi.data.DocumentFilterSecIndex; -import com.aerospike.documentapi.data.KeyResult; import com.aerospike.documentapi.jsonpath.JsonPathObject; import com.aerospike.documentapi.jsonpath.JsonPathParser; import com.aerospike.documentapi.jsonpath.JsonPathQuery; import com.aerospike.documentapi.policy.DocumentPolicy; import com.aerospike.documentapi.util.Lut; import com.fasterxml.jackson.databind.JsonNode; -import net.minidev.json.JSONArray; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Spliterator; -import java.util.Spliterators; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; /** * Primary object for accessing and mutating documents. @@ -47,14 +31,12 @@ public class AerospikeDocumentClient implements IAerospikeDocumentClient { private final Policy readPolicy; private final WritePolicy writePolicy; private final BatchPolicy batchPolicy; - private final QueryPolicy queryPolicy; public AerospikeDocumentClient(IAerospikeClient client) { this.aerospikeDocumentRepository = new AerospikeDocumentRepository(client); this.readPolicy = client.getReadPolicyDefault(); this.writePolicy = client.getWritePolicyDefault(); this.batchPolicy = client.getBatchPolicyDefault(); - this.queryPolicy = client.getQueryPolicyDefault(); } public AerospikeDocumentClient(IAerospikeClient client, DocumentPolicy documentPolicy) { @@ -62,7 +44,6 @@ public AerospikeDocumentClient(IAerospikeClient client, DocumentPolicy documentP this.readPolicy = documentPolicy.getReadPolicy(); this.writePolicy = documentPolicy.getWritePolicy(); this.batchPolicy = documentPolicy.getBatchPolicy(); - this.queryPolicy = documentPolicy.getQueryPolicy(); } @Override @@ -194,80 +175,6 @@ public List batchPerform(List batchOperations, bool .collect(Collectors.toList()); } - @Override - public Stream query(DocumentQueryStatement queryStatement, DocumentFilter... docFilters) { - QueryPolicy policy = new QueryPolicy(queryPolicy); - policy.filterExp = getFilterExp(docFilters); - - Filter secIndexFilter = getSecIndexFilter(docFilters); - Stream keyRecords = StreamSupport.stream(Spliterators.spliteratorUnknownSize( - aerospikeDocumentRepository.query(policy, queryStatement.toStatement(secIndexFilter)).iterator(), - Spliterator.ORDERED - ), false); - - // no need to parse if there is no jsonPath given - if (queryStatement.getJsonPaths() == null || queryStatement.getJsonPaths().length == 0) { - return keyRecords.map(keyRecord -> new KeyResult(keyRecord.key, keyRecord.record)); - } - - // parsing KeyRecords to return the required objects - return keyRecords - .map(keyRecord -> getKeyResult(keyRecord.key, - getResults(queryStatement.getJsonPaths(), keyRecord.record.bins))) - .filter(Objects::nonNull); - } - - private Filter getSecIndexFilter(DocumentFilter[] docFilters) { - if (docFilters == null || docFilters.length == 0) return null; - - return Arrays.stream(docFilters) - .filter(Objects::nonNull) - .filter(DocumentFilterSecIndex.class::isInstance) - .map(filterExp -> ((DocumentFilterSecIndex) filterExp).toSecIndexFilter()) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - } - - private KeyResult getKeyResult(Key key, Map results) { - return results.isEmpty() ? null : new KeyResult(key, results); - } - - private Expression getFilterExp(DocumentFilter[] docFilters) { - if (docFilters == null || docFilters.length == 0) return null; - - List filterExps = Arrays.stream(docFilters) - .filter(Objects::nonNull) - .filter(DocumentFilterExp.class::isInstance) - .map(filterExp -> ((DocumentFilterExp) filterExp).toFilterExp()) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - if (filterExps.isEmpty()) return null; - - Exp expResult = filterExps.size() == 1 ? - filterExps.get(0) - : Exp.and(filterExps.toArray(new Exp[0])); - return Exp.build(expResult); - } - - private Map getResults(String[] jsonPaths, Map bins) { - if (jsonPaths == null || jsonPaths.length == 0) return Collections.emptyMap(); - - Map res = new HashMap<>(); - bins.values() - .forEach(binValue -> Arrays.stream(jsonPaths) - .forEach(jsonPath -> addNonNull(jsonPath, JsonPathQuery.read(binValue, jsonPath), res)) - ); - return res; - } - - private void addNonNull(String jsonPath, Object readRes, Map res) { - if (readRes == null || (readRes instanceof JSONArray && ((JSONArray) readRes).isEmpty())) return; - - res.put(jsonPath, readRes); - } - private WritePolicy getLutPolicy(Map result) { return Lut.setLutPolicy(new WritePolicy(writePolicy), (long) result.get(Lut.LUT_BIN)); } diff --git a/src/main/java/com/aerospike/documentapi/AerospikeDocumentRepository.java b/src/main/java/com/aerospike/documentapi/AerospikeDocumentRepository.java index 376b384..947c3dc 100644 --- a/src/main/java/com/aerospike/documentapi/AerospikeDocumentRepository.java +++ b/src/main/java/com/aerospike/documentapi/AerospikeDocumentRepository.java @@ -10,10 +10,7 @@ import com.aerospike.client.cdt.MapOperation; import com.aerospike.client.policy.BatchPolicy; import com.aerospike.client.policy.Policy; -import com.aerospike.client.policy.QueryPolicy; import com.aerospike.client.policy.WritePolicy; -import com.aerospike.client.query.RecordSet; -import com.aerospike.client.query.Statement; import com.aerospike.documentapi.jsonpath.JsonPathObject; import com.aerospike.documentapi.jsonpath.PathDetails; import com.aerospike.documentapi.util.Lut; @@ -213,13 +210,4 @@ public boolean batchPerform(BatchPolicy batchPolicy, List batchReco throw DocumentApiException.toDocumentException(e); } } - - @Override - public RecordSet query(QueryPolicy policy, Statement statement) { - try { - return client.query(policy, statement); - } catch (AerospikeException e) { - throw DocumentApiException.toDocumentException(e); - } - } } diff --git a/src/main/java/com/aerospike/documentapi/IAerospikeDocumentClient.java b/src/main/java/com/aerospike/documentapi/IAerospikeDocumentClient.java index b06b3b0..6d43539 100644 --- a/src/main/java/com/aerospike/documentapi/IAerospikeDocumentClient.java +++ b/src/main/java/com/aerospike/documentapi/IAerospikeDocumentClient.java @@ -3,16 +3,11 @@ import com.aerospike.client.BatchRecord; import com.aerospike.client.Key; import com.aerospike.documentapi.batch.BatchOperation; -import com.aerospike.documentapi.data.DocFilterExp; -import com.aerospike.documentapi.data.DocumentFilter; -import com.aerospike.documentapi.data.DocumentQueryStatement; -import com.aerospike.documentapi.data.KeyResult; import com.fasterxml.jackson.databind.JsonNode; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.stream.Stream; public interface IAerospikeDocumentClient { @@ -124,22 +119,4 @@ public interface IAerospikeDocumentClient { * @throws IllegalArgumentException if the batch has multiple two-step operations with the same key. */ List batchPerform(List batchOperations, boolean parallel); - - /** - * Perform query. - *

Filtering can be done by setting one or more of the following items:

- *
    - *
  • optional secondary index filter (record level),
  • - *
  • optional document filter expressions (record level),
  • - *
  • optional bin names (bin level),
  • - *
  • optional json paths (inner objects less than a bin if necessary).
  • - *
- * - * @param queryStatement object for building query definition, storing required bin names and json paths - * @param documentFilters filters (can include one secondary index filter and/or one or more filter expressions; - * if there are multiple filter expressions given they are concatenated using logical AND) - * @return stream of {@link KeyResult} objects - * @throws DocumentApiException if query fails - */ - Stream query(DocumentQueryStatement queryStatement, DocumentFilter... documentFilters); } diff --git a/src/main/java/com/aerospike/documentapi/IAerospikeDocumentRepository.java b/src/main/java/com/aerospike/documentapi/IAerospikeDocumentRepository.java index 57044a2..f842137 100644 --- a/src/main/java/com/aerospike/documentapi/IAerospikeDocumentRepository.java +++ b/src/main/java/com/aerospike/documentapi/IAerospikeDocumentRepository.java @@ -4,10 +4,7 @@ import com.aerospike.client.Key; import com.aerospike.client.policy.BatchPolicy; import com.aerospike.client.policy.Policy; -import com.aerospike.client.policy.QueryPolicy; import com.aerospike.client.policy.WritePolicy; -import com.aerospike.client.query.RecordSet; -import com.aerospike.client.query.Statement; import com.aerospike.documentapi.jsonpath.JsonPathObject; import com.fasterxml.jackson.databind.JsonNode; @@ -36,6 +33,4 @@ void append(WritePolicy writePolicy, Key key, Collection binNames, Strin void delete(WritePolicy writePolicy, Key key, Collection binNames, JsonPathObject jsonPathObject); boolean batchPerform(BatchPolicy batchPolicy, List batchRecords); - - RecordSet query(QueryPolicy policy, Statement statement); } diff --git a/src/main/java/com/aerospike/documentapi/data/DocFilterExp.java b/src/main/java/com/aerospike/documentapi/data/DocFilterExp.java deleted file mode 100644 index ee4cc71..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocFilterExp.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.exp.Exp; -import com.aerospike.documentapi.util.ExpConverter; - -public class DocFilterExp implements DocumentFilterExp { - - private final String binName; - private final String jsonPath; - private final Operator operator; - private final Object value; - private Integer regexFlags = null; - - public DocFilterExp(String binName, String jsonPath, Operator operator, Object value) { - this.binName = binName; - this.jsonPath = jsonPath; - this.operator = operator; - this.value = value; - } - - public Exp toFilterExp() { - switch (operator) { - case LT: - return ExpConverter.lt(binName, jsonPath, value); - case GT: - return ExpConverter.gt(binName, jsonPath, value); - case LE: - return ExpConverter.le(binName, jsonPath, value); - case GE: - return ExpConverter.ge(binName, jsonPath, value); - case EQ: - return ExpConverter.eq(binName, jsonPath, value); - case NE: - return ExpConverter.ne(binName, jsonPath, value); - case REGEX: - return ExpConverter.regex(binName, jsonPath, value.toString(), regexFlags); - default: - return null; - } - } - - @Override - public void setRegexFlags(int regexFlags) { - this.regexFlags = regexFlags; - } -} diff --git a/src/main/java/com/aerospike/documentapi/data/DocFilterSecIndex.java b/src/main/java/com/aerospike/documentapi/data/DocFilterSecIndex.java deleted file mode 100644 index 8b16440..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocFilterSecIndex.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.query.Filter; -import com.aerospike.client.query.IndexCollectionType; -import com.aerospike.documentapi.util.FilterConverter; - -import static com.aerospike.client.query.IndexCollectionType.DEFAULT; - -public class DocFilterSecIndex implements DocumentFilterSecIndex { - - private final String binName; - private final String jsonPath; - private final Operator operator; - private final Object value; - private IndexCollectionType idxCollectionType = DEFAULT; - - public DocFilterSecIndex(String binName, String jsonPath, Operator operator, Object value) { - this.binName = binName; - this.jsonPath = jsonPath; - this.operator = operator; - this.value = value; - } - - public Filter toSecIndexFilter() { - switch (operator) { - case LT: - return FilterConverter.lt(binName, jsonPath, value, idxCollectionType); - case GT: - return FilterConverter.gt(binName, jsonPath, value, idxCollectionType); - case LE: - return FilterConverter.le(binName, jsonPath, value, idxCollectionType); - case GE: - return FilterConverter.ge(binName, jsonPath, value, idxCollectionType); - case EQ: - return FilterConverter.eq(binName, jsonPath, value); - default: - throw new UnsupportedOperationException(String.format("'%s' secondary filter is not supported", operator)); - } - } - - @Override - public void setIdxCollectionType(IndexCollectionType idxCollectionType) { - this.idxCollectionType = idxCollectionType; - } -} diff --git a/src/main/java/com/aerospike/documentapi/data/DocumentFilter.java b/src/main/java/com/aerospike/documentapi/data/DocumentFilter.java deleted file mode 100644 index cfecefe..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocumentFilter.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.aerospike.documentapi.data; - -/** - * Document filter interface extended by {@link DocumentFilterExp} and {@link DocumentFilterSecIndex}. - */ -public interface DocumentFilter { -} diff --git a/src/main/java/com/aerospike/documentapi/data/DocumentFilterExp.java b/src/main/java/com/aerospike/documentapi/data/DocumentFilterExp.java deleted file mode 100644 index b6f2f3f..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocumentFilterExp.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.exp.Exp; - -/** - * Base interface for creating filter expression. - * - *

For the supported json paths see {@link com.aerospike.documentapi.util.ExpConverter}.

- *

Supported operators:

- *
    - *
  • EQ
  • - *
  • NE
  • - *
  • GT
  • - *
  • GE
  • - *
  • LT
  • - *
  • LE
  • - *
  • REGEX
  • - *
- */ -public interface DocumentFilterExp extends DocumentFilter { - Exp toFilterExp(); - - void setRegexFlags(int regexFlags); -} diff --git a/src/main/java/com/aerospike/documentapi/data/DocumentFilterSecIndex.java b/src/main/java/com/aerospike/documentapi/data/DocumentFilterSecIndex.java deleted file mode 100644 index cb1f3d7..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocumentFilterSecIndex.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.query.Filter; -import com.aerospike.client.query.IndexCollectionType; - -/** - * Base interface for creating secondary index filter. - * - *

For the supported json paths see {@link com.aerospike.documentapi.util.FilterConverter}.

- *

Supported operators:

- *
    - *
  • EQ
  • - *
  • GT
  • - *
  • GE
  • - *
  • LT
  • - *
  • LE
  • - *
- */ -public interface DocumentFilterSecIndex extends DocumentFilter { - Filter toSecIndexFilter(); - - void setIdxCollectionType(IndexCollectionType idxCollectionType); -} diff --git a/src/main/java/com/aerospike/documentapi/data/DocumentQueryStatement.java b/src/main/java/com/aerospike/documentapi/data/DocumentQueryStatement.java deleted file mode 100644 index d8015ef..0000000 --- a/src/main/java/com/aerospike/documentapi/data/DocumentQueryStatement.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.query.Filter; -import com.aerospike.client.query.Statement; -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class DocumentQueryStatement { - String namespace; - String setName; - String indexName; - String[] binNames; - long maxRecords; - int recordsPerSecond; - String[] jsonPaths; - - public Statement toStatement(Filter secIndexFilter) { - Statement statement = new Statement(); - statement.setNamespace(namespace); - statement.setSetName(setName); - statement.setIndexName(indexName); - statement.setBinNames(binNames); - statement.setMaxRecords(maxRecords); - statement.setRecordsPerSecond(recordsPerSecond); - statement.setFilter(secIndexFilter); - return statement; - } -} diff --git a/src/main/java/com/aerospike/documentapi/data/KeyResult.java b/src/main/java/com/aerospike/documentapi/data/KeyResult.java deleted file mode 100644 index 598f17f..0000000 --- a/src/main/java/com/aerospike/documentapi/data/KeyResult.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.aerospike.documentapi.data; - -import com.aerospike.client.Key; -import lombok.Value; - -@Value -public class KeyResult { - Key key; - Object result; -} diff --git a/src/main/java/com/aerospike/documentapi/data/Operator.java b/src/main/java/com/aerospike/documentapi/data/Operator.java deleted file mode 100644 index 5ec0a26..0000000 --- a/src/main/java/com/aerospike/documentapi/data/Operator.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.aerospike.documentapi.data; - -public enum Operator { - - EQ("=="), - NE("!="), - LT("<"), - GT(">"), - GE(">="), - LE("<="), - REGEX("=~"); - - private final String name; - - Operator(String operatorName) { - name = operatorName; - } - - public static Operator fromString(String name) { - for (Operator v : Operator.values()) { - if (v.name.equalsIgnoreCase(name)) { - return v; - } - } - return null; - } - - public String getName() { - return this.name; - } -} diff --git a/src/main/java/com/aerospike/documentapi/util/ExpConverter.java b/src/main/java/com/aerospike/documentapi/util/ExpConverter.java deleted file mode 100644 index 8f640a4..0000000 --- a/src/main/java/com/aerospike/documentapi/util/ExpConverter.java +++ /dev/null @@ -1,257 +0,0 @@ -package com.aerospike.documentapi.util; - -import com.aerospike.client.cdt.CTX; -import com.aerospike.client.cdt.ListReturnType; -import com.aerospike.client.cdt.MapReturnType; -import com.aerospike.client.exp.Exp; -import com.aerospike.client.exp.ListExp; -import com.aerospike.client.exp.MapExp; -import com.aerospike.documentapi.DocumentApiException; -import com.aerospike.documentapi.jsonpath.JsonPathObject; -import com.aerospike.documentapi.jsonpath.JsonPathParser; -import com.aerospike.documentapi.token.ContextAwareToken; -import com.aerospike.documentapi.token.ListToken; -import com.aerospike.documentapi.token.MapToken; -import lombok.experimental.UtilityClass; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static com.aerospike.documentapi.util.Utils.validateJsonPathSingleStep; - -/** - * Utility class for converting JSON path to Aerospike {@link Exp}. - * - *

Supported JSON paths: containing only map and/or array elements, - * without wildcards, recursive descent, filter expressions, functions and scripts. - * - *

Examples of supported JSON paths:

- *
    - *
  • $.store.book,
  • - *
  • $[0],
  • - *
  • $.store.book[0],
  • - *
  • $.store.book[0][1].title.
  • - *
- * - *

Examples of unsupported JSON paths:

- *
    - *
  • $.store.book[*].author,
  • - *
  • $.store..price,
  • - *
  • $.store.book[?(@.price < 10)]
  • - *
  • $..book[(@.length-1)]
  • - *
- */ -@UtilityClass -public class ExpConverter { - - /** - * Create equal (==) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp eq(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.eq( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create not equal (!=) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp ne(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.ne( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create greater than (>) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp gt(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.gt( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create greater than or equals (>=) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp ge(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.ge( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create less than (<) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp lt(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.lt( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create less than or equals (<=) expression. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp le(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.le( - buildExp(binName, value, jsonPathObject), - getExpValue(value) - ); - } - - /** - * Create expression that performs a regex match on a value specified by a JSON path. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param regex regular expression string. - * @param flags regular expression bit flags. See {@link com.aerospike.client.query.RegexFlag}. - * @return generated filter expression. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Exp regex(String binName, String jsonPath, String regex, int flags) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - return Exp.regexCompare( - regex, - flags, - buildExp(binName, regex, jsonPathObject) - ); - } - - private static Exp buildExp(String binName, Object value, JsonPathObject jsonPathObject) { - List partList = new ArrayList<>(jsonPathObject.getTokensNotRequiringSecondStepQuery()); - ContextAwareToken lastPart = partList.remove(partList.size() - 1); - ContextAwareToken rootPart = partList.isEmpty() ? lastPart : partList.get(0); - CTX[] ctx = partList.stream() - .map(ContextAwareToken::toAerospikeContext) - .toArray(CTX[]::new); - - if (lastPart instanceof ListToken) { - return ListExp.getByIndex( - ListReturnType.VALUE, - getValueType(value), - Exp.val(((ListToken) lastPart).getListPosition()), - binExp(binName, rootPart), - ctx - ); - } else if (lastPart instanceof MapToken) { - return MapExp.getByKey( - MapReturnType.VALUE, - getValueType(value), - Exp.val(((MapToken) lastPart).getKey()), - binExp(binName, rootPart), - ctx - ); - } else { - throw new IllegalArgumentException(String.format("Unexpected path token type '%s'", lastPart.getType())); - } - } - - private static Exp binExp(String binName, ContextAwareToken root) { - if (root instanceof ListToken) { - return Exp.bin(binName, Exp.Type.LIST); - } - return Exp.bin(binName, Exp.Type.MAP); - } - - private static Exp.Type getValueType(Object value) { - if (value instanceof Integer || value instanceof Long || value instanceof Short) { - return Exp.Type.INT; - } else if (value instanceof Double || value instanceof Float) { - return Exp.Type.FLOAT; - } else if (value instanceof String) { - return Exp.Type.STRING; - } else if (value instanceof Boolean) { - return Exp.Type.BOOL; - } else if (value instanceof List) { - return Exp.Type.LIST; - } else if (value instanceof Map) { - return Exp.Type.MAP; - } else { - throw new IllegalArgumentException("Unsupported value type"); - } - } - - private static Exp getExpValue(Object value) { - if (value instanceof Integer) { - return Exp.val((int) value); - } else if (value instanceof Long) { - return Exp.val((long) value); - } else if (value instanceof Short) { - return Exp.val((short) value); - } else if (value instanceof Double) { - return Exp.val((double) value); - } else if (value instanceof Float) { - return Exp.val((float) value); - } else if (value instanceof String) { - return Exp.val((String) value); - } else if (value instanceof Boolean) { - return Exp.val((boolean) value); - } else if (value instanceof List) { - return Exp.val((List) value); - } else if (value instanceof Map) { - return Exp.val((Map) value); - } else { - throw new IllegalArgumentException("Unsupported value type"); - } - } - - private static String errMsg(String jsonPath) { - return String.format("Two-step JSON path '%s' cannot be converted to a filter expression", jsonPath); - } -} diff --git a/src/main/java/com/aerospike/documentapi/util/FilterConverter.java b/src/main/java/com/aerospike/documentapi/util/FilterConverter.java deleted file mode 100644 index 2a39c03..0000000 --- a/src/main/java/com/aerospike/documentapi/util/FilterConverter.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.aerospike.documentapi.util; - -import com.aerospike.client.cdt.CTX; -import com.aerospike.client.query.Filter; -import com.aerospike.client.query.IndexCollectionType; -import com.aerospike.documentapi.DocumentApiException; -import com.aerospike.documentapi.jsonpath.JsonPathObject; -import com.aerospike.documentapi.jsonpath.JsonPathParser; -import com.aerospike.documentapi.token.ContextAwareToken; -import lombok.experimental.UtilityClass; - -import java.util.ArrayList; -import java.util.List; - -import static com.aerospike.documentapi.util.Utils.validateJsonPathSingleStep; - -/** - * Utility class for converting JSON path to Aerospike {@link Filter}. - * - *

Supported JSON paths: containing only map and/or array elements, - * without wildcards, recursive descent, filter expressions, functions and scripts. - * - *

Examples of supported JSON paths:

- *
    - *
  • $.store.book,
  • - *
  • $[0],
  • - *
  • $.store.book[0],
  • - *
  • $.store.book[0][1].title.
  • - *
- * - *

Examples of unsupported JSON paths:

- *
    - *
  • $.store.book[*].author,
  • - *
  • $.store..price,
  • - *
  • $.store.book[?(@.price < 10)]
  • - *
  • $..book[(@.length-1)]
  • - *
- */ -@UtilityClass -public class FilterConverter { - - /** - * Create equal (==) Filter. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated Filter. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Filter eq(String binName, String jsonPath, Object value) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - Number val = getNumber(value); - if (val == null) { - return Filter.equal(binName, value.toString(), getCTX(jsonPathObject)); - } else { - return Filter.equal(binName, val.longValue(), getCTX(jsonPathObject)); - } - } - - /** - * Create less than (<) Filter. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated Filter. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Filter lt(String binName, String jsonPath, Object value, IndexCollectionType idxCollectionType) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - Number val = getNumber(value); - if (val == null) { - throw new IllegalArgumentException("'<' operator can be applied only to a number"); - } else { - return Filter.range(binName, idxCollectionType, Long.MIN_VALUE, val.longValue() - 1, - getCTX(jsonPathObject)); - } - } - - /** - * Create greater than (>) Filter. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated Filter. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Filter gt(String binName, String jsonPath, Object value, IndexCollectionType idxCollectionType) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - Number val = getNumber(value); - if (val == null) { - throw new IllegalArgumentException("'>' operator can be applied only to a number"); - } else { - return Filter.range(binName, idxCollectionType, val.longValue() + 1, Long.MAX_VALUE, - getCTX(jsonPathObject)); - } - } - - /** - * Create less than or equals (<=) Filter. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated Filter. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Filter le(String binName, String jsonPath, Object value, IndexCollectionType idxCollectionType) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - Number val = getNumber(value); - if (val == null) { - throw new IllegalArgumentException("'<=' operator can be applied only to a number"); - } else { - return Filter.range(binName, idxCollectionType, Long.MIN_VALUE, val.longValue(), getCTX(jsonPathObject)); - } - } - - /** - * Create greater than or equals (>) Filter. - * - * @param binName document bin name in a record. - * @param jsonPath JSON path to build a filter expression from. - * @param value value object to compare with. - * @return generated Filter. - * @throws DocumentApiException if fails to parse the jsonPath. - */ - public static Filter ge(String binName, String jsonPath, Object value, IndexCollectionType idxCollectionType) - throws DocumentApiException { - JsonPathObject jsonPathObject = validateJsonPathSingleStep(new JsonPathParser().parse(jsonPath), errMsg(jsonPath)); - Number val = getNumber(value); - if (val == null) { - throw new IllegalArgumentException("'>=' operator can be applied only to a number"); - } else { - return Filter.range(binName, idxCollectionType, val.longValue(), Long.MAX_VALUE, getCTX(jsonPathObject)); - } - } - - private static String errMsg(String jsonPath) { - return String.format("Two-step JSON path '%s' cannot be converted to a Filter", jsonPath); - } - - private static Number getNumber(Object value) { - if (value instanceof Integer) { - return (int) value; - } else if (value instanceof Long) { - return (long) value; - } else if (value instanceof Short) { - return (long) value; - } else if (value instanceof String) { - return null; - } else if (value instanceof Boolean) { - return null; - } else { - throw new IllegalArgumentException("Unsupported value type"); - } - } - - private static CTX[] getCTX(JsonPathObject jsonPathObject) { - List partList = new ArrayList<>(jsonPathObject.getTokensNotRequiringSecondStepQuery()); - return partList.stream() - .map(ContextAwareToken::toAerospikeContext) - .toArray(CTX[]::new); - } -} diff --git a/src/test/java/com/aerospike/documentapi/DocumentQueryTests.java b/src/test/java/com/aerospike/documentapi/DocumentQueryTests.java deleted file mode 100644 index 111fead..0000000 --- a/src/test/java/com/aerospike/documentapi/DocumentQueryTests.java +++ /dev/null @@ -1,454 +0,0 @@ -package com.aerospike.documentapi; - -import com.aerospike.client.AerospikeException; -import com.aerospike.client.Bin; -import com.aerospike.client.IAerospikeClient; -import com.aerospike.client.Key; -import com.aerospike.client.ResultCode; -import com.aerospike.client.Value; -import com.aerospike.client.cdt.CTX; -import com.aerospike.client.exp.Exp; -import com.aerospike.client.policy.Policy; -import com.aerospike.client.policy.QueryPolicy; -import com.aerospike.client.policy.WritePolicy; -import com.aerospike.client.query.IndexCollectionType; -import com.aerospike.client.query.IndexType; -import com.aerospike.client.query.KeyRecord; -import com.aerospike.client.query.RecordSet; -import com.aerospike.client.query.RegexFlag; -import com.aerospike.client.query.Statement; -import com.aerospike.client.task.IndexTask; -import com.aerospike.documentapi.data.DocFilterExp; -import com.aerospike.documentapi.data.DocFilterSecIndex; -import com.aerospike.documentapi.data.DocumentFilterExp; -import com.aerospike.documentapi.data.DocumentQueryStatement; -import com.aerospike.documentapi.data.DocumentFilterSecIndex; -import com.aerospike.documentapi.data.KeyResult; -import com.aerospike.documentapi.util.ExpConverter; -import net.minidev.json.JSONArray; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static com.aerospike.documentapi.data.Operator.EQ; -import static com.aerospike.documentapi.data.Operator.GE; -import static com.aerospike.documentapi.data.Operator.GT; -import static com.aerospike.documentapi.data.Operator.LT; -import static com.aerospike.documentapi.data.Operator.NE; -import static com.aerospike.documentapi.data.Operator.REGEX; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class DocumentQueryTests extends BaseTestConfig { - - private static final Key QUERY_KEY_1 = new Key(AEROSPIKE_NAMESPACE, AEROSPIKE_SET, "key1"); - private static final Key QUERY_KEY_2 = new Key(AEROSPIKE_NAMESPACE, AEROSPIKE_SET, "key2"); - private static final String MAP_BIN_NAME = "mapBin"; - private static final String LIST_BIN_NAME = "listBin"; - - private final Bin mapBin1 = new Bin(MAP_BIN_NAME, mapBin(0)); - private final Bin listBin1 = new Bin(LIST_BIN_NAME, listBin(0)); - private final Bin mapBin2 = new Bin(MAP_BIN_NAME, mapBin(100)); - private final Bin listBin2 = new Bin(LIST_BIN_NAME, listBin(100)); - - @BeforeAll - void setUp() { - client.put(writePolicy(), QUERY_KEY_1, mapBin1, listBin1); - client.put(writePolicy(), QUERY_KEY_2, mapBin2, listBin2); - createIndex(client, AEROSPIKE_NAMESPACE, AEROSPIKE_SET, "mapkey_k1_k11_idx", MAP_BIN_NAME, - IndexType.NUMERIC, IndexCollectionType.DEFAULT, - CTX.mapKey(Value.get("mapKey")), - CTX.mapKey(Value.get("k1")), - CTX.mapKey(Value.get("k11"))); - } - - @AfterAll - void tearDown() { - client.delete(null, QUERY_KEY_1); - client.delete(null, QUERY_KEY_2); - client.dropIndex(null, AEROSPIKE_NAMESPACE, AEROSPIKE_SET, "mapkey_index"); - } - - @Test - void queryList() throws DocumentApiException { - String jsonPath = "$.listKey[0].k11"; - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, LT, 100); - Stream test = documentClient.query(queryStatement, filterExp); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - assertEquals("key1", keyResults.get(0).getKey().userKey.getObject()); - } - - @Test - void queryMap() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, GE, 100); - Stream test = documentClient.query(queryStatement, filterExp); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - assertEquals("key2", keyResults.get(0).getKey().userKey.toString()); - } - - @Test - void queryMapSecondaryIndex() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .build(); - - DocumentFilterSecIndex sIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, GT, 11); - Stream test = documentClient.query(queryStatement, sIndexFilter); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - } - - @Test - void queryMapSecondaryIndexNoMatch() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - // there are only 11 and 111 values of k11 - DocumentFilterSecIndex secIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, EQ, 100); - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .build(); - - Stream test = documentClient.query(queryStatement, secIndexFilter); - List keyResults = test.collect(Collectors.toList()); - assertEquals(0, keyResults.size()); - } - - @Test - void queryMapSecondaryIndexUnsupportedOperator() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - // NE is not supported - try { - DocumentFilterSecIndex secIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, NE, 100); - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .build(); - - Stream test = documentClient.query(queryStatement, secIndexFilter); - fail("IllegalArgumentException should have been thrown"); - } catch (UnsupportedOperationException ignored) { - } - } - - @Test - void queryMapSecondaryIndexUnsupportedValueTypeDouble() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - // float and double numbers are not supported - try { - DocumentFilterSecIndex secIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, EQ, 110.335); - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - Stream test = documentClient.query(queryStatement, secIndexFilter); - fail("IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - void queryMapSecondaryIndexUnsupportedValueTypeCollection() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - // Collections are not supported - try { - DocumentFilterSecIndex secIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, EQ, - new ArrayList()); - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - Stream test = documentClient.query(queryStatement, secIndexFilter); - fail("IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - void queryMapSecondaryIndexJsonPathFilterExp() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - DocumentFilterSecIndex secIndexFilter = new DocFilterSecIndex(MAP_BIN_NAME, jsonPath, GE, 100); - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, GE, 100); - Stream test = documentClient.query(queryStatement, filterExp, secIndexFilter); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - assertEquals("key2", keyResults.get(0).getKey().userKey.toString()); - } - - @Test - void queryMapMultipleBins() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .binNames(new String[]{LIST_BIN_NAME, MAP_BIN_NAME}) - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, GE, 100); - Stream test = documentClient.query(queryStatement, filterExp); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - } - - @Test - void queryMapEmptyResult() throws DocumentApiException { - String jsonPath = "$.mapKey.k1.k11"; // value exists in MapBin - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .binNames(new String[]{LIST_BIN_NAME}) // requiring only ListBin - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, GE, 100); - Stream test = documentClient.query(queryStatement, filterExp); - List keyResults = test.collect(Collectors.toList()); - assertEquals(0, keyResults.size()); - } - - @Test - void queryMapMultiplePathsNoFilterExp() throws DocumentApiException { - String jsonPathMapKey = "$.mapKey.k1.k11"; - String jsonPathListKey = "$.listKey[0].k11"; - String jsonPathListBin = "$[0]]"; - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPathMapKey, jsonPathListKey, jsonPathListBin}) - .build(); - - Stream test = documentClient.query(queryStatement); - List keyResults = test.collect(Collectors.toList()); - assertEquals(2, keyResults.size()); - //noinspection unchecked - assertEquals(3, ((Map) keyResults.get(0).getResult()).keySet().size()); - assertEquals(keyResults.get(0).getResult(), keyResults.get(0).getResult()); - } - - @Test - void queryMapMultiplePathsAndFilterExps() throws DocumentApiException { - String jsonPathMapKey = "$.mapKey.k1.k11"; - String jsonPathListKey = "$.listKey[0].k11"; - String jsonPathListBin = "$[0]]"; - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPathMapKey, jsonPathListKey, jsonPathListBin}) - .build(); - - DocumentFilterExp filterExpMapKeyGte100 = new DocFilterExp(MAP_BIN_NAME, jsonPathMapKey, GE, 100); - DocumentFilterExp filterExpListKeyGte100 = new DocFilterExp(MAP_BIN_NAME, jsonPathListKey, GT, 100); - DocumentFilterExp filterExpListBin = new DocFilterExp(LIST_BIN_NAME, jsonPathListBin, GE, 100); - Stream test = documentClient.query(queryStatement, - filterExpMapKeyGte100, filterExpListKeyGte100, filterExpListBin); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - assertEquals("key2", keyResults.get(0).getKey().userKey.toString()); - } - - @Test - void queryFilter() throws DocumentApiException { - String jsonPath = "$.listKey[?(@.k11 < 20)]"; - - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - Stream test = documentClient.query(queryStatement); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - - @SuppressWarnings("unchecked") - List nonEmptyKeyResults = keyResults.stream() - .filter(keyRes -> ((Map) keyRes.getResult()).values() - .stream() - .anyMatch(arr -> !arr.isEmpty())) - .collect(Collectors.toList()); - assertEquals(1, nonEmptyKeyResults.size()); - - @SuppressWarnings("unchecked") - List jsonArrays = new ArrayList<>(((Map) - nonEmptyKeyResults.get(0).getResult()).values()); - assertEquals(1, jsonArrays.size()); - Map result = ((Map) jsonArrays.get(0).get(0)); - assertEquals(2, result.keySet().size()); - assertTrue(result.containsKey("k12")); - assertTrue(result.containsKey("k11")); - assertEquals("key1", nonEmptyKeyResults.get(0).getKey().userKey.toString()); - } - - @Test - void queryListRegex() throws DocumentApiException { - String jsonPath = "$.listKey[2]"; - DocumentQueryStatement queryStatement = DocumentQueryStatement.builder() - .namespace(AEROSPIKE_NAMESPACE) - .setName(AEROSPIKE_SET) - .jsonPaths(new String[]{jsonPath}) - .build(); - - DocumentFilterExp filterExp = new DocFilterExp(MAP_BIN_NAME, jsonPath, REGEX, "10.*"); - filterExp.setRegexFlags(RegexFlag.ICASE); - Stream test = documentClient.query(queryStatement, filterExp); - List keyResults = test.collect(Collectors.toList()); - assertEquals(1, keyResults.size()); - assertEquals("key2", keyResults.get(0).getKey().userKey.getObject()); - } - - @Test - void queryRootList() throws DocumentApiException { - String jsonPath = "$[1]"; - Exp exp = ExpConverter.ne(LIST_BIN_NAME, jsonPath, 102); - QueryPolicy queryPolicy = new QueryPolicy(writePolicy()); - queryPolicy.filterExp = Exp.build(exp); - - List keyRecords = recordSetToList(client.query(queryPolicy, statement())); - assertEquals(1, keyRecords.size()); - assertEquals("key1", keyRecords.get(0).key.userKey.getObject()); - } - - @Test - void testInvalidJsonPath() { - String jsonPath = "abc"; - assertThrows( - DocumentApiException.class, - () -> ExpConverter.eq(MAP_BIN_NAME, jsonPath, 100) - ); - } - - @Test - void testTwoStepJsonPath() { - String jsonPath = "$.listKey[*]"; - assertThrows( - IllegalArgumentException.class, - () -> ExpConverter.gt(MAP_BIN_NAME, jsonPath, 100) - ); - } - - private Statement statement() { - Statement statement = new Statement(); - statement.setNamespace(AEROSPIKE_NAMESPACE); - statement.setSetName(AEROSPIKE_SET); - return statement; - } - - private WritePolicy writePolicy() { - WritePolicy writePolicy = new WritePolicy(); - writePolicy.sendKey = true; - return writePolicy; - } - - private List recordSetToList(RecordSet recordSet) { - return StreamSupport.stream(Spliterators.spliteratorUnknownSize( - recordSet.iterator(), - Spliterator.ORDERED - ), false) - .collect(Collectors.toList()); - } - - private Map mapBin(int offset) { - Map mapBin = new HashMap<>(); - Map innerMap = new HashMap<>(); - innerMap.put("k1", Stream.of(new Object[][]{ - {"k11", offset + 11}, - {"k12", offset + 12}, - }).collect(Collectors.toMap(data -> data[0], data -> data[1]))); - mapBin.put("mapKey", innerMap); - List innerList = new ArrayList<>(); - innerList.add(Stream.of(new Object[][]{ - {"k11", offset + 11}, - {"k12", offset + 12}, - }).collect(Collectors.toMap(data -> data[0], data -> data[1]))); - innerList.add(Stream.of(new Object[][]{ - {"k13", offset + 13}, - {"k14", offset + 14}, - }).collect(Collectors.toMap(data -> data[0], data -> data[1]))); - innerList.add(String.format("%d", offset)); - mapBin.put("listKey", innerList); - return mapBin; - } - - private List listBin(int offset) { - return Arrays.asList( - 1 + offset, - 2 + offset, - 3 + offset - ); - } - - @SuppressWarnings("SameParameterValue") - private void createIndex( - IAerospikeClient client, - String namespace, - String set, - String indexName, - String binName, - IndexType idxType, - IndexCollectionType collectionType, - CTX... ctx - ) throws RuntimeException { - Policy policy = new Policy(); - policy.socketTimeout = 0; // Do not time out on index create. - - try { - IndexTask task = client.createIndex(policy, namespace, set, indexName, binName, - idxType, collectionType, ctx); - task.waitTillComplete(); - } catch (AerospikeException ae) { - if (ae.getResultCode() != ResultCode.INDEX_ALREADY_EXISTS) { - throw ae; - } - } - } -}