diff --git a/src/main/java/com/aerospike/dsl/DSLParser.java b/src/main/java/com/aerospike/dsl/DSLParser.java index a6d70ae..6f48081 100644 --- a/src/main/java/com/aerospike/dsl/DSLParser.java +++ b/src/main/java/com/aerospike/dsl/DSLParser.java @@ -1,10 +1,6 @@ package com.aerospike.dsl; -import com.aerospike.client.exp.Expression; import com.aerospike.client.query.Filter; -import com.aerospike.dsl.exception.AerospikeDSLException; - -import java.util.List; /** * Contains API to convert dot separated String path into an Aerospike filter - @@ -25,6 +21,7 @@ public interface DSLParser { *

* Examples: * + * * * * @@ -47,9 +44,12 @@ public interface DSLParser { * * * - * + * + * * - * + * + * + * * * * @@ -59,12 +59,11 @@ public interface DSLParser { * * * + * *
Path element
$.binName Bin “binName”
{=bb} Map value “bb”
{='1'} Map value (String) “1” {='1'} Map value (String) “1”
{#1} Map rank 1
{#1} Map rank 1
[1] List index 1
[#1] List rank 1
*
* - * - * - * + * * * * @@ -82,30 +81,84 @@ public interface DSLParser { * mapKey("10")] * *
$.binName [binName]
A nested element
$.mapBinName.k [mapBinName -> mapKey("a")]
- * @param input String consisting of dot separated elements, typically bin name and optional context - * @return Expression object - * @throws AerospikeDSLException in case of unsupported DSL String or invalid syntax + * + * @param dslString String consisting of dot separated elements, typically bin name and optional context + * @return {@link ParsedExpression} object + * @throws DslParseException in case of invalid syntax */ - Expression parseExpression(String input); + ParsedExpression parseExpression(String dslString); /** - * Parse String DSL path into Aerospike secondary index Filter. + * Parse String DSL path into Aerospike filter Expression. *

* Examples: * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * - * + * + * * * - * + * + * * * - * + * + * + * + * + * + * + * + * + *
Path element
$.binName Bin “binName”
a Map key “a”
'1' Map key (String) “1”
1 Map key 1
{1} Map index 1
{=1} Map value (int) 1
{=bb} Map value “bb”
$.intBin1 == 10 Filter.equal("intBin1", 10) {='1'} Map value (String) “1”
$.intBin1 > 10 Filter.range("intBin1", 11, Long.MAX_VALUE) {#1} Map rank 1
$.stringBin1 == 'text' Filter.equal("stringBin1", "text") [1] List index 1
[=1] List value 1
[#1] List rank 1
+ *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * *
A nested element
$.mapBinName.k [mapBinName -> mapKey("a")]
$.mapBinName.a.aa.aaa [mapBinName -> mapKey("a") -> mapKey("aa") -> mapKey("aaa")]
$.mapBinName.a.55 [mapBinName -> mapKey("a") -> mapKey(55)]
$.listBinName.[1].aa [listBinName -> listIndex(1) -> mapKey("aa")]
$.mapBinName.ab.cd.[-1].'10' [mapBinName -> mapKey("ab") -> mapKey("cd") -> listIndex(-1) -> + * mapKey("10")]
- * @param input String consisting of dot separated elements, typically bin name and value - * @return List of Filter objects - * @throws AerospikeDSLException in case of unsupported DSL String or invalid syntax + * + * @param dslString String consisting of dot separated elements, typically bin name and optional context + * @param indexContext Class containing namespace and collection of {@link Index} objects that represent + * existing secondary indexes. Required for creating {@link Filter}. Can be null + * @return {@link ParsedExpression} object + * @throws DslParseException in case of invalid syntax */ - List parseFilters(String input); + ParsedExpression parseExpression(String dslString, IndexContext indexContext); } diff --git a/src/main/java/com/aerospike/dsl/DSLParserImpl.java b/src/main/java/com/aerospike/dsl/DSLParserImpl.java index 8a9287b..7253ca0 100644 --- a/src/main/java/com/aerospike/dsl/DSLParserImpl.java +++ b/src/main/java/com/aerospike/dsl/DSLParserImpl.java @@ -1,50 +1,60 @@ package com.aerospike.dsl; -import com.aerospike.client.exp.Exp; -import com.aerospike.client.exp.Expression; -import com.aerospike.client.query.Filter; import com.aerospike.dsl.annotation.Beta; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.AbstractPart; +import com.aerospike.dsl.parts.AbstractPart; import com.aerospike.dsl.visitor.ExpressionConditionVisitor; -import com.aerospike.dsl.visitor.FilterConditionVisitor; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; public class DSLParserImpl implements DSLParser { @Beta - public Expression parseExpression(String input) { - ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input)); - ConditionParser parser = new ConditionParser(new CommonTokenStream(lexer)); - ParseTree tree = parser.parse(); - - ExpressionConditionVisitor visitor = new ExpressionConditionVisitor(); - AbstractPart abstractPart = visitor.visit(tree); - - // When we can't identify a specific case of syntax error, we throw a generic DSL syntax error instead of NPE - if (abstractPart == null) { - throw new AerospikeDSLException("Could not parse given input, wrong syntax"); - } - Exp expResult = abstractPart.getExp(); - return Exp.build(expResult); + public ParsedExpression parseExpression(String dslString) { + ParseTree parseTree = getParseTree(dslString); + return getParsedExpression(parseTree, null); } @Beta - public List parseFilters(String input) { + public ParsedExpression parseExpression(String input, IndexContext indexContext) { + ParseTree parseTree = getParseTree(input); + return getParsedExpression(parseTree, indexContext); + } + + private ParseTree getParseTree(String input) { ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input)); ConditionParser parser = new ConditionParser(new CommonTokenStream(lexer)); - ParseTree tree = parser.parse(); - - FilterConditionVisitor visitor = new FilterConditionVisitor(); - AbstractPart abstractPart = visitor.visit(tree); + return parser.parse(); + } - if (abstractPart == null) { - throw new AerospikeDSLException("Could not parse given input, wrong syntax"); + private ParsedExpression getParsedExpression(ParseTree parseTree, IndexContext indexContext) { + final String namespace = Optional.ofNullable(indexContext) + .map(IndexContext::getNamespace) + .orElse(null); + final Collection indexes = Optional.ofNullable(indexContext) + .map(IndexContext::getIndexes) + .orElse(Collections.emptyList()); + + Map> indexesMap = indexes.stream() + // Filtering the indexes with the given namespace + .filter(idx -> idx.getNamespace() != null && idx.getNamespace().equals(namespace)) + // Group the indexes by bin name + .collect(Collectors.groupingBy(Index::getBin)); + + AbstractPart resultingPart = new ExpressionConditionVisitor().visit(parseTree); + + // When we can't identify a specific case of syntax error, we throw a generic DSL syntax error + if (resultingPart == null) { + throw new DslParseException("Could not parse given DSL expression input"); } - return abstractPart.getSIndexFilter().getFilters(); + // Return the parsed tree along with indexes Map + return new ParsedExpression(resultingPart, indexesMap); } } diff --git a/src/main/java/com/aerospike/dsl/DslParseException.java b/src/main/java/com/aerospike/dsl/DslParseException.java new file mode 100644 index 0000000..0668285 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/DslParseException.java @@ -0,0 +1,13 @@ +package com.aerospike.dsl; + +/** + * Represents a general processing exception that can occur during DSL expression parsing. + * It is typically not expected to be caught by the caller, but rather indicates a potentially + * unrecoverable issue like invalid input, failing validation or unsupported functionality. + */ +public class DslParseException extends RuntimeException { + + public DslParseException(String description) { + super(description); + } +} diff --git a/src/main/java/com/aerospike/dsl/Index.java b/src/main/java/com/aerospike/dsl/Index.java new file mode 100644 index 0000000..7c75761 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/Index.java @@ -0,0 +1,34 @@ +package com.aerospike.dsl; + +import com.aerospike.client.query.IndexType; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +/** + * This class represents a secondary index created in the cluster. + */ +@Builder +@EqualsAndHashCode +@Getter +public class Index { + + /** + * Namespace of the indexed bin + */ + private final String namespace; + /** + * Name of the indexed bin + */ + private final String bin; + /** + * {@link IndexType} of the index + */ + private final IndexType indexType; + /** + * Cardinality of the index calculated using "sindex-stat" command and looking at the ratio of entries + * to unique bin values for the given secondary index on the node (entries_per_bval) + * + */ + private int binValuesRatio; +} diff --git a/src/main/java/com/aerospike/dsl/IndexContext.java b/src/main/java/com/aerospike/dsl/IndexContext.java new file mode 100644 index 0000000..8229e26 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/IndexContext.java @@ -0,0 +1,26 @@ +package com.aerospike.dsl; + +import com.aerospike.client.query.Filter; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Collection; + +/** + * This class stores namespace and indexes required to build secondary index {@link Filter}. + */ +@AllArgsConstructor(staticName = "of") +@Getter +public class IndexContext { + + /** + * Namespace to be used for creating {@link Filter}. Is matched with namespace of indexes + */ + private String namespace; + /** + * Collection of {@link Index} objects to be used for creating {@link 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 indexes; +} diff --git a/src/main/java/com/aerospike/dsl/ParseResult.java b/src/main/java/com/aerospike/dsl/ParseResult.java new file mode 100644 index 0000000..398456d --- /dev/null +++ b/src/main/java/com/aerospike/dsl/ParseResult.java @@ -0,0 +1,24 @@ +package com.aerospike.dsl; + +import com.aerospike.client.exp.Exp; +import com.aerospike.client.query.Filter; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * This class stores result of parsing DSL expression using {@link DSLParserImpl#parseExpression} + * in form of Java client's secondary index {@link Filter} and filter {@link Exp}. + */ +@AllArgsConstructor +@Getter +public class ParseResult { + + /** + * Secondary index {@link Filter}. Can be null in case of invalid or unsupported DSL string + */ + Filter filter; + /** + * Filter {@link Exp}. Can be null in case of invalid or unsupported DSL string + */ + Exp exp; +} diff --git a/src/main/java/com/aerospike/dsl/ParsedExpression.java b/src/main/java/com/aerospike/dsl/ParsedExpression.java new file mode 100644 index 0000000..eb4f845 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/ParsedExpression.java @@ -0,0 +1,59 @@ +package com.aerospike.dsl; + +import com.aerospike.client.exp.Exp; +import com.aerospike.client.query.Filter; +import com.aerospike.dsl.annotation.Beta; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.ExpressionContainer; +import lombok.Getter; + +import java.util.List; +import java.util.Map; + +import static com.aerospike.dsl.parts.AbstractPart.PartType.EXPRESSION_CONTAINER; +import static com.aerospike.dsl.visitor.VisitorUtils.buildExpr; + + +/** + * A class to build and store the results of DSL expression parsing: {@link ParseResult} that holds + * a potential secondary index {@link Filter} and a potential filter {@link Exp}, and parsed {@code expressionTree}. + */ +@Beta +@Getter +public class ParsedExpression { + + private final AbstractPart expressionTree; + private final Map> indexesMap; + private ParseResult result; + + public ParsedExpression(AbstractPart expressionTree, Map> indexesMap) { + this.expressionTree = expressionTree; + this.indexesMap = indexesMap; + } + + /** + * @return Pair of secondary index {@link Filter} and filter {@link Exp}. Each can be null in case of invalid or + * unsupported DSL string + * @throws DslParseException If there was an error + */ + public ParseResult getResult() { + if (result == null) { + result = getParseResult(); + } + return result; + } + + private ParseResult getParseResult() { + if (expressionTree != null) { + if (expressionTree.getPartType() == EXPRESSION_CONTAINER) { + AbstractPart resultPart = buildExpr((ExpressionContainer) expressionTree, indexesMap); + return new ParseResult(resultPart.getFilter(), resultPart.getExp()); + } else { + Filter filter = expressionTree.getFilter(); + Exp exp = expressionTree.getExp(); + return new ParseResult(filter, exp); + } + } + return new ParseResult(null, null); + } +} diff --git a/src/main/java/com/aerospike/dsl/exception/AerospikeDSLException.java b/src/main/java/com/aerospike/dsl/exception/AerospikeDSLException.java deleted file mode 100644 index 962b1b2..0000000 --- a/src/main/java/com/aerospike/dsl/exception/AerospikeDSLException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.aerospike.dsl.exception; - -public class AerospikeDSLException extends RuntimeException { - - public AerospikeDSLException(String description) { - super(description); - } -} diff --git a/src/main/java/com/aerospike/dsl/model/Expr.java b/src/main/java/com/aerospike/dsl/model/Expr.java deleted file mode 100644 index ce68eeb..0000000 --- a/src/main/java/com/aerospike/dsl/model/Expr.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -@Getter -public class Expr extends AbstractPart { - - protected AbstractPart left; - protected AbstractPart right; - private ExprPartsOperation operationType; - - public Expr(Exp exp) { - super(PartType.EXPR, exp); - } - - public Expr(SIndexFilter filter) { - super(PartType.EXPR, filter); - } - - public Expr(AbstractPart left, AbstractPart right, ExprPartsOperation operationType) { - super(PartType.EXPR); - this.left = left; - this.right = right; - this.operationType = operationType; - } - - public enum ExprPartsOperation { - ADD, - SUB, - DIV, - MUL - } -} diff --git a/src/main/java/com/aerospike/dsl/model/IntOperand.java b/src/main/java/com/aerospike/dsl/model/IntOperand.java deleted file mode 100644 index c414fc2..0000000 --- a/src/main/java/com/aerospike/dsl/model/IntOperand.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -@Getter -public class IntOperand extends AbstractPart implements ParsedOperand { - - private final Long value; - - public IntOperand(Long value) { - super(AbstractPart.PartType.INT_OPERAND, Exp.val(value)); - this.value = value; - } -} diff --git a/src/main/java/com/aerospike/dsl/model/ListOperand.java b/src/main/java/com/aerospike/dsl/model/ListOperand.java deleted file mode 100644 index ab7b9a5..0000000 --- a/src/main/java/com/aerospike/dsl/model/ListOperand.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -import java.util.List; - -@Getter -public class ListOperand extends AbstractPart implements ParsedOperand { - - private final List value; - - public ListOperand(List list) { - super(PartType.LIST_OPERAND, Exp.val(list)); - this.value = list; - } -} diff --git a/src/main/java/com/aerospike/dsl/model/MapOperand.java b/src/main/java/com/aerospike/dsl/model/MapOperand.java deleted file mode 100644 index 6b49aca..0000000 --- a/src/main/java/com/aerospike/dsl/model/MapOperand.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -import java.util.TreeMap; - -@Getter -public class MapOperand extends AbstractPart implements ParsedOperand { - - private final TreeMap value; - - public MapOperand(TreeMap map) { - super(PartType.MAP_OPERAND, Exp.val(map)); - this.value = map; - } -} diff --git a/src/main/java/com/aerospike/dsl/model/ParsedOperand.java b/src/main/java/com/aerospike/dsl/model/ParsedOperand.java deleted file mode 100644 index 1b17960..0000000 --- a/src/main/java/com/aerospike/dsl/model/ParsedOperand.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.aerospike.dsl.model; - -public interface ParsedOperand { - Object getValue(); -} diff --git a/src/main/java/com/aerospike/dsl/model/SIndexFilter.java b/src/main/java/com/aerospike/dsl/model/SIndexFilter.java deleted file mode 100644 index 54a9c15..0000000 --- a/src/main/java/com/aerospike/dsl/model/SIndexFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.query.Filter; -import lombok.Getter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class SIndexFilter { - - @Getter - protected final List filters = new ArrayList<>(); - - public SIndexFilter(Filter filter) { - filters.add(filter); - } - - public SIndexFilter(Collection filters) { - this.filters.addAll(filters); - } -} diff --git a/src/main/java/com/aerospike/dsl/model/StringOperand.java b/src/main/java/com/aerospike/dsl/model/StringOperand.java deleted file mode 100644 index 805d1ea..0000000 --- a/src/main/java/com/aerospike/dsl/model/StringOperand.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -@Getter -public class StringOperand extends AbstractPart implements ParsedOperand { - - private final String value; - - public StringOperand(String string) { - super(PartType.STRING_OPERAND, Exp.val(string)); - this.value = string; - } -} diff --git a/src/main/java/com/aerospike/dsl/model/VariableOperand.java b/src/main/java/com/aerospike/dsl/model/VariableOperand.java deleted file mode 100644 index 2af1fbf..0000000 --- a/src/main/java/com/aerospike/dsl/model/VariableOperand.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.aerospike.dsl.model; - -import com.aerospike.client.exp.Exp; -import lombok.Getter; - -@Getter -public class VariableOperand extends AbstractPart { - - private final String name; - - public VariableOperand(String name) { - super(PartType.VARIABLE_OPERAND, Exp.var(name)); - this.name = name; - } -} diff --git a/src/main/java/com/aerospike/dsl/model/cdt/CdtPart.java b/src/main/java/com/aerospike/dsl/model/cdt/CdtPart.java deleted file mode 100644 index fcf8ea0..0000000 --- a/src/main/java/com/aerospike/dsl/model/cdt/CdtPart.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.aerospike.dsl.model.cdt; - -import com.aerospike.client.cdt.CTX; -import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.BasePath; -import com.aerospike.dsl.model.PathFunction; - -public abstract class CdtPart extends AbstractPart { - - protected CdtPart(PartType partType) { - super(partType); - } - - public static Exp getExpVal(Exp.Type valueType, Object cdtValue) { - return switch (valueType) { - case BOOL -> Exp.val((Boolean) cdtValue); - case INT -> Exp.val((Integer) cdtValue); - case STRING -> Exp.val((String) cdtValue); - case FLOAT -> Exp.val((Float) cdtValue); - default -> throw new IllegalStateException( - "Get by value from a CDT: unexpected value '%s'".formatted(valueType)); - }; - } - - public abstract Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context); - - public CTX getContext() { - // should print the subclass of the cdt type - throw new AerospikeDSLException("Context is not supported for %s".formatted(this.getClass().getName())); - } - - public abstract int getReturnType(PathFunction.ReturnParam returnParam); -} diff --git a/src/main/java/com/aerospike/dsl/model/AbstractPart.java b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java similarity index 68% rename from src/main/java/com/aerospike/dsl/model/AbstractPart.java rename to src/main/java/com/aerospike/dsl/parts/AbstractPart.java index 2499c79..e0ae3c8 100644 --- a/src/main/java/com/aerospike/dsl/model/AbstractPart.java +++ b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java @@ -1,6 +1,7 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts; import com.aerospike.client.exp.Exp; +import com.aerospike.client.query.Filter; import lombok.Getter; import lombok.Setter; @@ -11,21 +12,16 @@ public abstract class AbstractPart { protected Exp.Type expType; protected PartType partType; protected Exp exp; - protected SIndexFilter sIndexFilter; + protected Filter filter; protected AbstractPart(PartType partType) { this.partType = partType; this.exp = null; } - protected AbstractPart(PartType partType, Exp exp) { + protected AbstractPart(PartType partType, Filter filter) { this.partType = partType; - this.exp = exp; - } - - protected AbstractPart(PartType partType, SIndexFilter filters) { - this.partType = partType; - this.sIndexFilter = filters; + this.filter = filter; } public enum PartType { @@ -35,6 +31,10 @@ public enum PartType { STRING_OPERAND, LIST_OPERAND, MAP_OPERAND, + WITH_OPERAND, + WITH_STRUCTURE, + WHEN_STRUCTURE, + EXCLUSIVE_STRUCTURE, BASE_PATH, BIN_PART, LIST_PART, @@ -42,7 +42,7 @@ public enum PartType { PATH_OPERAND, PATH_FUNCTION, METADATA_OPERAND, - EXPR, + EXPRESSION_CONTAINER, VARIABLE_OPERAND } } diff --git a/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java new file mode 100644 index 0000000..d5ba17e --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java @@ -0,0 +1,67 @@ +package com.aerospike.dsl.parts; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +public class ExpressionContainer extends AbstractPart { + + protected final AbstractPart left; + protected final AbstractPart right; + private final boolean isUnary; + private final ExprPartsOperation operationType; + @Setter() + @Accessors(fluent = true) + private boolean hasSecondaryIndexFilter; + + public ExpressionContainer() { + super(PartType.EXPRESSION_CONTAINER); + this.isUnary = false; + this.left = null; + this.right = null; + this.operationType = null; + } + + public ExpressionContainer(AbstractPart left, AbstractPart right, ExprPartsOperation operationType) { + super(PartType.EXPRESSION_CONTAINER); + this.left = left; + this.right = right; + this.operationType = operationType; + this.isUnary = false; + } + + public ExpressionContainer(AbstractPart singleOperand, ExprPartsOperation operationType) { + super(PartType.EXPRESSION_CONTAINER); + this.left = singleOperand; + this.right = null; + this.operationType = operationType; + this.isUnary = true; + } + + public enum ExprPartsOperation { + ADD, + SUB, + DIV, + MUL, + MOD, + INT_XOR, + INT_NOT, // unary + INT_AND, + INT_OR, + L_SHIFT, + R_SHIFT, + AND, + OR, + NOT, // unary + EQ, + NOTEQ, + GT, + GTEQ, + LT, + LTEQ, + WITH_STRUCTURE, // unary + WHEN_STRUCTURE, // unary + EXCLUSIVE_STRUCTURE // unary + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/CdtPart.java b/src/main/java/com/aerospike/dsl/parts/cdt/CdtPart.java new file mode 100644 index 0000000..4e14758 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/cdt/CdtPart.java @@ -0,0 +1,24 @@ +package com.aerospike.dsl.parts.cdt; + +import com.aerospike.client.cdt.CTX; +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.path.BasePath; +import com.aerospike.dsl.parts.path.PathFunction; + +public abstract class CdtPart extends AbstractPart { + + protected CdtPart(PartType partType) { + super(partType); + } + + public abstract Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context); + + public CTX getContext() { + // should print the subclass of the cdt type + throw new DslParseException("Context is not supported for %s".formatted(this.getClass().getName())); + } + + public abstract int getReturnType(PathFunction.ReturnParam returnParam); +} diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListIndex.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndex.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListIndex.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndex.java index 9862142..dbd6f56 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListIndex.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndex.java @@ -1,10 +1,10 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; public class ListIndex extends ListPart { private final int index; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListIndexRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndexRange.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListIndexRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndexRange.java index 2fc5d6f..1b4d24f 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListIndexRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListIndexRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -39,7 +39,7 @@ public static ListIndexRange from(ConditionParser.ListIndexRangeContext ctx) { return new ListIndexRange(isInverted, start, end); } - throw new AerospikeDSLException("Could not translate ListIndexRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate ListIndexRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListPart.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListPart.java similarity index 77% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListPart.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListPart.java index d9d5761..fa9911e 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListPart.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListPart.java @@ -1,9 +1,9 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.ListReturnType; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.PathFunction; -import com.aerospike.dsl.model.cdt.CdtPart; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.PathFunction; +import com.aerospike.dsl.parts.cdt.CdtPart; import lombok.Getter; @Getter @@ -27,7 +27,7 @@ public int getReturnType(PathFunction.ReturnParam returnParam) { case REVERSE_INDEX -> ListReturnType.REVERSE_INDEX; case REVERSE_RANK -> ListReturnType.REVERSE_RANK; default -> - throw new AerospikeDSLException("Unsupported Return Param for List CDT: %s".formatted(returnParam)); + throw new DslParseException("Unsupported Return Param for List CDT: %s".formatted(returnParam)); }; } diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRank.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRank.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListRank.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListRank.java index d78e3a5..4e77d85 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRank.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRank.java @@ -1,10 +1,10 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; public class ListRank extends ListPart { private final int rank; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRange.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRange.java index 6064cef..5e3c14d 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -39,7 +39,7 @@ public static ListRankRange from(ConditionParser.ListRankRangeContext ctx) { return new ListRankRange(isInverted, start, end); } - throw new AerospikeDSLException("Could not translate ListRankRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate ListRankRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRangeRelative.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRangeRelative.java similarity index 91% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRangeRelative.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRangeRelative.java index cad2325..98ac1dc 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListRankRangeRelative.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListRankRangeRelative.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -57,7 +57,7 @@ public static ListRankRangeRelative from(ConditionParser.ListRankRangeRelativeCo return new ListRankRangeRelative(isInverted, start, end, relativeValue); } - throw new AerospikeDSLException("Could not translate ListRankRangeRelative from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate ListRankRangeRelative from ctx: %s".formatted(ctx)); } @Override @@ -72,7 +72,7 @@ public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType } else if (relative instanceof Integer rel) { relativeExp = Exp.val(rel); } else { - throw new AerospikeDSLException("Unsupported value relative rank"); + throw new DslParseException("Unsupported value relative rank"); } Exp startExp = Exp.val(start); @@ -81,6 +81,7 @@ public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } + return ListExp.getByValueRelativeRankRange(cdtReturnType, startExp, relativeExp, Exp.val(count), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListTypeDesignator.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java similarity index 86% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListTypeDesignator.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java index 7ef6e2d..b8d7cb1 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListTypeDesignator.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java @@ -1,10 +1,10 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.BasePath; -import com.aerospike.dsl.model.cdt.CdtPart; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.path.BasePath; +import com.aerospike.dsl.parts.cdt.CdtPart; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java similarity index 95% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListValue.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java index 580ec96..8cea8d2 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java @@ -1,11 +1,11 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.Value; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValueList.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueList.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListValueList.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueList.java index de762e4..a1a3cc4 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValueList.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueList.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import java.util.List; @@ -44,7 +44,7 @@ public static ListValueList from(ConditionParser.ListValueListContext ctx) { return new ListValueList(isInverted, valueListObjects); } - throw new AerospikeDSLException("Could not translate ListValueList from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate ListValueList from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValueRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueRange.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/list/ListValueRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueRange.java index 25a321c..51254f0 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/list/ListValueRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValueRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.list; +package com.aerospike.dsl.parts.cdt.list; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; public class ListValueRange extends ListPart { private final boolean inverted; @@ -39,7 +39,7 @@ public static ListValueRange from(ConditionParser.ListValueRangeContext ctx) { return new ListValueRange(isInverted, startValue, endValue); } - throw new AerospikeDSLException("Could not translate ListValueRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate ListValueRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndex.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndex.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapIndex.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndex.java index 3120ca5..25dd0a4 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndex.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndex.java @@ -1,10 +1,10 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; public class MapIndex extends MapPart { private final int index; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRange.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRange.java index 72f8bf6..4015869 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -39,7 +39,7 @@ public static MapIndexRange from(ConditionParser.MapIndexRangeContext ctx) { return new MapIndexRange(isInverted, start, end); } - throw new AerospikeDSLException("Could not translate MapIndexRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapIndexRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRangeRelative.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRangeRelative.java similarity index 92% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRangeRelative.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRangeRelative.java index e0fc6c6..b33628f 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapIndexRangeRelative.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapIndexRangeRelative.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -52,7 +52,7 @@ public static MapIndexRangeRelative from(ConditionParser.MapIndexRangeRelativeCo } return new MapIndexRangeRelative(isInverted, start, end, relativeKey); } - throw new AerospikeDSLException("Could not translate MapIndexRangeRelative from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapIndexRangeRelative from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKey.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKey.java similarity index 82% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapKey.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapKey.java index 8bd7774..2f4b0ba 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKey.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKey.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.Value; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; @@ -25,7 +25,7 @@ public static MapKey from(ConditionParser.MapKeyContext ctx) { if (ctx.NAME_IDENTIFIER() != null) { return new MapKey(ctx.NAME_IDENTIFIER().getText()); } - throw new AerospikeDSLException("Could not translate MapKey from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapKey from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyList.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyList.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java index be5c428..3b43e6b 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyList.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import java.util.List; @@ -43,7 +43,7 @@ public static MapKeyList from(ConditionParser.MapKeyListContext ctx) { return new MapKeyList(isInverted, keyListStrings); } - throw new AerospikeDSLException("Could not translate MapKeyList from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapKeyList from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyRange.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyRange.java index 1c223da..11dbc98 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapKeyRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import java.util.Optional; @@ -45,7 +45,7 @@ public static MapKeyRange from(ConditionParser.MapKeyRangeContext ctx) { return new MapKeyRange(isInverted, startKey, endKey); } - throw new AerospikeDSLException("Could not translate MapKeyRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapKeyRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapPart.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapPart.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapPart.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapPart.java index 7209268..5550eba 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapPart.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapPart.java @@ -1,8 +1,8 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.MapReturnType; -import com.aerospike.dsl.model.PathFunction; -import com.aerospike.dsl.model.cdt.CdtPart; +import com.aerospike.dsl.parts.path.PathFunction; +import com.aerospike.dsl.parts.cdt.CdtPart; import lombok.Getter; @Getter diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRank.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRank.java similarity index 90% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapRank.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapRank.java index 642155c..9c84f13 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRank.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRank.java @@ -1,10 +1,10 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; public class MapRank extends MapPart { private final int rank; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRange.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRange.java index d84adf2..ab6f81c 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -39,7 +39,7 @@ public static MapRankRange from(ConditionParser.MapRankRangeContext ctx) { return new MapRankRange(isInverted, start, end); } - throw new AerospikeDSLException("Could not translate MapRankRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapRankRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRangeRelative.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRangeRelative.java similarity index 91% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRangeRelative.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRangeRelative.java index 7afef60..e703569 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapRankRangeRelative.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapRankRangeRelative.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; import static com.aerospike.dsl.util.ParsingUtils.subtractNullable; @@ -56,7 +56,7 @@ public static MapRankRangeRelative from(ConditionParser.MapRankRangeRelativeCont return new MapRankRangeRelative(isInverted, start, end, relativeValue); } - throw new AerospikeDSLException("Could not translate MapRankRangeRelative from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapRankRangeRelative from ctx: %s".formatted(ctx)); } @Override @@ -71,7 +71,7 @@ public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType } else if (relative instanceof Integer rel) { relativeExp = Exp.val(rel); } else { - throw new AerospikeDSLException("Unsupported value relative rank"); + throw new DslParseException("Unsupported value relative rank"); } Exp startExp = Exp.val(start); diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapTypeDesignator.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapTypeDesignator.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java index 49eea10..226ba60 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapTypeDesignator.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java @@ -1,9 +1,9 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.model.BasePath; -import com.aerospike.dsl.model.cdt.CdtPart; +import com.aerospike.dsl.parts.path.BasePath; +import com.aerospike.dsl.parts.cdt.CdtPart; /** * Designates that the element to the left is a Map. diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java similarity index 95% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapValue.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java index 4427e9d..5ff7e9f 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java @@ -1,11 +1,11 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.Value; import com.aerospike.client.cdt.CTX; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.parts.path.BasePath; import static com.aerospike.dsl.util.ParsingUtils.unquote; diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValueList.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueList.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapValueList.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueList.java index e0be45c..da3cdfd 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValueList.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueList.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; import java.util.List; @@ -44,7 +44,7 @@ public static MapValueList from(ConditionParser.MapValueListContext ctx) { return new MapValueList(isInverted, valueListObjects); } - throw new AerospikeDSLException("Could not translate MapValueList from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapValueList from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValueRange.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueRange.java similarity index 89% rename from src/main/java/com/aerospike/dsl/model/cdt/map/MapValueRange.java rename to src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueRange.java index 89ecbfb..8cf5031 100644 --- a/src/main/java/com/aerospike/dsl/model/cdt/map/MapValueRange.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValueRange.java @@ -1,12 +1,12 @@ -package com.aerospike.dsl.model.cdt.map; +package com.aerospike.dsl.parts.cdt.map; import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.MapReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.MapExp; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.BasePath; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.path.BasePath; public class MapValueRange extends MapPart { private final boolean inverted; @@ -38,7 +38,7 @@ public static MapValueRange from(ConditionParser.MapValueRangeContext ctx) { return new MapValueRange(isInverted, startValue, endValue); } - throw new AerospikeDSLException("Could not translate MapValueRange from ctx: %s".formatted(ctx)); + throw new DslParseException("Could not translate MapValueRange from ctx: %s".formatted(ctx)); } @Override diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/ExclusiveStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/ExclusiveStructure.java new file mode 100644 index 0000000..9be7857 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/ExclusiveStructure.java @@ -0,0 +1,18 @@ +package com.aerospike.dsl.parts.controlstructure; + +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.ExpressionContainer; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ExclusiveStructure extends AbstractPart { + + private final List operands; + + public ExclusiveStructure(List operands) { + super(PartType.EXCLUSIVE_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/WhenStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/WhenStructure.java new file mode 100644 index 0000000..6849c9e --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/WhenStructure.java @@ -0,0 +1,17 @@ +package com.aerospike.dsl.parts.controlstructure; + +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +import java.util.List; + +@Getter +public class WhenStructure extends AbstractPart { + + private final List operands; + + public WhenStructure(List operands) { + super(PartType.WHEN_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java new file mode 100644 index 0000000..6e1c413 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java @@ -0,0 +1,18 @@ +package com.aerospike.dsl.parts.controlstructure; + +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.operand.WithOperand; +import lombok.Getter; + +import java.util.List; + +@Getter +public class WithStructure extends AbstractPart { + + private final List operands; + + public WithStructure(List operands) { + super(PartType.WITH_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/model/BooleanOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/BooleanOperand.java similarity index 53% rename from src/main/java/com/aerospike/dsl/model/BooleanOperand.java rename to src/main/java/com/aerospike/dsl/parts/operand/BooleanOperand.java index 7be9063..18ea4fc 100644 --- a/src/main/java/com/aerospike/dsl/model/BooleanOperand.java +++ b/src/main/java/com/aerospike/dsl/parts/operand/BooleanOperand.java @@ -1,15 +1,21 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.operand; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; import lombok.Getter; @Getter -public class BooleanOperand extends AbstractPart implements ParsedOperand { +public class BooleanOperand extends AbstractPart implements ParsedValueOperand { private final Boolean value; public BooleanOperand(Boolean value) { - super(PartType.BOOL_OPERAND, Exp.val(value)); + super(PartType.BOOL_OPERAND); this.value = value; } + + @Override + public Exp getExp() { + return Exp.val(value); + } } diff --git a/src/main/java/com/aerospike/dsl/model/FloatOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/FloatOperand.java similarity index 52% rename from src/main/java/com/aerospike/dsl/model/FloatOperand.java rename to src/main/java/com/aerospike/dsl/parts/operand/FloatOperand.java index 4aa380b..5c843c6 100644 --- a/src/main/java/com/aerospike/dsl/model/FloatOperand.java +++ b/src/main/java/com/aerospike/dsl/parts/operand/FloatOperand.java @@ -1,15 +1,21 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.operand; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; import lombok.Getter; @Getter -public class FloatOperand extends AbstractPart implements ParsedOperand { +public class FloatOperand extends AbstractPart implements ParsedValueOperand { private final Double value; public FloatOperand(Double value) { - super(PartType.FLOAT_OPERAND, Exp.val(value)); + super(PartType.FLOAT_OPERAND); this.value = value; } + + @Override + public Exp getExp() { + return Exp.val(value); + } } diff --git a/src/main/java/com/aerospike/dsl/parts/operand/IntOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/IntOperand.java new file mode 100644 index 0000000..e8405f6 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/IntOperand.java @@ -0,0 +1,21 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +@Getter +public class IntOperand extends AbstractPart implements ParsedValueOperand { + + private final Long value; + + public IntOperand(Long value) { + super(AbstractPart.PartType.INT_OPERAND); + this.value = value; + } + + @Override + public Exp getExp() { + return Exp.val(value); + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/ListOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/ListOperand.java new file mode 100644 index 0000000..afc5b3e --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/ListOperand.java @@ -0,0 +1,23 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ListOperand extends AbstractPart implements ParsedValueOperand { + + private final List value; + + public ListOperand(List list) { + super(PartType.LIST_OPERAND); + this.value = list; + } + + @Override + public Exp getExp() { + return Exp.val(value); + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/MapOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/MapOperand.java new file mode 100644 index 0000000..4a6efd9 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/MapOperand.java @@ -0,0 +1,23 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +import java.util.SortedMap; + +@Getter +public class MapOperand extends AbstractPart implements ParsedValueOperand { + + private final SortedMap value; + + public MapOperand(SortedMap map) { + super(PartType.MAP_OPERAND); + this.value = map; + } + + @Override + public Exp getExp() { + return Exp.val(value); + } +} diff --git a/src/main/java/com/aerospike/dsl/model/MetadataOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/MetadataOperand.java similarity index 79% rename from src/main/java/com/aerospike/dsl/model/MetadataOperand.java rename to src/main/java/com/aerospike/dsl/parts/operand/MetadataOperand.java index e0359e1..3b3c18c 100644 --- a/src/main/java/com/aerospike/dsl/model/MetadataOperand.java +++ b/src/main/java/com/aerospike/dsl/parts/operand/MetadataOperand.java @@ -1,26 +1,30 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.operand; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.ExpressionContainer; import lombok.Getter; @Getter -public class MetadataOperand extends Expr { +public class MetadataOperand extends ExpressionContainer { private final String functionName; + private final Integer parameter; public MetadataOperand(String functionName) { - super(constructExp(functionName, null)); + super(); this.partType = PartType.METADATA_OPERAND; this.functionName = functionName; + this.parameter = null; } public MetadataOperand(String functionName, int parameter) { - super(constructExp(functionName, parameter)); + super(); this.partType = PartType.METADATA_OPERAND; this.functionName = functionName; + this.parameter = parameter; } - private static Exp constructExp(String functionName, Integer parameter) { + private Exp constructMetadataExp(String functionName, Integer parameter) { return switch (functionName) { case "deviceSize" -> Exp.deviceSize(); case "memorySize" -> Exp.memorySize(); @@ -37,6 +41,11 @@ private static Exp constructExp(String functionName, Integer parameter) { }; } + @Override + public Exp getExp() { + return constructMetadataExp(functionName, parameter); + } + public MetadataReturnType getMetadataType() { return switch (functionName) { case "deviceSize", diff --git a/src/main/java/com/aerospike/dsl/parts/operand/ParsedValueOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/ParsedValueOperand.java new file mode 100644 index 0000000..d479108 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/ParsedValueOperand.java @@ -0,0 +1,9 @@ +package com.aerospike.dsl.parts.operand; + +/** + * This interface provides an abstraction for an operand that returns a single value to be used for constructing + * the resulting filter (e.g., a String for StringOperand or a list of objects for ListOperand) + */ +public interface ParsedValueOperand { + Object getValue(); +} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/StringOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/StringOperand.java new file mode 100644 index 0000000..a33303b --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/StringOperand.java @@ -0,0 +1,30 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; +import lombok.Setter; + +import java.util.Base64; + +@Getter +public class StringOperand extends AbstractPart implements ParsedValueOperand { + + private final String value; + @Setter + private boolean isBlob = false; + + public StringOperand(String string) { + super(PartType.STRING_OPERAND); + this.value = string; + } + + @Override + public Exp getExp() { + if (isBlob) { + byte[] byteValue = Base64.getDecoder().decode(value); + return Exp.val(byteValue); + } + return Exp.val(value); + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/VariableOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/VariableOperand.java new file mode 100644 index 0000000..d15652c --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/VariableOperand.java @@ -0,0 +1,21 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +@Getter +public class VariableOperand extends AbstractPart implements ParsedValueOperand { + + private final String value; + + public VariableOperand(String name) { + super(PartType.VARIABLE_OPERAND); + this.value = name; + } + + @Override + public Exp getExp() { + return Exp.var(value); + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java new file mode 100644 index 0000000..1611e4f --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java @@ -0,0 +1,26 @@ +package com.aerospike.dsl.parts.operand; + +import com.aerospike.dsl.parts.AbstractPart; +import lombok.Getter; + +@Getter +public class WithOperand extends AbstractPart { + + private final String string; + private final AbstractPart part; + private final boolean isLastPart; + + public WithOperand(AbstractPart part, String string) { + super(PartType.WITH_OPERAND); + this.string = string; + this.part = part; + this.isLastPart = false; + } + + public WithOperand(AbstractPart part, boolean isLastPart) { + super(PartType.WITH_OPERAND); + this.string = null; + this.part = part; + this.isLastPart = isLastPart; + } +} diff --git a/src/main/java/com/aerospike/dsl/model/BasePath.java b/src/main/java/com/aerospike/dsl/parts/path/BasePath.java similarity index 79% rename from src/main/java/com/aerospike/dsl/model/BasePath.java rename to src/main/java/com/aerospike/dsl/parts/path/BasePath.java index b061d60..d5b39cd 100644 --- a/src/main/java/com/aerospike/dsl/model/BasePath.java +++ b/src/main/java/com/aerospike/dsl/parts/path/BasePath.java @@ -1,6 +1,7 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.path; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; import lombok.Getter; import java.util.List; @@ -20,12 +21,12 @@ public BasePath(BinPart binOperand, List parts) { // Bin type is determined by the base path's first element public Exp.Type getBinType() { if (!parts.isEmpty()) { - return switch (parts.get(0).partType) { + return switch (parts.get(0).getPartType()) { case MAP_PART -> Exp.Type.MAP; case LIST_PART -> Exp.Type.LIST; default -> null; }; } - return binPart.expType; + return binPart.getExpType(); } } diff --git a/src/main/java/com/aerospike/dsl/model/BinPart.java b/src/main/java/com/aerospike/dsl/parts/path/BinPart.java similarity index 58% rename from src/main/java/com/aerospike/dsl/model/BinPart.java rename to src/main/java/com/aerospike/dsl/parts/path/BinPart.java index 98098ac..4f0b9cb 100644 --- a/src/main/java/com/aerospike/dsl/model/BinPart.java +++ b/src/main/java/com/aerospike/dsl/parts/path/BinPart.java @@ -1,15 +1,16 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.path; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.ExpressionContainer; import lombok.Getter; @Getter -public class BinPart extends Expr { +public class BinPart extends ExpressionContainer { private final String binName; public BinPart(String binName) { - super((Exp) null); // Exp unknown + super(); this.binName = binName; this.partType = PartType.BIN_PART; this.expType = null; // Exp type unknown @@ -17,7 +18,10 @@ public BinPart(String binName) { public void updateExp(Exp.Type expType) { this.expType = expType; - // Update Expression of Abstract Part - super.exp = Exp.bin(this.binName, expType); + } + + @Override + public Exp getExp() { + return Exp.bin(this.binName, expType); } } diff --git a/src/main/java/com/aerospike/dsl/model/PathOperand.java b/src/main/java/com/aerospike/dsl/parts/path/Path.java similarity index 60% rename from src/main/java/com/aerospike/dsl/model/PathOperand.java rename to src/main/java/com/aerospike/dsl/parts/path/Path.java index 204f755..328927c 100644 --- a/src/main/java/com/aerospike/dsl/model/PathOperand.java +++ b/src/main/java/com/aerospike/dsl/parts/path/Path.java @@ -1,7 +1,8 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.path; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.model.cdt.CdtPart; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.cdt.CdtPart; import lombok.Getter; import java.util.List; @@ -13,22 +14,28 @@ import static com.aerospike.dsl.util.PathOperandUtils.updateWithCdtTypeDesignator; @Getter -public class PathOperand extends AbstractPart { +public class Path extends AbstractPart { - public PathOperand(Exp exp) { - super(PartType.PATH_OPERAND, exp); + private final BasePath basePath; + private final PathFunction pathFunction; + + public Path(BasePath basePath, PathFunction pathFunction) { + super(PartType.PATH_OPERAND); + this.basePath = basePath; + this.pathFunction = pathFunction; } - public static Exp processPath(BasePath basePath, PathFunction pathFunction) { + public Exp processPath(BasePath basePath, PathFunction pathFunction) { List parts = basePath.getParts(); - basePath = updateWithCdtTypeDesignator(basePath, pathFunction); + updateWithCdtTypeDesignator(basePath, pathFunction); AbstractPart lastPathPart = !parts.isEmpty() ? parts.get(parts.size() - 1) : null; pathFunction = processPathFunction(basePath, lastPathPart, pathFunction); Exp.Type valueType = processValueType(lastPathPart, pathFunction); int cdtReturnType = 0; - if (lastPathPart instanceof CdtPart lastPart) cdtReturnType = - lastPart.getReturnType(pathFunction.getReturnParam()); + if (lastPathPart instanceof CdtPart lastPart) { + cdtReturnType = lastPart.getReturnType(pathFunction.getReturnParam()); + } if (lastPathPart != null) { // only if there are other parts except a bin return switch (pathFunction.getPathFunctionType()) { @@ -39,4 +46,9 @@ public static Exp processPath(BasePath basePath, PathFunction pathFunction) { } return null; } + + @Override + public Exp getExp() { + return processPath(basePath, pathFunction); + } } diff --git a/src/main/java/com/aerospike/dsl/model/PathFunction.java b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java similarity index 93% rename from src/main/java/com/aerospike/dsl/model/PathFunction.java rename to src/main/java/com/aerospike/dsl/parts/path/PathFunction.java index d04e9b6..0e7291f 100644 --- a/src/main/java/com/aerospike/dsl/model/PathFunction.java +++ b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java @@ -1,6 +1,7 @@ -package com.aerospike.dsl.model; +package com.aerospike.dsl.parts.path; import com.aerospike.client.exp.Exp; +import com.aerospike.dsl.parts.AbstractPart; import lombok.Getter; @Getter diff --git a/src/main/java/com/aerospike/dsl/util/ParsingUtils.java b/src/main/java/com/aerospike/dsl/util/ParsingUtils.java index 8677e88..949384b 100644 --- a/src/main/java/com/aerospike/dsl/util/ParsingUtils.java +++ b/src/main/java/com/aerospike/dsl/util/ParsingUtils.java @@ -1,5 +1,6 @@ package com.aerospike.dsl.util; +import com.aerospike.dsl.DslParseException; import lombok.NonNull; import lombok.experimental.UtilityClass; @@ -28,4 +29,47 @@ public static String unquote(String str) { public static Integer subtractNullable(Integer a, @NonNull Integer b) { return a == null ? null : a - b; } + + /** + * Extracts the type string from a method name expected to start with "as" and end with "()". + * + * @param methodName The method name string + * @return The extracted type string + * @throws DslParseException if the method name is not in the correct format + */ + public static String extractTypeFromMethod(String methodName) { + if (methodName.startsWith("as") && methodName.endsWith("()")) { + return methodName.substring(2, methodName.length() - 2); + } else { + throw new DslParseException("Invalid method name: %s".formatted(methodName)); + } + } + + /** + * Extracts the function name from a string that may include parameters in parentheses. + * + * @param text The input string containing the function name and potentially parameters + * @return The extracted function name + */ + public static String extractFunctionName(String text) { + int startParen = text.indexOf('('); + return (startParen != -1) ? text.substring(0, startParen) : text; + } + + /** + * Extracts an integer parameter from a string enclosed in parentheses. + * + * @param text The input string + * @return The extracted integer parameter, or {@code null} if not found or invalid + */ + public static Integer extractParameter(String text) { + int startParen = text.indexOf('('); + int endParen = text.indexOf(')'); + + if (startParen != -1 && endParen != -1 && endParen > startParen + 1) { + String numberStr = text.substring(startParen + 1, endParen); + return Integer.parseInt(numberStr); + } + return null; + } } diff --git a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java index a9688ac..3da3d49 100644 --- a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java +++ b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java @@ -4,26 +4,26 @@ import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.client.exp.MapExp; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.BasePath; -import com.aerospike.dsl.model.BinPart; -import com.aerospike.dsl.model.PathFunction; -import com.aerospike.dsl.model.cdt.CdtPart; -import com.aerospike.dsl.model.cdt.list.ListPart; -import com.aerospike.dsl.model.cdt.list.ListTypeDesignator; -import com.aerospike.dsl.model.cdt.map.MapPart; -import com.aerospike.dsl.model.cdt.map.MapTypeDesignator; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.path.BasePath; +import com.aerospike.dsl.parts.path.BinPart; +import com.aerospike.dsl.parts.path.PathFunction; +import com.aerospike.dsl.parts.cdt.CdtPart; +import com.aerospike.dsl.parts.cdt.list.ListPart; +import com.aerospike.dsl.parts.cdt.list.ListTypeDesignator; +import com.aerospike.dsl.parts.cdt.map.MapPart; +import com.aerospike.dsl.parts.cdt.map.MapTypeDesignator; import lombok.experimental.UtilityClass; import java.util.ArrayList; import java.util.List; import java.util.function.UnaryOperator; -import static com.aerospike.dsl.model.AbstractPart.PartType.LIST_PART; -import static com.aerospike.dsl.model.AbstractPart.PartType.MAP_PART; -import static com.aerospike.dsl.model.PathFunction.PathFunctionType.*; -import static com.aerospike.dsl.model.cdt.list.ListPart.ListPartType.*; -import static com.aerospike.dsl.model.cdt.map.MapPart.MapPartType.MAP_TYPE_DESIGNATOR; +import static com.aerospike.dsl.parts.AbstractPart.PartType.LIST_PART; +import static com.aerospike.dsl.parts.AbstractPart.PartType.MAP_PART; +import static com.aerospike.dsl.parts.path.PathFunction.PathFunctionType.*; +import static com.aerospike.dsl.parts.cdt.list.ListPart.ListPartType.*; +import static com.aerospike.dsl.parts.cdt.map.MapPart.MapPartType.MAP_TYPE_DESIGNATOR; @UtilityClass public class PathOperandUtils { @@ -201,15 +201,13 @@ private static Exp processMapPartSize(BasePath basePath, AbstractPart lastPathPa return MapExp.size(Exp.bin(bin.getBinName(), basePath.getBinType()), context); } - public static BasePath updateWithCdtTypeDesignator(BasePath basePath, PathFunction pathFunction) { + public static void updateWithCdtTypeDesignator(BasePath basePath, PathFunction pathFunction) { if (mustHaveCdtDesignator(pathFunction, basePath.getParts())) { // For cases like list.count() and map.count() with no explicit designator ([] is for List, {} is for Map) // When the last path part is CDT and potentially ambiguous, we apply List designator by default AbstractPart lastPathPart = new ListTypeDesignator(); basePath.getParts().add(lastPathPart); - return basePath; } - return basePath; } private static boolean mustHaveCdtDesignator(PathFunction pathFunction, diff --git a/src/main/java/com/aerospike/dsl/util/TypeUtils.java b/src/main/java/com/aerospike/dsl/util/TypeUtils.java index 58a76fe..ac8ea38 100644 --- a/src/main/java/com/aerospike/dsl/util/TypeUtils.java +++ b/src/main/java/com/aerospike/dsl/util/TypeUtils.java @@ -1,9 +1,9 @@ package com.aerospike.dsl.util; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.cdt.map.MapPart; -import com.aerospike.dsl.model.cdt.map.MapTypeDesignator; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.cdt.map.MapPart; +import com.aerospike.dsl.parts.cdt.map.MapTypeDesignator; import lombok.experimental.UtilityClass; @UtilityClass diff --git a/src/main/java/com/aerospike/dsl/util/ValidationUtils.java b/src/main/java/com/aerospike/dsl/util/ValidationUtils.java index f79bbee..f707b9c 100644 --- a/src/main/java/com/aerospike/dsl/util/ValidationUtils.java +++ b/src/main/java/com/aerospike/dsl/util/ValidationUtils.java @@ -1,7 +1,7 @@ package com.aerospike.dsl.util; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import lombok.experimental.UtilityClass; @UtilityClass @@ -14,7 +14,7 @@ public static void validateComparableTypes(Exp.Type leftType, Exp.Type rightType (leftType.equals(Exp.Type.FLOAT) && rightType.equals(Exp.Type.INT)); if (!leftType.equals(rightType) && !isIntAndFloat) { - throw new AerospikeDSLException("Cannot compare %s to %s".formatted(leftType, rightType)); + throw new DslParseException("Cannot compare %s to %s".formatted(leftType, rightType)); } } } diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index 175e948..3be45a8 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -3,101 +3,113 @@ import com.aerospike.client.exp.Exp; import com.aerospike.dsl.ConditionBaseVisitor; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.*; -import com.aerospike.dsl.model.cdt.list.ListIndex; -import com.aerospike.dsl.model.cdt.list.ListIndexRange; -import com.aerospike.dsl.model.cdt.list.ListRank; -import com.aerospike.dsl.model.cdt.list.ListRankRange; -import com.aerospike.dsl.model.cdt.list.ListRankRangeRelative; -import com.aerospike.dsl.model.cdt.list.ListTypeDesignator; -import com.aerospike.dsl.model.cdt.list.ListValue; -import com.aerospike.dsl.model.cdt.list.ListValueList; -import com.aerospike.dsl.model.cdt.list.ListValueRange; -import com.aerospike.dsl.model.cdt.map.*; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.ExpressionContainer; +import com.aerospike.dsl.parts.cdt.list.*; +import com.aerospike.dsl.parts.cdt.map.*; +import com.aerospike.dsl.parts.controlstructure.ExclusiveStructure; +import com.aerospike.dsl.parts.controlstructure.WhenStructure; +import com.aerospike.dsl.parts.controlstructure.WithStructure; +import com.aerospike.dsl.parts.operand.*; +import com.aerospike.dsl.parts.path.BasePath; +import com.aerospike.dsl.parts.path.BinPart; +import com.aerospike.dsl.parts.path.Path; +import com.aerospike.dsl.parts.path.PathFunction; import com.aerospike.dsl.util.TypeUtils; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.RuleNode; import java.util.ArrayList; import java.util.List; +import java.util.SortedMap; import java.util.TreeMap; +import static com.aerospike.dsl.util.ParsingUtils.extractFunctionName; +import static com.aerospike.dsl.util.ParsingUtils.extractParameter; +import static com.aerospike.dsl.util.ParsingUtils.extractTypeFromMethod; import static com.aerospike.dsl.util.ParsingUtils.unquote; -import static com.aerospike.dsl.visitor.VisitorUtils.*; +import static com.aerospike.dsl.visitor.VisitorUtils.detectImplicitTypeFromUpperTree; +import static com.aerospike.dsl.visitor.VisitorUtils.extractVariableNameOrFail; +import static com.aerospike.dsl.visitor.VisitorUtils.getPathFunctionParam; +import static com.aerospike.dsl.visitor.VisitorUtils.logicalSetBinAsBooleanExpr; +import static com.aerospike.dsl.visitor.VisitorUtils.logicalSetBinsAsBooleanExpr; +import static com.aerospike.dsl.visitor.VisitorUtils.shouldVisitListElement; +import static com.aerospike.dsl.visitor.VisitorUtils.shouldVisitMapElement; public class ExpressionConditionVisitor extends ConditionBaseVisitor { @Override public AbstractPart visitWithExpression(ConditionParser.WithExpressionContext ctx) { - List expressions = new ArrayList<>(); + List expressions = new ArrayList<>(); - // iterate each definition + // iterate through each definition for (ConditionParser.VariableDefinitionContext vdc : ctx.variableDefinition()) { - expressions.add(Exp.def(vdc.stringOperand().getText(), visit(vdc.expression()).getExp())); + AbstractPart part = visit(vdc.expression()); + WithOperand withOperand = new WithOperand(part, vdc.stringOperand().getText()); + expressions.add(withOperand); } // last expression is the action (described after "do") - expressions.add(visit(ctx.expression()).getExp()); - return new Expr(Exp.let(expressions.toArray(new Exp[0]))); + expressions.add(new WithOperand(visit(ctx.expression()), true)); + return new ExpressionContainer(new WithStructure(expressions), + ExpressionContainer.ExprPartsOperation.WITH_STRUCTURE); } @Override public AbstractPart visitWhenExpression(ConditionParser.WhenExpressionContext ctx) { - List expressions = new ArrayList<>(); - - // iterate each condition declaration + List parts = new ArrayList<>(); + // iterate through each definition declaration for (ConditionParser.ExpressionMappingContext emc : ctx.expressionMapping()) { // visit condition - expressions.add(visit(emc.expression(0)).getExp()); + parts.add(visit(emc.expression(0))); // visit action - expressions.add(visit(emc.expression(1)).getExp()); + parts.add(visit(emc.expression(1))); } - // visit default - expressions.add(visit(ctx.expression()).getExp()); - return new Expr(Exp.cond(expressions.toArray(new Exp[0]))); + parts.add(visit(ctx.expression())); + return new ExpressionContainer(new WhenStructure(parts), ExpressionContainer.ExprPartsOperation.WHEN_STRUCTURE); } @Override public AbstractPart visitAndExpression(ConditionParser.AndExpressionContext ctx) { - Expr left = (Expr) visit(ctx.expression(0)); - Expr right = (Expr) visit(ctx.expression(1)); + ExpressionContainer left = (ExpressionContainer) visit(ctx.expression(0)); + ExpressionContainer right = (ExpressionContainer) visit(ctx.expression(1)); logicalSetBinsAsBooleanExpr(left, right); - return new Expr(Exp.and(left.getExp(), right.getExp())); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.AND); } @Override public AbstractPart visitOrExpression(ConditionParser.OrExpressionContext ctx) { - Expr left = (Expr) visit(ctx.expression(0)); - Expr right = (Expr) visit(ctx.expression(1)); + ExpressionContainer left = (ExpressionContainer) visit(ctx.expression(0)); + ExpressionContainer right = (ExpressionContainer) visit(ctx.expression(1)); logicalSetBinsAsBooleanExpr(left, right); - return new Expr(Exp.or(left.getExp(), right.getExp())); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.OR); } @Override public AbstractPart visitNotExpression(ConditionParser.NotExpressionContext ctx) { - Expr expr = (Expr) visit(ctx.expression()); + ExpressionContainer expr = (ExpressionContainer) visit(ctx.expression()); logicalSetBinAsBooleanExpr(expr); - return new Expr(Exp.not(expr.getExp())); + return new ExpressionContainer(expr, ExpressionContainer.ExprPartsOperation.NOT); } @Override public AbstractPart visitExclusiveExpression(ConditionParser.ExclusiveExpressionContext ctx) { if (ctx.expression().size() < 2) { - throw new AerospikeDSLException("Exclusive logical operator requires 2 or more expressions"); + throw new DslParseException("Exclusive logical operator requires 2 or more expressions"); } - List expressions = new ArrayList<>(); - - // iterate each condition declaration + List expressions = new ArrayList<>(); + // iterate through each definition for (ConditionParser.ExpressionContext ec : ctx.expression()) { - Expr expr = (Expr) visit(ec); + ExpressionContainer expr = (ExpressionContainer) visit(ec); logicalSetBinAsBooleanExpr(expr); - expressions.add(expr.getExp()); + expressions.add(expr); } - return new Expr(Exp.exclusive(expressions.toArray(new Exp[0]))); + return new ExpressionContainer(new ExclusiveStructure(expressions), + ExpressionContainer.ExprPartsOperation.EXCLUSIVE_STRUCTURE); } @Override @@ -105,8 +117,7 @@ public AbstractPart visitGreaterThanExpression(ConditionParser.GreaterThanExpres AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::gt); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.GT); } @Override @@ -114,8 +125,7 @@ public AbstractPart visitGreaterThanOrEqualExpression(ConditionParser.GreaterTha AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::ge); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.GTEQ); } @Override @@ -123,8 +133,7 @@ public AbstractPart visitLessThanExpression(ConditionParser.LessThanExpressionCo AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::lt); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.LT); } @Override @@ -132,8 +141,7 @@ public AbstractPart visitLessThanOrEqualExpression(ConditionParser.LessThanOrEqu AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::le); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.LTEQ); } @Override @@ -141,8 +149,7 @@ public AbstractPart visitEqualityExpression(ConditionParser.EqualityExpressionCo AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::eq); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.EQ); } @Override @@ -150,8 +157,7 @@ public AbstractPart visitInequalityExpression(ConditionParser.InequalityExpressi AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::ne); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.NOTEQ); } @Override @@ -159,8 +165,7 @@ public AbstractPart visitAddExpression(ConditionParser.AddExpressionContext ctx) AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::add); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.ADD); } @Override @@ -168,8 +173,7 @@ public AbstractPart visitSubExpression(ConditionParser.SubExpressionContext ctx) AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::sub); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.SUB); } @Override @@ -177,8 +181,7 @@ public AbstractPart visitMulExpression(ConditionParser.MulExpressionContext ctx) AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::mul); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.MUL); } @Override @@ -186,8 +189,7 @@ public AbstractPart visitDivExpression(ConditionParser.DivExpressionContext ctx) AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::div); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.DIV); } @Override @@ -195,8 +197,7 @@ public AbstractPart visitModExpression(ConditionParser.ModExpressionContext ctx) AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::mod); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.MOD); } @Override @@ -204,8 +205,7 @@ public AbstractPart visitIntAndExpression(ConditionParser.IntAndExpressionContex AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::intAnd); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_AND); } @Override @@ -213,8 +213,7 @@ public AbstractPart visitIntOrExpression(ConditionParser.IntOrExpressionContext AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::intOr); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_OR); } @Override @@ -222,16 +221,14 @@ public AbstractPart visitIntXorExpression(ConditionParser.IntXorExpressionContex AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::intXor); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_XOR); } @Override public AbstractPart visitIntNotExpression(ConditionParser.IntNotExpressionContext ctx) { AbstractPart operand = visit(ctx.operand()); - Exp exp = getExpOrFail(operand, Exp::intNot); - return new Expr(exp); + return new ExpressionContainer(operand, ExpressionContainer.ExprPartsOperation.INT_NOT); } @Override @@ -239,8 +236,7 @@ public AbstractPart visitIntLShiftExpression(ConditionParser.IntLShiftExpression AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::lshift); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.L_SHIFT); } @Override @@ -248,8 +244,7 @@ public AbstractPart visitIntRShiftExpression(ConditionParser.IntRShiftExpression AbstractPart left = visit(ctx.operand(0)); AbstractPart right = visit(ctx.operand(1)); - Exp exp = getExpOrFail(left, right, Exp::rshift); - return new Expr(exp); + return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.R_SHIFT); } @Override @@ -312,7 +307,7 @@ public AbstractPart visitListConstant(ConditionParser.ListConstantContext ctx) { public ListOperand readChildrenIntoListOperand(RuleNode listNode) { int size = listNode.getChildCount(); - List list = new ArrayList<>(); + List values = new ArrayList<>(); for (int i = 0; i < size; i++) { ParseTree child = listNode.getChild(i); if (!shouldVisitListElement(i, size, child)) { @@ -321,17 +316,17 @@ public ListOperand readChildrenIntoListOperand(RuleNode listNode) { AbstractPart operand = visit(child); // delegate to a dedicated visitor if (operand == null) { - throw new AerospikeDSLException("Unable to parse list operand"); + throw new DslParseException("Unable to parse list operand"); } try { - list.add(((ParsedOperand) operand).getValue()); + values.add(((ParsedValueOperand) operand).getValue()); } catch (ClassCastException e) { - throw new AerospikeDSLException("List constant contains elements of different type"); + throw new DslParseException("List constant contains elements of different type"); } } - return new ListOperand(list); + return new ListOperand(values); } @Override @@ -339,32 +334,35 @@ public AbstractPart visitOrderedMapConstant(ConditionParser.OrderedMapConstantCo return readChildrenIntoMapOperand(ctx); } - public TreeMap getOrderedMapPair(ParseTree ctx) { + public SortedMap getOrderedMapPair(ParseTree ctx) { if (ctx.getChild(0) == null || ctx.getChild(2) == null) { - throw new AerospikeDSLException("Unable to parse map operand"); + throw new DslParseException("Unable to parse map operand"); } - Object key = ((ParsedOperand) visit(ctx.getChild(0))).getValue(); - Object value = ((ParsedOperand) visit(ctx.getChild(2))).getValue(); - TreeMap map = new TreeMap<>(); + + Object key = ((ParsedValueOperand) visit(ctx.getChild(0))).getValue(); + Object value = ((ParsedValueOperand) visit(ctx.getChild(2))).getValue(); + + SortedMap map = new TreeMap<>(); map.put(key, value); + return map; } public MapOperand readChildrenIntoMapOperand(RuleNode mapNode) { int size = mapNode.getChildCount(); - TreeMap map = new TreeMap<>(); + SortedMap map = new TreeMap<>(); for (int i = 0; i < size; i++) { ParseTree child = mapNode.getChild(i); if (!shouldVisitMapElement(i, size, child)) { continue; } - TreeMap mapOfPair = getOrderedMapPair(child); // delegate to a dedicated visitor + SortedMap mapOfPair = getOrderedMapPair(child); // delegate to a dedicated visitor try { mapOfPair.forEach(map::putIfAbsent); // put contents of the current map pair to the resulting map } catch (ClassCastException e) { - throw new AerospikeDSLException("Map constant contains elements of different type"); + throw new DslParseException("Map constant contains elements of different type"); } } @@ -414,12 +412,12 @@ public AbstractPart visitBasePath(ConditionParser.BasePathContext ctx) { switch (part.getPartType()) { case BIN_PART -> binPart = (BinPart) overrideType(part, ctx); case LIST_PART, MAP_PART -> parts.add(overrideType(part, ctx)); - default -> throw new AerospikeDSLException("Unexpected path part: %s".formatted(part.getPartType())); + default -> throw new DslParseException("Unexpected path part: %s".formatted(part.getPartType())); } } if (binPart == null) { - throw new AerospikeDSLException("Expecting bin to be the first path part from the left"); + throw new DslParseException("Expecting bin to be the first path part from the left"); } return new BasePath(binPart, parts); @@ -428,7 +426,7 @@ public AbstractPart visitBasePath(ConditionParser.BasePathContext ctx) { @Override public AbstractPart visitVariable(ConditionParser.VariableContext ctx) { String text = ctx.VARIABLE_REFERENCE().getText(); - return new VariableOperand(extractVariableName(text)); + return new VariableOperand(extractVariableNameOrFail(text)); } private AbstractPart overrideType(AbstractPart part, ParseTree ctx) { @@ -442,8 +440,8 @@ private AbstractPart overrideType(AbstractPart part, ParseTree ctx) { if (pathFunction != null) { Exp.Type type = pathFunction.getBinType(); if (type != null) { - if (part instanceof BinPart) { - ((BinPart) part).updateExp(type); + if (part instanceof BinPart binPart) { + binPart.updateExp(type); } else { part.setExpType(type); } @@ -451,11 +449,11 @@ private AbstractPart overrideType(AbstractPart part, ParseTree ctx) { } } else { // Override using Implicit type detection Exp.Type implicitType = detectImplicitTypeFromUpperTree(ctx); - if (part instanceof BinPart) { + if (part instanceof BinPart binPart) { if (implicitType == null) { implicitType = Exp.Type.INT; } - ((BinPart) part).updateExp(implicitType); + binPart.updateExp(implicitType); } else { // ListPart or MapPart if (implicitType == null) { implicitType = TypeUtils.getDefaultType(part); @@ -473,10 +471,9 @@ public AbstractPart visitPath(ConditionParser.PathContext ctx) { // if there are other parts except bin, get a corresponding Exp if (!parts.isEmpty() || ctx.pathFunction() != null && ctx.pathFunction().pathFunctionCount() != null) { - Exp exp = PathOperand.processPath(basePath, ctx.pathFunction() == null + return new Path(basePath, ctx.pathFunction() == null ? null : (PathFunction) visit(ctx.pathFunction())); - return exp == null ? null : new PathOperand(exp); } return basePath.getBinPart(); } @@ -493,7 +490,7 @@ public AbstractPart visitListPart(ConditionParser.ListPartContext ctx) { if (ctx.listRankRange() != null) return ListRankRange.from(ctx.listRankRange()); if (ctx.listRankRangeRelative() != null) return ListRankRangeRelative.from(ctx.listRankRangeRelative()); - throw new AerospikeDSLException("Unexpected list part: %s".formatted(ctx.getText())); + throw new DslParseException("Unexpected list part: %s".formatted(ctx.getText())); } @Override @@ -513,7 +510,7 @@ public AbstractPart visitMapPart(ConditionParser.MapPartContext ctx) { return MapRankRangeRelative.from(ctx.mapRankRangeRelative()); if (ctx.mapIndexRangeRelative() != null) return MapIndexRangeRelative.from(ctx.mapIndexRangeRelative()); - throw new AerospikeDSLException("Unexpected map part: %s".formatted(ctx.getText())); + throw new DslParseException("Unexpected map part: %s".formatted(ctx.getText())); } @Override diff --git a/src/main/java/com/aerospike/dsl/visitor/FilterConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/FilterConditionVisitor.java deleted file mode 100644 index c11829f..0000000 --- a/src/main/java/com/aerospike/dsl/visitor/FilterConditionVisitor.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.aerospike.dsl.visitor; - -import com.aerospike.client.query.Filter; -import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.Expr; -import com.aerospike.dsl.model.SIndexFilter; - -import static com.aerospike.dsl.model.Expr.ExprPartsOperation.*; -import static com.aerospike.dsl.visitor.VisitorUtils.FilterOperationType.*; -import static com.aerospike.dsl.visitor.VisitorUtils.getFilterOrFail; -import static com.aerospike.dsl.visitor.VisitorUtils.validateNumericBin; - -public class FilterConditionVisitor extends ExpressionConditionVisitor { - - @Override - public AbstractPart visitGreaterThanExpression(ConditionParser.GreaterThanExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - Filter filter = getFilterOrFail(left, right, GT); - return new Expr(new SIndexFilter(filter)); - } - - @Override - public AbstractPart visitGreaterThanOrEqualExpression(ConditionParser.GreaterThanOrEqualExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - Filter filter = getFilterOrFail(left, right, GTEQ); - return new Expr(new SIndexFilter(filter)); - } - - @Override - public AbstractPart visitLessThanExpression(ConditionParser.LessThanExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - Filter filter = getFilterOrFail(left, right, LT); - return new Expr(new SIndexFilter(filter)); - } - - @Override - public AbstractPart visitLessThanOrEqualExpression(ConditionParser.LessThanOrEqualExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - Filter filter = getFilterOrFail(left, right, LTEQ); - return new Expr(new SIndexFilter(filter)); - } - - @Override - public AbstractPart visitEqualityExpression(ConditionParser.EqualityExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - Filter filter = getFilterOrFail(left, right, EQ); - return new Expr(new SIndexFilter(filter)); - } - - @Override - public AbstractPart visitInequalityExpression(ConditionParser.InequalityExpressionContext ctx) { - throw new AerospikeDSLException("The operation is not supported by secondary index filter"); - } - - @Override - public AbstractPart visitAddExpression(ConditionParser.AddExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - validateNumericBin(left, right); - return new Expr(left, right, ADD); - } - - @Override - public AbstractPart visitSubExpression(ConditionParser.SubExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - validateNumericBin(left, right); - return new Expr(left, right, SUB); - } - - @Override - public AbstractPart visitDivExpression(ConditionParser.DivExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - validateNumericBin(left, right); - return new Expr(left, right, DIV); - } - - @Override - public AbstractPart visitMulExpression(ConditionParser.MulExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); - - validateNumericBin(left, right); - return new Expr(left, right, MUL); - } -} diff --git a/src/main/java/com/aerospike/dsl/visitor/NoApplicableFilterException.java b/src/main/java/com/aerospike/dsl/visitor/NoApplicableFilterException.java new file mode 100644 index 0000000..4b6813e --- /dev/null +++ b/src/main/java/com/aerospike/dsl/visitor/NoApplicableFilterException.java @@ -0,0 +1,19 @@ +package com.aerospike.dsl.visitor; + +import com.aerospike.client.query.Filter; + +/** + * Indicates that no applicable {@link Filter} could be generated for a given DSL expression. For internal use. + * + *

This exception is typically thrown when attempting to create a Filter for a DSL expression + * but the structure or types of the expression do not match any supported filtering patterns + * (e.g., comparing Strings using arithmetical operations, using OR-combined expression etc.). + * It signifies that while the expression might be valid in a broader context, it cannot be represented with a + * secondary index Filter. + */ +class NoApplicableFilterException extends RuntimeException { + + NoApplicableFilterException(String description) { + super(description); + } +} diff --git a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java index 744416e..a954924 100644 --- a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java +++ b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java @@ -2,61 +2,150 @@ import com.aerospike.client.exp.Exp; import com.aerospike.client.query.Filter; +import com.aerospike.client.query.IndexType; import com.aerospike.dsl.ConditionParser; -import com.aerospike.dsl.exception.AerospikeDSLException; -import com.aerospike.dsl.model.AbstractPart; -import com.aerospike.dsl.model.BinPart; -import com.aerospike.dsl.model.Expr; -import com.aerospike.dsl.model.IntOperand; -import com.aerospike.dsl.model.MetadataOperand; -import com.aerospike.dsl.model.StringOperand; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.ExpressionContainer; +import com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation; +import com.aerospike.dsl.parts.controlstructure.ExclusiveStructure; +import com.aerospike.dsl.parts.controlstructure.WhenStructure; +import com.aerospike.dsl.parts.controlstructure.WithStructure; +import com.aerospike.dsl.parts.operand.IntOperand; +import com.aerospike.dsl.parts.operand.MetadataOperand; +import com.aerospike.dsl.parts.operand.StringOperand; +import com.aerospike.dsl.parts.operand.WithOperand; +import com.aerospike.dsl.parts.path.BinPart; import lombok.experimental.UtilityClass; import org.antlr.v4.runtime.misc.Pair; import org.antlr.v4.runtime.tree.ParseTree; -import java.util.Base64; -import java.util.Objects; +import java.util.*; import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.UnaryOperator; -import static com.aerospike.dsl.model.Expr.ExprPartsOperation.*; +import static com.aerospike.dsl.parts.AbstractPart.PartType.BIN_PART; +import static com.aerospike.dsl.parts.AbstractPart.PartType.EXPRESSION_CONTAINER; +import static com.aerospike.dsl.parts.AbstractPart.PartType.INT_OPERAND; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.ADD; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.AND; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.DIV; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.MUL; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.OR; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.SUB; import static com.aerospike.dsl.util.ValidationUtils.validateComparableTypes; -import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.*; -import static com.aerospike.dsl.visitor.VisitorUtils.FilterOperationType.*; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.ADDEND; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.DIVIDEND; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.DIVISOR; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.MIN; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.MULTIPLICAND; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.MULTIPLIER; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.SUBTR; @UtilityClass public class VisitorUtils { - protected enum FilterOperationType { - GT, - GTEQ, - LT, - LTEQ, - EQ, - NOTEQ + private final Map expTypeToIndexType = Map.of( + Exp.Type.INT, IndexType.NUMERIC, + Exp.Type.STRING, IndexType.STRING, + Exp.Type.BLOB, IndexType.BLOB + ); + + /** + * Converts an {@link ExprPartsOperation} enum value to its corresponding {@link FilterOperationType}. + * + * @param exprPartsOperation The {@link ExprPartsOperation} to convert + * @return The corresponding {@link FilterOperationType} + * @throws NoApplicableFilterException if the {@link ExprPartsOperation} has no matching {@link FilterOperationType} + */ + private static FilterOperationType getFilterOperation(ExprPartsOperation exprPartsOperation) { + return switch (exprPartsOperation) { + case GT -> FilterOperationType.GT; + case GTEQ -> FilterOperationType.GTEQ; + case LT -> FilterOperationType.LT; + case LTEQ -> FilterOperationType.LTEQ; + case EQ -> FilterOperationType.EQ; + case NOTEQ -> FilterOperationType.NOTEQ; + default -> throw new NoApplicableFilterException("ExprPartsOperation has no matching FilterOperationType"); + }; } - protected enum ArithmeticTermType { - ADDEND, - SUBTR, - MIN, - DIFFERENCE, - DIVIDEND, - DIVISOR, - QUOTIENT, - MULTIPLICAND, - MULTIPLIER, - PRODUCT + /** + * Gets the appropriate {@link BinaryOperator} for a given {@link ExprPartsOperation}. + * + * @param exprPartsOperation The {@link ExprPartsOperation} for which to get the binary operator + * @return A {@link BinaryOperator} that performs the corresponding operation on two {@link Exp} operands + * @throws NoApplicableFilterException if the {@link ExprPartsOperation} has no matching {@link BinaryOperator} + */ + private static BinaryOperator getExpOperator(ExprPartsOperation exprPartsOperation) { + return switch (exprPartsOperation) { + case ADD -> Exp::add; + case SUB -> Exp::sub; + case MUL -> Exp::mul; + case DIV -> Exp::div; + case MOD -> Exp::mod; + case INT_XOR -> Exp::intXor; + case L_SHIFT -> Exp::lshift; + case R_SHIFT -> Exp::rshift; + case INT_AND -> Exp::intAnd; + case INT_OR -> Exp::intOr; + case AND -> Exp::and; + case OR -> Exp::or; + case EQ -> Exp::eq; + case NOTEQ -> Exp::ne; + case LT -> Exp::lt; + case LTEQ -> Exp::le; + case GT -> Exp::gt; + case GTEQ -> Exp::ge; + default -> throw new NoApplicableFilterException("ExprPartsOperation has no matching BinaryOperator"); + }; } - static String extractVariableName(String variableReference) { + /** + * Gets the appropriate {@link UnaryOperator} for a given {@link ExprPartsOperation}. + * + * @param exprPartsOperation The {@link ExprPartsOperation} for which to get the unary operator + * @return A {@link UnaryOperator} that performs the corresponding operation on a single {@link Exp} operand + * @throws NoApplicableFilterException if the {@link ExprPartsOperation} has no matching {@link UnaryOperator} + */ + private static UnaryOperator getUnaryExpOperator(ExprPartsOperation exprPartsOperation) { + return switch (exprPartsOperation) { + case INT_NOT -> Exp::intNot; + case NOT -> Exp::not; + default -> throw new NoApplicableFilterException("ExprPartsOperation has no matching UnaryOperator"); + }; + } + + /** + * Extracts the variable name from a string formatted as "${variableName}". + * If it matches this format, it returns the substring between these markers, otherwise it throws an + * {@code IllegalArgumentException}. + * + * @param variableReference The input string + * @return The extracted variable name + * @throws IllegalArgumentException if the input string does not match the format + */ + static String extractVariableNameOrFail(String variableReference) { if (variableReference.startsWith("${") && variableReference.endsWith("}")) { return variableReference.substring(2, variableReference.length() - 1); } throw new IllegalArgumentException("Input string is not in the correct format"); - } + /** + * The method traverses the parse tree from the given context {@code ctx} + * towards the root. At each level, it checks the children for operand contexts. + * If an operand context representing an integer, float, string, or boolean + * is found, its corresponding {@link Exp.Type} is returned. The search stops + * upon finding the first such operand in the upward path. + * + * @param ctx The current parse tree context to start searching from. + * @return The detected implicit {@link Exp.Type} (INT, FLOAT, STRING, or BOOL), + * or {@code null} if no such operand is found in the upper tree levels. + */ static Exp.Type detectImplicitTypeFromUpperTree(ParseTree ctx) { // Search for a "leaf" operand child (Int, Float, String and Boolean) // in the above levels of the current path in the expression tree @@ -85,25 +174,58 @@ static Exp.Type detectImplicitTypeFromUpperTree(ParseTree ctx) { return null; } - static void logicalSetBinsAsBooleanExpr(Expr left, Expr right) { + /** + * Sets the logical bin type for both the left and right expression containers + * to {@link Exp.Type#BOOL} if they represent a bin part. + * + * @param left The left {@link ExpressionContainer} + * @param right The right {@link ExpressionContainer} + */ + static void logicalSetBinsAsBooleanExpr(ExpressionContainer left, ExpressionContainer right) { logicalSetBinAsBooleanExpr(left); logicalSetBinAsBooleanExpr(right); } - static void logicalSetBinAsBooleanExpr(Expr expr) { - if (expr instanceof BinPart) { + /** + * Sets the logical bin type for a single expression container to {@link Exp.Type#BOOL} + * if it represents a bin part. + * + * @param expr The {@link ExpressionContainer} to potentially update + */ + static void logicalSetBinAsBooleanExpr(ExpressionContainer expr) { + if (expr.getPartType() == BIN_PART) { ((BinPart) expr).updateExp(Exp.Type.BOOL); } } + /** + * Determines whether a child parse tree element at a specific index within a list + * should be visited during tree traversal. + * + * @param i The index of the child element + * @param size The total number of children in the list context + * @param child The parse tree child element at the given index + * @return {@code true} if the child should be visited as a list element, {@code false} otherwise + */ static boolean shouldVisitListElement(int i, int size, ParseTree child) { + //noinspection GrazieInspection return size > 0 // size is not 0 && i != 0 // not the first element ('[') && i != size - 1 // not the last element (']') && !child.getText().equals(","); // not a comma (list elements separator) } + /** + * Determines whether a child parse tree element at a specific index within a map + * should be visited during tree traversal. + * + * @param i The index of the child element + * @param size The total number of children in the map context + * @param child The parse tree child element at the given index + * @return {@code true} if the child should be visited as a map element, {@code false} otherwise + */ static boolean shouldVisitMapElement(int i, int size, ParseTree child) { + //noinspection GrazieInspection return size > 0 // size is not 0 && i != 0 // not the first element ('{') && i != size - 1 // not the last element ('}') @@ -111,157 +233,120 @@ static boolean shouldVisitMapElement(int i, int size, ParseTree child) { && !child.getText().equals(","); // not a comma (map pairs separator) } - // 2 operands Expressions - static Exp getExpOrFail(AbstractPart left, AbstractPart right, BinaryOperator operator) { - if (left == null) { - throw new AerospikeDSLException("Unable to parse left operand"); - } - if (right == null) { - throw new AerospikeDSLException("Unable to parse right operand"); - } - - if (left.getPartType() == AbstractPart.PartType.BIN_PART) { - return getExpLeftBinTypeComparison((BinPart) left, right, operator); - } - if (right.getPartType() == AbstractPart.PartType.BIN_PART) { - return getExpRightBinTypeComparison(left, (BinPart) right, operator); - } - - // Handle non Bin operands cases - Exp leftExp = left.getExp(); - Exp rightExp = right.getExp(); - return operator.apply(leftExp, rightExp); - } - - static Exp getExpLeftBinTypeComparison(BinPart left, AbstractPart right, BinaryOperator operator) { - String binNameLeft = left.getBinName(); - return switch (right.getPartType()) { + /** + * Creates an expression for comparing a bin with another operand. + * + * @param binPart The bin part + * @param otherPart The other operand to compare with + * @param operator The binary operator to apply + * @param binIsLeft Whether the bin is on the left side of the comparison + * @return The resulting expression + * @throws DslParseException if the operand type is not supported + */ + private static Exp getExpBinComparison(BinPart binPart, AbstractPart otherPart, + BinaryOperator operator, boolean binIsLeft) { + Exp binExp = Exp.bin(binPart.getBinName(), binPart.getExpType()); + Exp otherExp = switch (otherPart.getPartType()) { case INT_OPERAND -> { - validateComparableTypes(left.getExpType(), Exp.Type.INT); - yield operator.apply(left.getExp(), right.getExp()); + validateComparableTypes(binPart.getExpType(), Exp.Type.INT); + yield otherPart.getExp(); } case FLOAT_OPERAND -> { - validateComparableTypes(left.getExpType(), Exp.Type.FLOAT); - yield operator.apply(left.getExp(), right.getExp()); + validateComparableTypes(binPart.getExpType(), Exp.Type.FLOAT); + yield otherPart.getExp(); } case BOOL_OPERAND -> { - validateComparableTypes(left.getExpType(), Exp.Type.BOOL); - yield operator.apply(left.getExp(), right.getExp()); - } - case STRING_OPERAND -> { - if (left.getExpType() != null && - left.getExpType().equals(Exp.Type.BLOB)) { - // Base64 Blob - validateComparableTypes(left.getExpType(), Exp.Type.BLOB); - String base64String = ((StringOperand) right).getValue(); - byte[] value = Base64.getDecoder().decode(base64String); - yield operator.apply(left.getExp(), Exp.val(value)); - } else { - // String - validateComparableTypes(left.getExpType(), Exp.Type.STRING); - yield operator.apply(left.getExp(), right.getExp()); - } + validateComparableTypes(binPart.getExpType(), Exp.Type.BOOL); + yield otherPart.getExp(); } + case STRING_OPERAND -> handleStringOperandComparison(binPart, (StringOperand) otherPart); case METADATA_OPERAND -> { - // No need to validate, types are determined by metadata function - Exp.Type binType = Exp.Type.valueOf(((MetadataOperand) right).getMetadataType().toString()); - yield operator.apply( - Exp.bin(binNameLeft, binType), - right.getExp() - ); + // Handle metadata comparison - type determined by metadata function + Exp.Type binType = Exp.Type.valueOf(((MetadataOperand) otherPart).getMetadataType().toString()); + binExp = Exp.bin(binPart.getBinName(), binType); + yield otherPart.getExp(); } - case EXPR, PATH_OPERAND -> - operator.apply(left.getExp(), right.getExp()); // Can't validate with Expr on one side - // Left and right are both bin parts + case EXPRESSION_CONTAINER, PATH_OPERAND, VARIABLE_OPERAND -> + // Can't validate with expression container + otherPart.getExp(); case BIN_PART -> { - // Validate types if possible - validateComparableTypes(left.getExpType(), right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); + // Both are bin parts + validateComparableTypes(binPart.getExpType(), otherPart.getExpType()); + yield otherPart.getExp(); } case LIST_OPERAND -> { - validateComparableTypes(left.getExpType(), Exp.Type.LIST); - yield operator.apply(left.getExp(), right.getExp()); + validateComparableTypes(binPart.getExpType(), Exp.Type.LIST); + yield otherPart.getExp(); } case MAP_OPERAND -> { - validateComparableTypes(left.getExpType(), Exp.Type.MAP); - yield operator.apply(left.getExp(), right.getExp()); + validateComparableTypes(binPart.getExpType(), Exp.Type.MAP); + yield otherPart.getExp(); } - default -> throw new AerospikeDSLException("Operand type not supported: %s".formatted(right.getPartType())); + default -> throw new DslParseException("Operand type not supported: %s".formatted(otherPart.getPartType())); }; - } - static Exp getExpRightBinTypeComparison(AbstractPart left, BinPart right, BinaryOperator operator) { - String binNameRight = right.getBinName(); - return switch (left.getPartType()) { - case INT_OPERAND -> { - validateComparableTypes(Exp.Type.INT, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - case FLOAT_OPERAND -> { - validateComparableTypes(Exp.Type.FLOAT, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - case BOOL_OPERAND -> { - validateComparableTypes(Exp.Type.BOOL, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - case STRING_OPERAND -> { - if (right.getExpType() != null && - right.getExpType().equals(Exp.Type.BLOB)) { - // Base64 Blob - validateComparableTypes(Exp.Type.BLOB, right.getExpType()); - String base64String = ((StringOperand) left).getValue(); - byte[] value = Base64.getDecoder().decode(base64String); - yield operator.apply(Exp.val(value), right.getExp()); - } else { - // String - validateComparableTypes(Exp.Type.STRING, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - } - case METADATA_OPERAND -> { - // No need to validate, types are determined by metadata function - Exp.Type binType = Exp.Type.valueOf(((MetadataOperand) left).getMetadataType().toString()); - yield operator.apply( - left.getExp(), - Exp.bin(binNameRight, binType) - ); - } - case EXPR, PATH_OPERAND -> - operator.apply(left.getExp(), right.getExp()); // Can't validate with Expr on one side - // No need for 2 BIN_OPERAND handling since it's covered in the left condition - case LIST_OPERAND -> { - validateComparableTypes(Exp.Type.LIST, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - case MAP_OPERAND -> { - validateComparableTypes(Exp.Type.MAP, right.getExpType()); - yield operator.apply(left.getExp(), right.getExp()); - } - default -> throw new AerospikeDSLException("Operand type not supported: %s".formatted(left.getPartType())); - }; + return binIsLeft ? operator.apply(binExp, otherExp) : operator.apply(otherExp, binExp); } - // 1 operand Expressions - static Exp getExpOrFail(AbstractPart operand, UnaryOperator operator) { - if (operand == null) { - throw new AerospikeDSLException("Unable to parse operand"); + /** + * Handles string operand comparison with type validation and blob handling. + * + * @param binPart The {@link BinPart} involved in the comparison + * @param stringOperand The {@link StringOperand} involved in the comparison + * @return The {@link Exp} generated from the {@link StringOperand} + * @throws DslParseException if type validation fails (e.g., comparing a non-string/blob bin with a String) + */ + private static Exp handleStringOperandComparison(BinPart binPart, StringOperand stringOperand) { + boolean isBlobType = binPart.getExpType() != null && binPart.getExpType().equals(Exp.Type.BLOB); + if (isBlobType) { + // Handle base64 blob comparison + validateComparableTypes(binPart.getExpType(), Exp.Type.BLOB); + stringOperand.setBlob(true); + } else { + // Handle regular string comparison + validateComparableTypes(binPart.getExpType(), Exp.Type.STRING); } + return stringOperand.getExp(); + } - // 1 Operand Expression is always a BIN Operand - String binName = ((BinPart) operand).getBinName(); + /** + * Creates an expression for comparing a bin on the left with an operand on the right. + * + * @param left The {@link BinPart} on the left side of the comparison + * @param right The {@link AbstractPart} on the right side of the comparison + * @param operator The binary operator to apply + * @return The resulting {@link Exp} for the comparison + * @throws DslParseException if an unsupported operand type is encountered or type validation fails + */ + private static Exp getExpLeftBinTypeComparison(BinPart left, AbstractPart right, BinaryOperator operator) { + return getExpBinComparison(left, right, operator, true); + } - // There is only 1 case of a single operand expression (int not), and it always gets an integer - return operator.apply(Exp.bin(binName, Exp.Type.INT)); + /** + * Creates an expression for comparing an operand on the left with a bin on the right. + * + * @param left The {@link AbstractPart} on the left side of the comparison + * @param right The {@link BinPart} on the right side of the comparison + * @param operator The binary operator to apply + * @return The resulting {@link Exp} for the comparison + * @throws DslParseException if an unsupported operand type is encountered or type validation fails + */ + private static Exp getExpRightBinTypeComparison(AbstractPart left, BinPart right, BinaryOperator operator) { + return getExpBinComparison(right, left, operator, false); } + /** + * Extracts the value of a specific parameter from a Path Function parameter context. + * + * @param paramCtx The parse tree context for the Path Function parameter + * @param paramName The name of the parameter to extract the value for + * @return The value of the specified parameter if found and matches the name, otherwise {@code null}. + */ static String getPathFunctionParam(ConditionParser.PathFunctionParamContext paramCtx, String paramName) { - String paramNameText; - String paramNameValue; String paramValue = null; if (paramCtx.pathFunctionParamName() != null) { - paramNameText = paramCtx.pathFunctionParamName().getText(); - paramNameValue = paramCtx.pathFunctionParamValue().getText(); + String paramNameText = paramCtx.pathFunctionParamName().getText(); + String paramNameValue = paramCtx.pathFunctionParamValue().getText(); if (paramNameText.equalsIgnoreCase(paramName)) { paramValue = paramNameValue; } @@ -269,118 +354,43 @@ static String getPathFunctionParam(ConditionParser.PathFunctionParamContext para return paramValue; } - static String extractTypeFromMethod(String methodName) { - if (methodName.startsWith("as") && methodName.endsWith("()")) { - return methodName.substring(2, methodName.length() - 2); - } else { - throw new AerospikeDSLException("Invalid method name: %s".formatted(methodName)); - } - } - - static String extractFunctionName(String text) { - int startParen = text.indexOf('('); - return (startParen != -1) ? text.substring(0, startParen) : text; - } - - static Integer extractParameter(String text) { - int startParen = text.indexOf('('); - int endParen = text.indexOf(')'); - - if (startParen != -1 && endParen != -1 && endParen > startParen + 1) { - String numberStr = text.substring(startParen + 1, endParen); - return Integer.parseInt(numberStr); - } - return null; - } - - // 2 operands Filters - static Filter getFilterOrFail(AbstractPart left, AbstractPart right, FilterOperationType type) { - if (left == null) { - throw new AerospikeDSLException("Unable to parse left operand"); - } - if (right == null) { - throw new AerospikeDSLException("Unable to parse right operand"); - } - - if (left.getPartType() == AbstractPart.PartType.BIN_PART) { - return getFilter((BinPart) left, right, type); - } - if (right.getPartType() == AbstractPart.PartType.BIN_PART) { - return getFilter((BinPart) right, left, invertType(type)); - } - - // Handle non Bin operands cases - if (left instanceof Expr leftExpr) { - return getFilterOrFail(leftExpr.getLeft(), leftExpr.getRight(), leftExpr.getOperationType(), right, type); - } - if (right instanceof Expr rightExpr) { - return getFilterOrFail(rightExpr.getLeft(), rightExpr.getRight(), rightExpr.getOperationType(), left, type); - } - return null; - } - - // 2 operands Filters - static Filter getFilterOrFail(AbstractPart exprLeft, AbstractPart exprRight, Expr.ExprPartsOperation operationType, - AbstractPart right, FilterOperationType type) { - if (exprLeft == null) { - throw new AerospikeDSLException("Unable to parse left operand of expression"); - } - if (exprRight == null) { - throw new AerospikeDSLException("Unable to parse right operand of expression"); - } - - if (exprLeft.getPartType() == AbstractPart.PartType.BIN_PART) { // bin is on the left side - if (exprRight instanceof IntOperand leftOperand && right instanceof IntOperand rightOperand) { - validateComparableTypes(exprLeft.getExpType(), Exp.Type.INT); - return applyFilterOperator(((BinPart) exprLeft).getBinName(), leftOperand, rightOperand, - operationType, type, getTermType(operationType, true)); - } - throw new AerospikeDSLException( - String.format("Operands not supported in secondary index Filter: %s, %s", exprRight, right)); - } - if (exprRight.getPartType() == AbstractPart.PartType.BIN_PART) { // bin is on the right side - if (exprLeft instanceof IntOperand leftOperand && right instanceof IntOperand rightOperand) { - validateComparableTypes(exprRight.getExpType(), Exp.Type.INT); - return applyFilterOperator(((BinPart) exprRight).getBinName(), leftOperand, rightOperand, - operationType, type, getTermType(operationType, false)); - } - throw new AerospikeDSLException( - String.format("Operands not supported in secondary index Filter: %s, %s", exprRight, right)); - } - - // Handle non Bin operands cases - if (exprLeft instanceof Expr leftExpr) { - return getFilterOrFail(leftExpr.getLeft(), leftExpr.getRight(), type); - } - return null; - } - - static void validateNumericBin(AbstractPart left, AbstractPart right) { - if (!isNumericBin(left, right)) { - throw new AerospikeDSLException("The operation is not supported by secondary index filter"); - } - } - - private static boolean isNumericBin(AbstractPart left, AbstractPart right) { - return (left instanceof BinPart && right instanceof IntOperand) - || (right instanceof BinPart && left instanceof IntOperand); - } - - private static ArithmeticTermType getTermType(Expr.ExprPartsOperation operationType, boolean isLeftTerm) { + /** + * Determines the arithmetic term type based on the operation type and whether it is the left or right operand. + * + * @param operationType The type of the arithmetic operation + * @param isLeftTerm {@code true} if the term is the left operand, {@code false} if it's the right + * @return The corresponding {@link ArithmeticTermType} + * @throws NoApplicableFilterException if the operation type is not supported for determining arithmetic term type + */ + private static ArithmeticTermType getFilterTermType(ExprPartsOperation operationType, boolean isLeftTerm) { return switch (operationType) { case ADD -> ADDEND; case SUB -> isLeftTerm ? SUBTR : MIN; case DIV -> isLeftTerm ? DIVIDEND : DIVISOR; case MUL -> isLeftTerm ? MULTIPLICAND : MULTIPLIER; - default -> throw new UnsupportedOperationException("Not supported: " + operationType); + default -> throw new NoApplicableFilterException( + "Operation type is not supported to get arithmetic term type: " + operationType); }; } - private static Pair getLimitsForDivision(long left, long right, FilterOperationType type, - ArithmeticTermType termType) { + /** + * This method determines the possible range of values for a bin that is either the dividend or the divisor + * in a division operation, based on the values of the other operand and the filter operation type* + * + * @param left The value of the left operand + * @param right The value of the right operand + * @param type The type of the filter operation + * @param termType The {@link ArithmeticTermType} of the bin + * @return A {@link Pair} representing the lower and upper bounds of the range for the bin. + * A {@code null} value in the pair indicates no bound on that side + * @throws NoApplicableFilterException if division by zero occurs or the term type is unsupported + * @throws DslParseException if undefined division (0/0) occurs + */ + private static Pair getLimitsForDivisionForFilter(long left, long right, FilterOperationType type, + ArithmeticTermType termType) { // Prevent division by zero if (right == 0) { - throw new AerospikeDSLException("Cannot divide by zero"); + throw new NoApplicableFilterException("Cannot divide by zero"); } return switch (termType) { @@ -390,13 +400,25 @@ private static Pair getLimitsForDivision(long left, long right, Filt }; } - private static Pair LimitsForBinDividend(long left, long right, - FilterOperationType operationType) { + /** + * This method determines the possible range of values for a bin when it is the dividend in a division operation, + * based on the value of the divisor (right operand) and the filter operation type. + * + * @param left The value of the dividend (the bin's value) + * @param right The value of the divisor + * @param operationType The type of the filter operation + * @return A {@link Pair} representing the lower and upper bounds of the range for the bin. + * A {@code null} value in the pair indicates no bound on that side + * @throws DslParseException if undefined division (0/0) occurs or if the operation type is not supported + */ + private static Pair LimitsForBinDividend( + long left, long right, FilterOperationType operationType + ) { if (left > 0 && right > 0) { // both operands are positive return getLimitsForBinDividendWithLeftNumberPositive(operationType, left, right); } else if (left == 0 && right == 0) { - throw new AerospikeDSLException("Undefined division for 0 / 0"); + throw new DslParseException("Undefined division for 0 / 0"); } else if (left < 0 && right < 0) { // both operands are negative return getLimitsForBinDividendWithLeftNumberNegative(operationType, left, right); @@ -407,14 +429,24 @@ private static Pair LimitsForBinDividend(long left, long right, // left negative, right positive return getLimitsForBinDividendWithLeftNumberNegative(operationType, left, right); } else if (left != 0) { - throw new AerospikeDSLException("Division by zero is not allowed"); + throw new DslParseException("Division by zero is not allowed"); } else { return new Pair<>(null, null); } } - private static Pair getLimitsForBinDividendWithLeftNumberNegative(FilterOperationType operationType, - long left, long right) { + /** + * Calculates the range limits for a bin that is the dividend when the left number is negative. + * + * @param operationType The type of the filter operation + * @param left The value of the dividend + * @param right The value of the divisor + * @return A {@link Pair} representing the lower and upper bounds of the range for the bin + * @throws DslParseException if the operation type is not supported for division + */ + private static Pair getLimitsForBinDividendWithLeftNumberNegative( + FilterOperationType operationType, long left, long right + ) { return switch (operationType) { case GT: yield new Pair<>(Long.MIN_VALUE, left * right - 1); @@ -425,12 +457,22 @@ private static Pair getLimitsForBinDividendWithLeftNumberNegative(Fi case LTEQ: yield new Pair<>(left * right, Long.MAX_VALUE); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } - private static Pair getLimitsForBinDividendWithLeftNumberPositive(FilterOperationType operationType, - long left, long right) { + /** + * Calculates the range limits for a bin that is the dividend when the left number is positive. + * + * @param operationType The type of the filter operation + * @param left The value of the dividend + * @param right The value of the divisor + * @return A {@link Pair} representing the lower and upper bounds of the range for the bin + * @throws DslParseException if the operation type is not supported for division + */ + private static Pair getLimitsForBinDividendWithLeftNumberPositive( + FilterOperationType operationType, long left, long right + ) { return switch (operationType) { case GT: yield new Pair<>(left * right + 1, Long.MAX_VALUE); @@ -441,10 +483,20 @@ private static Pair getLimitsForBinDividendWithLeftNumberPositive(Fi case LTEQ: yield new Pair<>(Long.MIN_VALUE, left * right); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } + /** + * Calculates the range limits for a bin that is the divisor in a division operation. + * + * @param left The value of the dividend + * @param right The value of the divisor + * @param operationType The type of the filter operation + * @return A {@link Pair} representing the lower and upper bounds of the range for the bin. + * A {@code null} value in the pair indicates no bound on that side + * @throws DslParseException if division by zero occurs or if the operation type is not supported + */ private static Pair getLimitsForBinDivisor(long left, long right, FilterOperationType operationType) { if (left > 0 && right > 0) { // both operands are positive @@ -456,10 +508,10 @@ private static Pair getLimitsForBinDivisor(long left, long right, Fi case LT, LTEQ: yield new Pair<>(null, null); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } else if (left == 0 && right == 0) { - throw new AerospikeDSLException("Cannot divide by zero"); + throw new DslParseException("Cannot divide by zero"); } else if (left < 0 && right < 0) { // both operands are negative return switch (operationType) { @@ -470,7 +522,7 @@ private static Pair getLimitsForBinDivisor(long left, long right, Fi case LTEQ: yield new Pair<>(1L, left / right); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } else if (left > 0 && right < 0) { // left positive, right negative @@ -482,7 +534,7 @@ private static Pair getLimitsForBinDivisor(long left, long right, Fi case LTEQ: yield new Pair<>(left / right, -1L); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } else if (right > 0 && left < 0) { // right positive, left negative @@ -494,61 +546,259 @@ private static Pair getLimitsForBinDivisor(long left, long right, Fi case LT, LTEQ: yield new Pair<>(null, null); default: - throw new AerospikeDSLException("OperationType not supported for division: " + operationType); + throw new DslParseException("OperationType not supported for division: " + operationType); }; } else if (left != 0) { - throw new AerospikeDSLException("Division by zero is not allowed"); + throw new DslParseException("Division by zero is not allowed"); } else { return new Pair<>(null, null); } } + /** + * Generates a {@link Filter} for a division operation based on the calculated value range and operation type. + * + * @param binName The name of the bin to filter on + * @param value A {@link Pair} representing the lower and upper bounds of the acceptable range for the bin. + * A {@code null} value in the pair indicates no bound on that side + * @param type The type of the filter operation + * @return A {@link Filter} representing the condition + * @throws DslParseException if the operation type is not supported for generating a filter + */ private static Filter getFilterForDivOrFail(String binName, Pair value, FilterOperationType type) { // Based on the operation type, generate the appropriate filter range return switch (type) { case GT, GTEQ, LT, LTEQ -> Filter.range(binName, value.a, value.b); // Range from 1 to value - 1 case EQ -> Filter.equal(binName, value.a); // Exact match for equality case - default -> throw new AerospikeDSLException("OperationType not supported for division: " + type); + default -> throw new DslParseException("OperationType not supported for division: " + type); }; } + /** + * Creates a Filter based on a bin and an operand. + * + * @param bin The bin part + * @param operand The operand part + * @param type The filter operation type + * @return The appropriate Filter + * @throws NoApplicableFilterException if no appropriate filter can be created + */ private static Filter getFilter(BinPart bin, AbstractPart operand, FilterOperationType type) { + validateOperands(bin, operand); String binName = bin.getBinName(); + return switch (operand.getPartType()) { case INT_OPERAND -> { validateComparableTypes(bin.getExpType(), Exp.Type.INT); yield getFilterForArithmeticOrFail(binName, ((IntOperand) operand).getValue(), type); - } - case STRING_OPERAND -> { - if (type != EQ) throw new AerospikeDSLException("Operand type not supported"); - - if (bin.getExpType() != null && - bin.getExpType().equals(Exp.Type.BLOB)) { - // Base64 Blob - validateComparableTypes(bin.getExpType(), Exp.Type.BLOB); - String base64String = ((StringOperand) operand).getValue(); - byte[] value = Base64.getDecoder().decode(base64String); - yield Filter.equal(binName, value); - } else { - // String - validateComparableTypes(bin.getExpType(), Exp.Type.STRING); - yield Filter.equal(binName, ((StringOperand) operand).getValue()); - } - } - default -> - throw new AerospikeDSLException("Operand type not supported: %s".formatted(operand.getPartType())); + case STRING_OPERAND -> handleStringOperand(bin, binName, (StringOperand) operand, type); + default -> throw new NoApplicableFilterException( + "Operand type not supported: %s".formatted(operand.getPartType())); }; } + /** + * This method is used to generate a {@link Filter} when one of the operands is a {@link BinPart} + * and the other is a {@link StringOperand}. It currently only supports equality (`EQ`) comparisons. + * It handles both regular strings and base64 encoded BLOBs. + * + * @param bin The {@link BinPart} involved in the filter + * @param binName The name of the bin + * @param operand The {@link StringOperand} involved in the filter + * @param type The type of the filter operation (must be {@link FilterOperationType#EQ}) + * @return An Aerospike {@link Filter} for the string or blob comparison + * @throws NoApplicableFilterException if the filter operation type is not equality + * @throws DslParseException if type validation fails or base64 decoding fails + */ + private static Filter handleStringOperand(BinPart bin, String binName, StringOperand operand, + FilterOperationType type) { + if (type != FilterOperationType.EQ) { + throw new NoApplicableFilterException("Only equality comparison is supported for string operands"); + } + + // Handle BLOB type + if (bin.getExpType() != null && bin.getExpType().equals(Exp.Type.BLOB)) { + validateComparableTypes(bin.getExpType(), Exp.Type.BLOB); + byte[] value = Base64.getDecoder().decode(operand.getValue()); + return Filter.equal(binName, value); + } + + // Handle STRING type + validateComparableTypes(bin.getExpType(), Exp.Type.STRING); + return Filter.equal(binName, operand.getValue()); + } + + /** + * Creates a Filter based on two operands and a filter operation type. + * + * @param left The left operand + * @param right The right operand + * @param type The filter operation type + * @return The appropriate Filter, or null if no filter can be created + * @throws DslParseException if operands are invalid + */ + private static Filter getFilterOrFail(AbstractPart left, AbstractPart right, FilterOperationType type) { + validateOperands(left, right); + + // Handle bin operands + if (left.getPartType() == BIN_PART) { + return getFilter((BinPart) left, right, type); + } + + if (right.getPartType() == BIN_PART) { + return getFilter((BinPart) right, left, invertType(type)); + } + + // Handle expressions + if (left.getPartType() == EXPRESSION_CONTAINER) { + return handleExpressionOperand((ExpressionContainer) left, right, type); + } + + if (right.getPartType() == EXPRESSION_CONTAINER) { + return handleExpressionOperand((ExpressionContainer) right, left, type); + } + + return null; + } + + /** + * This method is used when one of the operands is an {@link ExpressionContainer}. + * It recursively processes the nested expression to determine if a filter can be generated from it in combination + * with the {@code otherOperand} and the overall {@code type} of the filter operation. + * + * @param expr The {@link ExpressionContainer} operand + * @param otherOperand The other operand in the filter condition + * @param type The type of the filter operation + * @return A {@link Filter} if one can be generated from the nested expression, otherwise {@code null} + * @throws DslParseException if operands within the nested expression are null + * @throws NoApplicableFilterException if the nested expression structure is not supported for filtering + */ + private static Filter handleExpressionOperand(ExpressionContainer expr, AbstractPart otherOperand, + FilterOperationType type) { + AbstractPart exprLeft = expr.getLeft(); + AbstractPart exprRight = expr.getRight(); + ExprPartsOperation operation = expr.getOperationType(); + + validateOperands(exprLeft, exprRight); + + return getFilterFromExpressionOrFail(exprLeft, exprRight, operation, otherOperand, type); + } + + /** + * Creates a secondary index {@link Filter} based on an expression and an external operand. + * The method examines the structure of the nested expression and attempts to generate a {@link Filter} + * by combining it with the {@code externalOperand} and the overall {@code type} of the filter operation. + * It specifically looks for cases where a bin is involved in an arithmetic expression with an external operand. + * + * @param exprLeft The left part of an expression + * @param exprRight The right part of an expression + * @param operationType The operation type of the expression + * @param externalOperand The operand outside the expression + * @param type The type of the overall filter operation + * @return A {@link Filter} if one can be generated, otherwise {@code null} + * @throws NoApplicableFilterException if the expression structure is not supported for filtering + */ + private static Filter getFilterFromExpressionOrFail(AbstractPart exprLeft, AbstractPart exprRight, + ExprPartsOperation operationType, + AbstractPart externalOperand, FilterOperationType type) { + // Handle bin on left side + if (exprLeft.getPartType() == BIN_PART) { + return handleBinArithmeticExpression((BinPart) exprLeft, exprRight, externalOperand, + operationType, type, true); + } + + // Handle bin on right side + if (exprRight.getPartType() == BIN_PART) { + return handleBinArithmeticExpression((BinPart) exprRight, exprLeft, externalOperand, + operationType, type, false); + } + + // Handle nested expressions + if (exprLeft.getPartType() == EXPRESSION_CONTAINER) { + return getFilterOrFail(exprLeft, exprRight, type); + } + + return null; + } + + /** + * This method is used when a secondary index {@link Filter} is being generated from an arithmetic + * expression where one operand is a bin and the other is a literal value. It enforces + * that both the literal operand and the external operand (from the overall filter condition) + * must be integers for secondary index filtering. + * It then calls{@link #applyFilterOperator} to generate the actual filter. + * + * @param bin The {@link BinPart} involved in the arithmetic expression + * @param operand The other operand within the arithmetic expression (expected to be an integer) + * @param externalOperand The operand from the overall filter condition (expected to be an integer) + * @param operation The type of the arithmetic operation + * @param type The type of the overall filter operation + * @param binOnLeft {@code true} if the bin is on the left side of the arithmetic operation, {@code false} otherwise + * @return A {@link Filter} for the arithmetic condition + * @throws NoApplicableFilterException if operands are not integers or if the operation is not supported + * @throws DslParseException if type validation fails + */ + private static Filter handleBinArithmeticExpression(BinPart bin, AbstractPart operand, + AbstractPart externalOperand, + ExprPartsOperation operation, + FilterOperationType type, boolean binOnLeft) { + // Only support integer arithmetic in filters + if (operand.getPartType() != INT_OPERAND || externalOperand.getPartType() != INT_OPERAND) { + throw new NoApplicableFilterException( + "Only integer operands are supported in arithmetic filter expressions"); + } + + validateComparableTypes(bin.getExpType(), Exp.Type.INT); + + IntOperand firstOperand = (IntOperand) operand; + IntOperand secondOperand = (IntOperand) externalOperand; + + return applyFilterOperator(bin.getBinName(), firstOperand, secondOperand, + operation, type, getFilterTermType(operation, binOnLeft)); + } + + /** + * Validates that both left and right operands are not null. + * This is a basic validation helper used to ensure that essential parts + * of an expression are present before attempting to process them. + * + * @param left The left {@link AbstractPart} + * @param right The right {@link AbstractPart} + * @throws DslParseException if either the left or right operand is null + */ + private static void validateOperands(AbstractPart left, AbstractPart right) { + if (left == null) { + throw new DslParseException("Left operand cannot be null"); + } + if (right == null) { + throw new DslParseException("Right operand cannot be null"); + } + } + + /** + * This method handles arithmetic operations between two integer operands and converts the result + * into an appropriate secondary index {@link Filter} based on the filter operation type. + * + * @param binName The name of the bin to apply the filter to + * @param leftOperand The left {@link IntOperand} + * @param rightOperand The right {@link IntOperand} + * @param operationType The type of the arithmetic operation + * @param type The type of the filter operation + * @param termType The {@link ArithmeticTermType} of the bin + * @return A secondary index {@link Filter} + * @throws NoApplicableFilterException if the operation is not supported by secondary index filters, + * division by zero occurs, or the calculated range is invalid + * @throws DslParseException if undefined division (0/0) occurs or other issues arise + */ private static Filter applyFilterOperator(String binName, IntOperand leftOperand, IntOperand rightOperand, - Expr.ExprPartsOperation operationType, FilterOperationType type, + ExprPartsOperation operationType, FilterOperationType type, ArithmeticTermType termType) { long leftValue = leftOperand.getValue(); long rightValue = rightOperand.getValue(); float value; if (Objects.requireNonNull(operationType) == ADD) { - value = rightValue - leftValue; + value = (float) rightValue - leftValue; } else if (operationType == SUB) { value = switch (termType) { case SUBTR -> rightValue + leftValue; @@ -559,17 +809,17 @@ private static Filter applyFilterOperator(String binName, IntOperand leftOperand default -> throw new IllegalStateException("Unexpected term type: " + termType); }; } else if (operationType == DIV) { - Pair valueForDiv = getLimitsForDivision(leftValue, rightValue, type, termType); + Pair valueForDiv = getLimitsForDivisionForFilter(leftValue, rightValue, type, termType); if (valueForDiv.a == null || valueForDiv.b == null || valueForDiv.a > valueForDiv.b || (valueForDiv.a == 0 && valueForDiv.b == 0)) { - throw new AerospikeDSLException("The operation is not supported by secondary index filter"); + throw new NoApplicableFilterException("The operation is not supported by secondary index filter"); } return getFilterForDivOrFail(binName, valueForDiv, type); } else if (operationType == MUL) { if (leftValue <= 0) { - if (leftValue == 0) throw new AerospikeDSLException("Cannot divide by zero"); + if (leftValue == 0) throw new NoApplicableFilterException("Cannot divide by zero"); type = invertType(type); } float val = (float) rightValue / leftValue; @@ -577,10 +827,18 @@ private static Filter applyFilterOperator(String binName, IntOperand leftOperand } else { throw new UnsupportedOperationException("Not supported"); } - return getFilterForArithmeticOrFail(binName, value, type); } + /** + * Generates a {@link Filter} for an arithmetic operation involving a bin and a value. + * + * @param binName The name of the bin to filter on + * @param value The calculated value from the arithmetic operation + * @param type The type of the filter operation + * @return A {@link Filter} representing the condition + * @throws NoApplicableFilterException if the operation type is not supported for secondary index filter + */ private static Filter getFilterForArithmeticOrFail(String binName, float value, FilterOperationType type) { return switch (type) { // "$.intBin1 > 100" and "100 < $.intBin1" represent the same Filter @@ -589,10 +847,17 @@ private static Filter getFilterForArithmeticOrFail(String binName, float value, case LT -> Filter.range(binName, Long.MIN_VALUE, getClosestLongToTheLeft(value)); case LTEQ -> Filter.range(binName, Long.MIN_VALUE, (long) value); case EQ -> Filter.equal(binName, (long) value); - default -> throw new AerospikeDSLException("The operation is not supported by secondary index filter"); + default -> + throw new NoApplicableFilterException("The operation is not supported by secondary index filter"); }; } + /** + * Finds the closest long integer to the left of a given float value. + * + * @param value The float value + * @return The closest long integer to the left + */ private static long getClosestLongToTheLeft(float value) { // Get the largest integer less than or equal to the float long flooredValue = (long) Math.floor(value); @@ -601,10 +866,15 @@ private static long getClosestLongToTheLeft(float value) { if (value == flooredValue) { return flooredValue - 1; } - return flooredValue; } + /** + * Finds the closest long integer to the right of a given float value. + * + * @param value The float value + * @return The closest long integer to the right + */ private static long getClosestLongToTheRight(float value) { // Get the smallest integer greater than or equal to the float long ceiledValue = (long) Math.ceil(value); @@ -617,13 +887,442 @@ private static long getClosestLongToTheRight(float value) { return ceiledValue; } - private FilterOperationType invertType(FilterOperationType type) { + /** + * This method provides the inverse operation for {@link FilterOperationType} comparison types. + * For other Filter operation types, it returns the original type. + * + * @param type Filter operation type to invert + * @return The inverted {@link FilterOperationType} + */ + private static FilterOperationType invertType(FilterOperationType type) { return switch (type) { - case GT -> LT; - case GTEQ -> LTEQ; - case LT -> GT; - case LTEQ -> GTEQ; + case GT -> FilterOperationType.LT; + case GTEQ -> FilterOperationType.LTEQ; + case LT -> FilterOperationType.GT; + case LTEQ -> FilterOperationType.GTEQ; default -> type; }; } + + /** + * Builds a secondary index {@link Filter} and a filter {@link Exp} for a given {@link ExpressionContainer}. + * This is the main entry point for enriching the parsed expression tree with query filters. + * + * @param expr The {@link ExpressionContainer} representing the expression tree + * @param indexes A map of available secondary indexes, keyed by bin name + * @return The updated {@link ExpressionContainer} with the generated {@link Filter} and {@link Exp}. + * Either of them can be null if there is no suitable filter + */ + public static AbstractPart buildExpr(ExpressionContainer expr, Map> indexes) { + Filter secondaryIndexFilter = null; + try { + secondaryIndexFilter = getSIFilter(expr, indexes); + } catch (NoApplicableFilterException ignored) { + } + expr.setFilter(secondaryIndexFilter); + + Exp exp = getFilterExp(expr); + expr.setExp(exp); + return expr; + } + + /** + * Returns the {@link Exp} generated for a given {@link ExpressionContainer}. + * + * @param expr The input {@link ExpressionContainer} + * @return The corresponding {@link Exp}, or {@code null} if a secondary index filter was applied + * or if there is no suitable filter + */ + private static Exp getFilterExp(ExpressionContainer expr) { + // Skip the expression already used in creating secondary index Filter + if (expr.hasSecondaryIndexFilter()) return null; + + return switch (expr.getOperationType()) { + case WITH_STRUCTURE -> withStructureToExp(expr); + case WHEN_STRUCTURE -> whenStructureToExp(expr); + case EXCLUSIVE_STRUCTURE -> exclStructureToExp(expr); + default -> processExpression(expr); + }; + } + + /** + * Generates filter {@link Exp} for a WITH structure {@link ExpressionContainer}. + * + * @param expr The {@link ExpressionContainer} representing WITH structure + * @return The resulting {@link Exp} expression + */ + private static Exp withStructureToExp(ExpressionContainer expr) { + List expressions = new ArrayList<>(); + WithStructure withOperandsList = (WithStructure) expr.getLeft(); // extract unary Expr operand + List operands = withOperandsList.getOperands(); + for (WithOperand withOperand : operands) { + if (!withOperand.isLastPart()) { + expressions.add(Exp.def(withOperand.getString(), getExp(withOperand.getPart()))); + } else { + // the last expression is the action (described after "do") + expressions.add(getExp(withOperand.getPart())); + } + } + return Exp.let(expressions.toArray(new Exp[0])); + } + + /** + * Generates filter {@link Exp} for a WHEN structure {@link ExpressionContainer}. + * + * @param expr The {@link ExpressionContainer} representing WHEN structure + * @return The resulting {@link Exp} expression + */ + private static Exp whenStructureToExp(ExpressionContainer expr) { + List expressions = new ArrayList<>(); + WhenStructure whenOperandsList = (WhenStructure) expr.getLeft(); // extract unary Expr operand + List operands = whenOperandsList.getOperands(); + for (AbstractPart part : operands) { + expressions.add(getExp(part)); + } + return Exp.cond(expressions.toArray(new Exp[0])); + } + + /** + * Generates filter {@link Exp} for an EXCLUSIVE structure {@link ExpressionContainer}. + * + * @param expr The {@link ExpressionContainer} representing EXCLUSIVE structure + * @return The resulting {@link Exp} expression + */ + private static Exp exclStructureToExp(ExpressionContainer expr) { + List expressions = new ArrayList<>(); + ExclusiveStructure whenOperandsList = (ExclusiveStructure) expr.getLeft(); // extract unary Expr operand + List operands = whenOperandsList.getOperands(); + for (ExpressionContainer part : operands) { + expressions.add(getExp(part)); + } + return Exp.exclusive(expressions.toArray(new Exp[0])); + } + + /** + * Processes an {@link ExpressionContainer} to generate the corresponding Exp. + * + * @param expr The expression to process + * @return The processed Exp + * @throws DslParseException if left or right operands are null in a binary expression + */ + private static Exp processExpression(ExpressionContainer expr) { + // For unary expressions + if (expr.isUnary()) { + Exp operandExp = processOperand(expr.getLeft()); + if (operandExp == null) return null; + + UnaryOperator operator = getUnaryExpOperator(expr.getOperationType()); + return operator.apply(operandExp); + } + + // For binary expressions + AbstractPart left = expr.getLeft(); + AbstractPart right = expr.getRight(); + if (left == null) { + throw new DslParseException("Unable to parse left operand"); + } + if (right == null) { + throw new DslParseException("Unable to parse right operand"); + } + + // Process operands + Exp leftExp = processOperand(left); + Exp rightExp = processOperand(right); + + // Special handling for BIN_PART + if (left.getPartType() == BIN_PART) { + return getExpLeftBinTypeComparison((BinPart) left, right, getExpOperator(expr.getOperationType())); + } else if (right.getPartType() == BIN_PART) { + return getExpRightBinTypeComparison(left, (BinPart) right, getExpOperator(expr.getOperationType())); + } + + // Special handling for AND operation + if (expr.getOperationType() == AND) { + if (leftExp == null) return rightExp; + if (rightExp == null) return leftExp; + } + + // Apply binary operator + BinaryOperator operator = getExpOperator(expr.getOperationType()); + return operator.apply(leftExp, rightExp); + } + + /** + * Processes an expression operand to generate its corresponding Aerospike {@link Exp}. + * If the operand is an {@link ExpressionContainer}, it recursively calls {@link #getFilterExp(ExpressionContainer)} + * to get the nested expression's {@link Exp}. Otherwise, it retrieves the {@link Exp} from the part itself. + * The generated {@link Exp} is set back on the {@link AbstractPart}. + * + * @param part The operand to process + * @return The processed Exp, or {@code null} if the part is null or represents + * an expression container that resulted in a null Exp + */ + private static Exp processOperand(AbstractPart part) { + if (part == null) return null; + + Exp exp; + if (part.getPartType() == EXPRESSION_CONTAINER) { + exp = getFilterExp((ExpressionContainer) part); + } else { + exp = part.getExp(); + } + part.setExp(exp); + return exp; + } + + /** + * This method that retrieves the {@link Exp} associated with an {@link AbstractPart}. + * If the part is an {@link ExpressionContainer}, it calls {@link #getFilterExp(ExpressionContainer)} + * to get the nested expression's {@link Exp}. Otherwise, it returns the {@link Exp} stored + * directly in the {@link AbstractPart}. + * + * @param part The {@link AbstractPart} for which to get the {@link Exp} + * @return The corresponding {@link Exp} or {@code null} + */ + private static Exp getExp(AbstractPart part) { + if (part.getPartType() == EXPRESSION_CONTAINER) { + return getFilterExp((ExpressionContainer) part); + } + return part.getExp(); + } + + /** + * Attempts to generate a secondary index {@link Filter} for a given {@link ExpressionContainer}. + * If the expression is not an OR operation (which is not supported + * for secondary index filters), the method attempts to find the most suitable + * expression within the tree to apply a filter based on index availability and cardinality. + * + * @param expr The {@link ExpressionContainer} representing the expression tree + * @param indexes A map of available secondary indexes, keyed by bin name + * @return A secondary index {@link Filter}, or {@code null} if no applicable filter can be generated + * @throws NoApplicableFilterException if the expression operation type is not supported + */ + private static Filter getSIFilter(ExpressionContainer expr, Map> indexes) { + // If it is an OR query + if (expr.getOperationType() == OR) return null; + + ExpressionContainer chosenExpr = chooseExprForFilter(expr, indexes); + if (chosenExpr == null) return null; + + return getFilterOrFail( + chosenExpr.getLeft(), + chosenExpr.getRight(), + getFilterOperation(chosenExpr.getOperationType()) + ); + } + + /** + * Chooses the most suitable {@link ExpressionContainer} within a tree to apply a secondary index filter. + * Identifies all potential expressions within the tree that could + * utilize a secondary index. Selects the expression associated with the secondary index + * having the largest cardinality (highest ratio of unique + * bin values). If multiple expressions have the same largest cardinality, it + * chooses alphabetically based on the bin name. The chosen expression is marked + * as having a secondary index filter applied. + * + * @param exprContainer The root {@link ExpressionContainer} of the expression tree + * @param indexes A map of available secondary indexes, keyed by bin name + * @return The chosen {@link ExpressionContainer} for secondary index filtering, + * or {@code null} if no suitable expression is found + */ + private static ExpressionContainer chooseExprForFilter(ExpressionContainer exprContainer, + Map> indexes) { + if (indexes == null || indexes.isEmpty()) return null; + + Map> exprsPerCardinality = + getExpressionsPerCardinality(exprContainer, indexes); + + // Find the entry with the largest key (cardinality) + Map> largestCardinalityMap = exprsPerCardinality.entrySet().stream() + .max(Map.Entry.comparingByKey()) + .map(entry -> Map.of(entry.getKey(), entry.getValue())) + .orElse(Collections.emptyMap()); + + List largestCardinalityExprs; + if (largestCardinalityMap.isEmpty()) return null; + largestCardinalityExprs = largestCardinalityMap.values().iterator().next(); + + ExpressionContainer chosenExpr; + if (largestCardinalityExprs.size() > 1) { + // Choosing alphabetically from a number of expressions + chosenExpr = largestCardinalityExprs.stream() + .min(Comparator.comparing(expr -> getBinPart(expr, 1).getBinName())) + .orElse(null); + chosenExpr.hasSecondaryIndexFilter(true); + return chosenExpr; + } + + // There is only one expression with the largest cardinality + chosenExpr = largestCardinalityExprs.get(0); + chosenExpr.hasSecondaryIndexFilter(true); + return chosenExpr; + } + + /** + * Collects all {@link ExpressionContainer}s within an expression tree that + * correspond to a bin with a secondary index, grouped by the index's cardinality. + * + * @param exprContainer The root {@link ExpressionContainer} of the expression tree + * @param indexes A map of available secondary indexes, keyed by bin name + * @return A map where keys are secondary index cardinalities (bin values ratio) + * and values are lists of {@link ExpressionContainer}s associated with bins + * having that cardinality + */ + private static Map> getExpressionsPerCardinality( + ExpressionContainer exprContainer, Map> indexes + ) { + Map> exprsPerCardinality = new HashMap<>(); + final BinPart[] binPartPrev = {null}; + Consumer exprsPerCardinalityCollector = part -> { + if (part.getPartType() == EXPRESSION_CONTAINER) { + ExpressionContainer expr = (ExpressionContainer) part; + BinPart binPart = getBinPart(expr, 2); + + if (binPart == null) return; // no bin found + if (!binPart.equals(binPartPrev[0])) { + binPartPrev[0] = binPart; + } else { + return; // the same bin + } + + updateExpressionsPerCardinality(exprsPerCardinality, expr, binPart, indexes); + } + }; + traverseTree(exprContainer, exprsPerCardinalityCollector, null); + return exprsPerCardinality; + } + + /** + * Updates a map of expression containers grouped by the cardinality of the indexes associated with an + * expression container's bin part. + * This method iterates through the indexes related to the provided {@code binPart}. + * For each index that matches the expression type of the {@code binPart}, it adds the given {@code expr} + * to the {@code exprsPerCardinality} map. + * + * @param exprsPerCardinality A map where keys are integer ratios (representing index cardinality) + * and values are lists of {@link ExpressionContainer} objects. + * The map is updated by this method + * @param expr The {@link ExpressionContainer} to be added to the appropriate list + * within {@code exprsPerCardinality} + * @param binPart The {@link BinPart} associated with the expression, used to find + * relevant indexes and determine the expression type + * @param indexes A map where keys are bin names and values are lists of + * {@link Index} objects associated with that bin + */ + private static void updateExpressionsPerCardinality(Map> exprsPerCardinality, + ExpressionContainer expr, BinPart binPart, + Map> indexes) { + List indexesByBin = indexes.get(binPart.getBinName()); + if (indexesByBin == null || indexesByBin.isEmpty()) return; + + for (Index idx : indexesByBin) { + // Iterate over all indexes for the same bin + if (expTypeToIndexType.get(binPart.getExpType()) == idx.getIndexType()) { + List exprsList = exprsPerCardinality.get(idx.getBinValuesRatio()); + if (exprsList != null) { + exprsList.add(expr); + } else { + exprsList = new ArrayList<>(); + exprsList.add(expr); + } + exprsPerCardinality.put(idx.getBinValuesRatio(), exprsList); + } + } + } + + /** + * The method traverses the expression tree starting from the given {@link ExpressionContainer}, + * searching for a {@link BinPart}. It limits the search depth and stops + * traversing a branch if a logical expression (AND / OR) is found. + * + * @param expr The {@link ExpressionContainer} to start searching from + * @param depth The maximum depth to traverse + * @return The first {@link BinPart} found within the specified depth, or {@code null} if none is found + */ + private static BinPart getBinPart(ExpressionContainer expr, int depth) { + final BinPart[] singleBinPartArray = {null}; + Consumer binPartRetriever = part -> { + if (part.getPartType() == BIN_PART) { + singleBinPartArray[0] = (BinPart) part; + } + }; + Predicate stopOnLogicalExpr = part -> { + if (part.getPartType() == EXPRESSION_CONTAINER) { + ExpressionContainer logicalExpr = (ExpressionContainer) part; + return logicalExpr.getOperationType() == AND || logicalExpr.getOperationType() == OR; + } + return false; + }; + + traverseTree(expr, binPartRetriever, depth, stopOnLogicalExpr); + return singleBinPartArray[0]; + } + + /** + * Traverses the AbstractPart nodes tree and applies the visitor function to each node. + * Uses a pre-order traversal (top-down, root-left-right). + * The visitor function can be used to modify the node's state or to extract information. + * + * @param part The current node being visited (start with the root) + * @param visitor The function to apply to each AbstractPart node + * @param stopCondition The condition that causes stop of traversing + */ + public static void traverseTree(AbstractPart part, Consumer visitor, + Predicate stopCondition) { + traverseTree(part, visitor, Integer.MAX_VALUE, stopCondition); + } + + /** + * Traverses the AbstractPart nodes tree and applies the visitor function to each node with limited depth. + * Uses a pre-order traversal (root-left-right). + * The visitor function can be used to modify the node's state or to extract information. + * + * @param part The current node being visited (start with the root) + * @param visitor The function to apply to each AbstractPart node + * @param depth The depth to limit traversing at + */ + public static void traverseTree(AbstractPart part, Consumer visitor, int depth, + Predicate stopCondition) { + if (part == null) return; + + // Stop if the depth limit is reached + if (depth < 0) { + return; + } + + // Stop traversing this branch if the stop condition is met + if (stopCondition != null && stopCondition.test(part)) { + return; + } + + visitor.accept(part); + + if (part.getPartType() == EXPRESSION_CONTAINER && depth > 0) { + ExpressionContainer container = (ExpressionContainer) part; + traverseTree(container.getLeft(), visitor, depth - 1, stopCondition); + traverseTree(container.getRight(), visitor, depth - 1, stopCondition); + } + } + + protected enum FilterOperationType { + GT, + GTEQ, + LT, + LTEQ, + EQ, + NOTEQ + } + + protected enum ArithmeticTermType { + ADDEND, + SUBTR, + MIN, + DIFFERENCE, + DIVIDEND, + DIVISOR, + QUOTIENT, + MULTIPLICAND, + MULTIPLIER, + PRODUCT + } } diff --git a/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java index 7876e58..ec13080 100644 --- a/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java @@ -1,124 +1,124 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ArithmeticExpressionsTests { @Test void add() { - parseExpressionAndCompare("($.apples + $.bananas) > 10", + parseExpAndCompare("($.apples + $.bananas) > 10", Exp.gt(Exp.add(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples + 5) > 10", + parseExpAndCompare("($.apples + 5) > 10", Exp.gt(Exp.add(Exp.intBin("apples"), Exp.val(5)), Exp.val(10))); - parseExpressionAndCompare("(5 + $.bananas) > 10", + parseExpAndCompare("(5 + $.bananas) > 10", Exp.gt(Exp.add(Exp.val(5), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("(5.2 + $.bananas) > 10.2", + parseExpAndCompare("(5.2 + $.bananas) > 10.2", Exp.gt(Exp.add(Exp.val(5.2), Exp.floatBin("bananas")), Exp.val(10.2))); } @Test void sub() { - parseExpressionAndCompare("($.apples - $.bananas) == 10", + parseExpAndCompare("($.apples - $.bananas) == 10", Exp.eq(Exp.sub(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples - 5) == 10", + parseExpAndCompare("($.apples - 5) == 10", Exp.eq(Exp.sub(Exp.intBin("apples"), Exp.val(5)), Exp.val(10))); - parseExpressionAndCompare("(15 - $.bananas) == 10", + parseExpAndCompare("(15 - $.bananas) == 10", Exp.eq(Exp.sub(Exp.val(15), Exp.intBin("bananas")), Exp.val(10))); } @Test void mul() { - parseExpressionAndCompare("($.apples * $.bananas) != 10", + parseExpAndCompare("($.apples * $.bananas) != 10", Exp.ne(Exp.mul(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples * 7) != 10", + parseExpAndCompare("($.apples * 7) != 10", Exp.ne(Exp.mul(Exp.intBin("apples"), Exp.val(7)), Exp.val(10))); - parseExpressionAndCompare("(3 * $.bananas) != 10", + parseExpAndCompare("(3 * $.bananas) != 10", Exp.ne(Exp.mul(Exp.val(3), Exp.intBin("bananas")), Exp.val(10))); } @Test void div() { - parseExpressionAndCompare("($.apples / $.bananas) <= 10", + parseExpAndCompare("($.apples / $.bananas) <= 10", Exp.le(Exp.div(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples / 5) <= 10", + parseExpAndCompare("($.apples / 5) <= 10", Exp.le(Exp.div(Exp.intBin("apples"), Exp.val(5)), Exp.val(10))); - parseExpressionAndCompare("(33 / $.bananas) <= 10", + parseExpAndCompare("(33 / $.bananas) <= 10", Exp.le(Exp.div(Exp.val(33), Exp.intBin("bananas")), Exp.val(10))); // Exp should be constructed and equal and therefore the test is passing // but when actually triggered server will throw divide by zero exception - parseExpressionAndCompare("($.apples / 0) <= 10", + parseExpAndCompare("($.apples / 0) <= 10", Exp.le(Exp.div(Exp.intBin("apples"), Exp.val(0)), Exp.val(10))); } @Test void mod() { - parseExpressionAndCompare("($.apples % $.bananas) != 10", + parseExpAndCompare("($.apples % $.bananas) != 10", Exp.ne(Exp.mod(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples % 7) != 10", + parseExpAndCompare("($.apples % 7) != 10", Exp.ne(Exp.mod(Exp.intBin("apples"), Exp.val(7)), Exp.val(10))); - parseExpressionAndCompare("(3 % $.bananas) != 10", + parseExpAndCompare("(3 % $.bananas) != 10", Exp.ne(Exp.mod(Exp.val(3), Exp.intBin("bananas")), Exp.val(10))); } @Test void intAnd() { - parseExpressionAndCompare("($.apples & $.bananas) != 10", - Exp.ne(Exp.intAnd(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples & 7) != 10", +// parseExpAndCompare("($.apples & $.bananas) != 10", +// Exp.ne(Exp.intAnd(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); + parseExpAndCompare("($.apples & 7) != 10", Exp.ne(Exp.intAnd(Exp.intBin("apples"), Exp.val(7)), Exp.val(10))); - parseExpressionAndCompare("(3 & $.bananas) != 10", + parseExpAndCompare("(3 & $.bananas) != 10", Exp.ne(Exp.intAnd(Exp.val(3), Exp.intBin("bananas")), Exp.val(10))); } @Test void intOr() { - parseExpressionAndCompare("($.apples | $.bananas) != 10", + parseExpAndCompare("($.apples | $.bananas) != 10", Exp.ne(Exp.intOr(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples | 7) != 10", + parseExpAndCompare("($.apples | 7) != 10", Exp.ne(Exp.intOr(Exp.intBin("apples"), Exp.val(7)), Exp.val(10))); - parseExpressionAndCompare("(3 | $.bananas) != 10", + parseExpAndCompare("(3 | $.bananas) != 10", Exp.ne(Exp.intOr(Exp.val(3), Exp.intBin("bananas")), Exp.val(10))); } @Test void intXor() { - parseExpressionAndCompare("($.apples ^ $.bananas) != 10", + parseExpAndCompare("($.apples ^ $.bananas) != 10", Exp.ne(Exp.intXor(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(10))); - parseExpressionAndCompare("($.apples ^ 7) != 10", + parseExpAndCompare("($.apples ^ 7) != 10", Exp.ne(Exp.intXor(Exp.intBin("apples"), Exp.val(7)), Exp.val(10))); - parseExpressionAndCompare("(3 ^ $.bananas) != 10", + parseExpAndCompare("(3 ^ $.bananas) != 10", Exp.ne(Exp.intXor(Exp.val(3), Exp.intBin("bananas")), Exp.val(10))); } @Test void intNot() { - parseExpressionAndCompare("(~$.apples) != 10", + parseExpAndCompare("(~$.apples) != 10", Exp.ne(Exp.intNot(Exp.intBin("apples")), Exp.val(10))); } @Test void intLShift() { - parseExpressionAndCompare("$.visits << 1", + parseExpAndCompare("$.visits << 1", Exp.lshift(Exp.intBin("visits"), Exp.val(1))); } @Test void intRShift() { - parseExpressionAndCompare("(($.flags >> 6) & 1) == 1", + parseExpAndCompare("(($.flags >> 6) & 1) == 1", Exp.eq(Exp.intAnd(Exp.rshift(Exp.intBin("flags"), Exp.val(6)), Exp.val(1)), Exp.val(1))); } @Test void negativeArithmetic() { - assertThatThrownBy(() -> parseExpression("($.apples.get(type: STRING) + 5) > 10")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("($.apples.get(type: STRING) + 5) > 10")) + .isInstanceOf(DslParseException.class) .hasMessageContaining("Cannot compare STRING to INT"); // TODO: should throw an exception (cannot use arithmetic operations on Strings) diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index eb0d85a..1342b8b 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -1,65 +1,65 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; class BinExpressionsTests { @Test void binGT() { - parseExpressionAndCompare("$.intBin1 > 100", Exp.gt(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.stringBin1 > 'text'", Exp.gt(Exp.stringBin("stringBin1"), Exp.val("text"))); - parseExpressionAndCompare("$.stringBin1 > \"text\"", Exp.gt(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.intBin1 > 100", Exp.gt(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.stringBin1 > 'text'", Exp.gt(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.stringBin1 > \"text\"", Exp.gt(Exp.stringBin("stringBin1"), Exp.val("text"))); - parseExpressionAndCompare("100 < $.intBin1", Exp.lt(Exp.val(100), Exp.intBin("intBin1"))); - parseExpressionAndCompare("'text' < $.stringBin1", Exp.lt(Exp.val("text"), Exp.stringBin("stringBin1"))); - parseExpressionAndCompare("\"text\" < $.stringBin1", Exp.lt(Exp.val("text"), Exp.stringBin("stringBin1"))); + parseExpAndCompare("100 < $.intBin1", Exp.lt(Exp.val(100), Exp.intBin("intBin1"))); + parseExpAndCompare("'text' < $.stringBin1", Exp.lt(Exp.val("text"), Exp.stringBin("stringBin1"))); + parseExpAndCompare("\"text\" < $.stringBin1", Exp.lt(Exp.val("text"), Exp.stringBin("stringBin1"))); } @Test void binGE() { - parseExpressionAndCompare("$.intBin1 >= 100", Exp.ge(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.stringBin1 >= 'text'", Exp.ge(Exp.stringBin("stringBin1"), Exp.val("text"))); - parseExpressionAndCompare("$.stringBin1 >= \"text\"", Exp.ge(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.intBin1 >= 100", Exp.ge(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.stringBin1 >= 'text'", Exp.ge(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.stringBin1 >= \"text\"", Exp.ge(Exp.stringBin("stringBin1"), Exp.val("text"))); } @Test void binLT() { - parseExpressionAndCompare("$.intBin1 < 100", Exp.lt(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.stringBin1 < 'text'", Exp.lt(Exp.stringBin("stringBin1"), Exp.val("text"))); - parseExpressionAndCompare("$.stringBin1 < \"text\"", Exp.lt(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.intBin1 < 100", Exp.lt(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.stringBin1 < 'text'", Exp.lt(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.stringBin1 < \"text\"", Exp.lt(Exp.stringBin("stringBin1"), Exp.val("text"))); } @Test void binLE() { - parseExpressionAndCompare("$.intBin1 <= 100", Exp.le(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.stringBin1 <= 'text'", Exp.le(Exp.stringBin("stringBin1"), Exp.val("text"))); - parseExpressionAndCompare("$.stringBin1 <= \"text\"", Exp.le(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.intBin1 <= 100", Exp.le(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.stringBin1 <= 'text'", Exp.le(Exp.stringBin("stringBin1"), Exp.val("text"))); + parseExpAndCompare("$.stringBin1 <= \"text\"", Exp.le(Exp.stringBin("stringBin1"), Exp.val("text"))); } @Test void binEquals() { - parseExpressionAndCompare("$.intBin1 == 100", Exp.eq(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.strBin == \"yes\"", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); - parseExpressionAndCompare("$.strBin == 'yes'", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); + parseExpAndCompare("$.intBin1 == 100", Exp.eq(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.strBin == \"yes\"", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); + parseExpAndCompare("$.strBin == 'yes'", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); } @Test void binNotEquals() { - parseExpressionAndCompare("$.intBin1 != 100", Exp.ne(Exp.intBin("intBin1"), Exp.val(100))); - parseExpressionAndCompare("$.strBin != \"yes\"", Exp.ne(Exp.stringBin("strBin"), Exp.val("yes"))); - parseExpressionAndCompare("$.strBin != 'yes'", Exp.ne(Exp.stringBin("strBin"), Exp.val("yes"))); + parseExpAndCompare("$.intBin1 != 100", Exp.ne(Exp.intBin("intBin1"), Exp.val(100))); + parseExpAndCompare("$.strBin != \"yes\"", Exp.ne(Exp.stringBin("strBin"), Exp.val("yes"))); + parseExpAndCompare("$.strBin != 'yes'", Exp.ne(Exp.stringBin("strBin"), Exp.val("yes"))); } @Test void negativeStringBinEquals() { - assertThatThrownBy(() -> parseExpression("$.strBin == yes")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("$.strBin == yes")) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse right operand"); } diff --git a/src/test/java/com/aerospike/dsl/expression/CastingTests.java b/src/test/java/com/aerospike/dsl/expression/CastingTests.java index c398386..398664e 100644 --- a/src/test/java/com/aerospike/dsl/expression/CastingTests.java +++ b/src/test/java/com/aerospike/dsl/expression/CastingTests.java @@ -1,11 +1,11 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class CastingTests { @@ -14,20 +14,20 @@ public class CastingTests { void floatToIntComparison() { Exp expectedExp = Exp.gt(Exp.intBin("intBin1"), Exp.intBin("floatBin1")); // Int is default - parseExpressionAndCompare("$.intBin1 > $.floatBin1.asInt()", expectedExp); - parseExpressionAndCompare("$.intBin1.get(type: INT) > $.floatBin1.asInt()", expectedExp); + parseExpAndCompare("$.intBin1 > $.floatBin1.asInt()", expectedExp); + parseExpAndCompare("$.intBin1.get(type: INT) > $.floatBin1.asInt()", expectedExp); } @Test void intToFloatComparison() { - parseExpressionAndCompare("$.intBin1.get(type: INT) > $.intBin2.asFloat()", + parseExpAndCompare("$.intBin1.get(type: INT) > $.intBin2.asFloat()", Exp.gt(Exp.intBin("intBin1"), Exp.floatBin("intBin2"))); } @Test void negativeInvalidTypesComparison() { - assertThatThrownBy(() -> parseExpression("$.stringBin1.get(type: STRING) > $.intBin2.asFloat()")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("$.stringBin1.get(type: STRING) > $.intBin2.asFloat()")) + .isInstanceOf(DslParseException.class) .hasMessageContaining("Cannot compare STRING to FLOAT"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java index 14b7c94..5f89e1b 100644 --- a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java @@ -3,7 +3,7 @@ import com.aerospike.client.exp.Exp; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; public class ControlStructuresTests { @@ -17,10 +17,10 @@ void whenWithASingleDeclaration() { Exp.val("other") ); - parseExpressionAndCompare("when ($.who == 1 => \"bob\", default => \"other\")", + parseExpAndCompare("when ($.who == 1 => \"bob\", default => \"other\")", expected); // different spacing style - parseExpressionAndCompare("when($.who == 1 => \"bob\", default => \"other\")", + parseExpAndCompare("when($.who == 1 => \"bob\", default => \"other\")", expected); } @@ -40,7 +40,7 @@ void whenUsingTheResult() { // Implicit detect as String //translateAndCompare("$.stringBin1 == (when ($.who == 1 => \"bob\", default => \"other\"))", // expected); - parseExpressionAndCompare("$.stringBin1.get(type: STRING) == (when ($.who == 1 => \"bob\", default => \"other\"))", + parseExpAndCompare("$.stringBin1.get(type: STRING) == (when ($.who == 1 => \"bob\", default => \"other\"))", expected); } @@ -58,7 +58,7 @@ void whenWithMultipleDeclarations() { Exp.val("other") ); - parseExpressionAndCompare("when ($.who == 1 => \"bob\", $.who == 2 => \"fred\", default => \"other\")", + parseExpAndCompare("when ($.who == 1 => \"bob\", $.who == 2 => \"fred\", default => \"other\")", expected); } @@ -74,10 +74,10 @@ void withMultipleVariablesDefinitionAndUsage() { Exp.add(Exp.var("x"), Exp.var("y")) ); - parseExpressionAndCompare("with (x = 1, y = ${x} + 1) do (${x} + ${y})", + parseExpAndCompare("with (x = 1, y = ${x} + 1) do (${x} + ${y})", expected); // different spacing style - parseExpressionAndCompare("with(x = 1, y = ${x}+1) do(${x}+${y})", + parseExpAndCompare("with(x = 1, y = ${x}+1) do(${x}+${y})", expected); } } diff --git a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java index 7774aa5..d74a480 100644 --- a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java @@ -1,15 +1,15 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; import java.util.Base64; import java.util.List; import java.util.TreeMap; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; // Explicit types tests, list and map explicit types are tested in their own test classes @@ -17,35 +17,35 @@ public class ExplicitTypesTests { @Test void integerComparison() { - parseExpressionAndCompare("$.intBin1.get(type: INT) > 5", + parseExpAndCompare("$.intBin1.get(type: INT) > 5", Exp.gt(Exp.intBin("intBin1"), Exp.val(5))); - parseExpressionAndCompare("5 < $.intBin1.get(type: INT)", + parseExpAndCompare("5 < $.intBin1.get(type: INT)", Exp.lt(Exp.val(5), Exp.intBin("intBin1"))); } @Test void stringComparison() { // A String constant must contain quoted Strings - parseExpressionAndCompare("$.stringBin1.get(type: STRING) == \"yes\"", + parseExpAndCompare("$.stringBin1.get(type: STRING) == \"yes\"", Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes"))); - parseExpressionAndCompare("$.stringBin1.get(type: STRING) == 'yes'", + parseExpAndCompare("$.stringBin1.get(type: STRING) == 'yes'", Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes"))); - parseExpressionAndCompare("\"yes\" == $.stringBin1.get(type: STRING)", + parseExpAndCompare("\"yes\" == $.stringBin1.get(type: STRING)", Exp.eq(Exp.val("yes"), Exp.stringBin("stringBin1"))); - parseExpressionAndCompare("'yes' == $.stringBin1.get(type: STRING)", + parseExpAndCompare("'yes' == $.stringBin1.get(type: STRING)", Exp.eq(Exp.val("yes"), Exp.stringBin("stringBin1"))); } @Test void stringComparisonNegativeTest() { // A String constant must be quoted - assertThatThrownBy(() -> parseExpressionAndCompare("$.stringBin1.get(type: STRING) == yes", + assertThatThrownBy(() -> parseExpAndCompare("$.stringBin1.get(type: STRING) == yes", Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes")))) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse right operand"); } @@ -53,65 +53,65 @@ void stringComparisonNegativeTest() { void blobComparison() { byte[] data = new byte[]{1, 2, 3}; String encodedString = Base64.getEncoder().encodeToString(data); - parseExpressionAndCompare("$.blobBin1.get(type: BLOB) == \"" + encodedString + "\"", + parseExpAndCompare("$.blobBin1.get(type: BLOB) == \"" + encodedString + "\"", Exp.eq(Exp.blobBin("blobBin1"), Exp.val(data))); // Reverse - parseExpressionAndCompare("\"" + encodedString + "\"" + " == $.blobBin1.get(type: BLOB)", + parseExpAndCompare("\"" + encodedString + "\"" + " == $.blobBin1.get(type: BLOB)", Exp.eq(Exp.val(data), Exp.blobBin("blobBin1"))); } @Test void floatComparison() { - parseExpressionAndCompare("$.floatBin1.get(type: FLOAT) == 1.5", + parseExpAndCompare("$.floatBin1.get(type: FLOAT) == 1.5", Exp.eq(Exp.floatBin("floatBin1"), Exp.val(1.5))); - parseExpressionAndCompare("1.5 == $.floatBin1.get(type: FLOAT)", + parseExpAndCompare("1.5 == $.floatBin1.get(type: FLOAT)", Exp.eq(Exp.val(1.5), Exp.floatBin("floatBin1"))); } @Test void booleanComparison() { - parseExpressionAndCompare("$.boolBin1.get(type: BOOL) == true", + parseExpAndCompare("$.boolBin1.get(type: BOOL) == true", Exp.eq(Exp.boolBin("boolBin1"), Exp.val(true))); - parseExpressionAndCompare("true == $.boolBin1.get(type: BOOL)", + parseExpAndCompare("true == $.boolBin1.get(type: BOOL)", Exp.eq(Exp.val(true), Exp.boolBin("boolBin1"))); } @Test void negativeBooleanComparison() { - assertThatThrownBy(() -> parseExpression("$.boolBin1.get(type: BOOL) == 5")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("$.boolBin1.get(type: BOOL) == 5")) + .isInstanceOf(DslParseException.class) .hasMessageContaining("Cannot compare BOOL to INT"); } @Test void listComparison_constantOnRightSide() { - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [100]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [100]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of(100)))); - parseExpressionAndCompare("$.listBin1.[] == [100]", + parseExpAndCompare("$.listBin1.[] == [100]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of(100)))); // integer values are read as long - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [100, 200, 300, 400]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [100, 200, 300, 400]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of(100, 200, 300, 400)))); // integer values are read as long - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [100, 200, 300, 400]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [100, 200, 300, 400]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of(100L, 200L, 300L, 400L)))); - parseExpressionAndCompare("$.listBin1.get(type: LIST) == ['yes']", + parseExpAndCompare("$.listBin1.get(type: LIST) == ['yes']", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes")))); - parseExpressionAndCompare("$.listBin1.get(type: LIST) == ['yes', 'of course']", + parseExpAndCompare("$.listBin1.get(type: LIST) == ['yes', 'of course']", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes", "of course")))); - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [\"yes\"]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [\"yes\"]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes")))); - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [\"yes\", \"of course\"]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [\"yes\", \"of course\"]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes", "of course")))); } @@ -119,39 +119,39 @@ void listComparison_constantOnRightSide() { void listComparison_constantOnRightSide_NegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> - parseExpressionAndCompare("$.listBin1.get(type: LIST) == [yes, of course]", + parseExpAndCompare("$.listBin1.get(type: LIST) == [yes, of course]", Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes", "of course")))) ) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse list operand"); } @Test void listComparison_constantOnLeftSide() { - parseExpressionAndCompare("[100] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[100] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of(100)), Exp.listBin("listBin1"))); - parseExpressionAndCompare("[100] == $.listBin1.[]", + parseExpAndCompare("[100] == $.listBin1.[]", Exp.eq(Exp.val(List.of(100)), Exp.listBin("listBin1"))); // integer values are read as long - parseExpressionAndCompare("[100, 200, 300, 400] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[100, 200, 300, 400] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of(100, 200, 300, 400)), Exp.listBin("listBin1"))); // integer values are read as long - parseExpressionAndCompare("[100, 200, 300, 400] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[100, 200, 300, 400] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of(100L, 200L, 300L, 400L)), Exp.listBin("listBin1"))); - parseExpressionAndCompare("['yes'] == $.listBin1.get(type: LIST)", + parseExpAndCompare("['yes'] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of("yes")), Exp.listBin("listBin1"))); - parseExpressionAndCompare("['yes', 'of course'] == $.listBin1.get(type: LIST)", + parseExpAndCompare("['yes', 'of course'] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of("yes", "of course")), Exp.listBin("listBin1"))); - parseExpressionAndCompare("[\"yes\"] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[\"yes\"] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of("yes")), Exp.listBin("listBin1"))); - parseExpressionAndCompare("[\"yes\", \"of course\"] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[\"yes\", \"of course\"] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of("yes", "of course")), Exp.listBin("listBin1"))); } @@ -159,11 +159,11 @@ void listComparison_constantOnLeftSide() { void listComparison_constantOnLeftSide_NegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> - parseExpressionAndCompare("[yes, of course] == $.listBin1.get(type: LIST)", + parseExpAndCompare("[yes, of course] == $.listBin1.get(type: LIST)", Exp.eq(Exp.val(List.of("yes", "of course")), Exp.listBin("listBin1"))) ) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Could not parse given input, wrong syntax"); + .isInstanceOf(DslParseException.class) + .hasMessage("Could not parse given DSL expression input"); } @SuppressWarnings("unchecked") @@ -185,39 +185,39 @@ public static TreeMap treeMapOf(Object... entries) { @Test void mapComparison_constantOnRightSide() { // Prerequisite for comparing maps: both sides must be ordered maps - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {100:100}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {100:100}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(100, 100)))); - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {100 : 100}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {100 : 100}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(100, 100)))); - parseExpressionAndCompare("$.mapBin1.{} == {100:100}", + parseExpAndCompare("$.mapBin1.{} == {100:100}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(100, 100)))); byte[] blobKey = new byte[]{1, 2, 3}; String encodedBlobKey = Base64.getEncoder().encodeToString(blobKey); // encoded blob key must be quoted as it is a String - parseExpressionAndCompare("$.mapBin1.{} == {'" + encodedBlobKey + "':100}", + parseExpAndCompare("$.mapBin1.{} == {'" + encodedBlobKey + "':100}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(encodedBlobKey, 100)))); // integer values are read as long - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {100:200, 300:400}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {100:200, 300:400}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(100L, 200L, 300L, 400L)))); - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {100:200, 300:400}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {100:200, 300:400}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf(100, 200, 300, 400)))); - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {'yes?':'yes!'}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {'yes?':'yes!'}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes?", "yes!")))); - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {\"yes\" : \"yes\"}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {\"yes\" : \"yes\"}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes", "yes")))); - parseExpressionAndCompare( + parseExpAndCompare( "$.mapBin1.get(type: MAP) == {\"yes of course\" : \"yes of course\"}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes of course", "yes of course")))); - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {\"yes\" : [\"yes\", \"of course\"]}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {\"yes\" : [\"yes\", \"of course\"]}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes", List.of("yes", "of course"))))); } @@ -225,64 +225,64 @@ void mapComparison_constantOnRightSide() { void mapComparison_constantOnRightSide_NegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {yes, of course}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {yes, of course}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes", "of course")))) ) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse map operand"); assertThatThrownBy(() -> - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == ['yes', 'of course']", + parseExpAndCompare("$.mapBin1.get(type: MAP) == ['yes', 'of course']", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(List.of("yes", "of course")))) ) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Cannot compare MAP to LIST"); // Map key can only be Integer or String assertThatThrownBy(() -> - parseExpressionAndCompare("$.mapBin1.get(type: MAP) == {[100]:[100]}", + parseExpAndCompare("$.mapBin1.get(type: MAP) == {[100]:[100]}", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(List.of("yes", "of course")))) ) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse map operand"); } @Test void mapComparison_constantOnLeftSide() { // Prerequisite for comparing maps: both sides must be ordered maps - parseExpressionAndCompare("{100:100} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{100:100} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf(100, 100)), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{100 : 100} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{100 : 100} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf(100, 100)), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{100:100} == $.mapBin1.{}", + parseExpAndCompare("{100:100} == $.mapBin1.{}", Exp.eq(Exp.val(treeMapOf(100, 100)), Exp.mapBin("mapBin1"))); byte[] blobKey = new byte[]{1, 2, 3}; String encodedBlobKey = Base64.getEncoder().encodeToString(blobKey); // encoded blob key must be quoted as it is a String - parseExpressionAndCompare("{'" + encodedBlobKey + "':100} == $.mapBin1.{}", + parseExpAndCompare("{'" + encodedBlobKey + "':100} == $.mapBin1.{}", Exp.eq(Exp.val(treeMapOf(encodedBlobKey, 100)), Exp.mapBin("mapBin1"))); // integer values are read as long - parseExpressionAndCompare("{100:200, 300:400} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{100:200, 300:400} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf(100L, 200L, 300L, 400L)), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{100:200, 300:400} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{100:200, 300:400} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf(100, 200, 300, 400)), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{'yes?':'yes!'} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{'yes?':'yes!'} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf("yes?", "yes!")), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{\"yes\" : \"yes\"} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{\"yes\" : \"yes\"} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf("yes", "yes")), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare( + parseExpAndCompare( "{\"yes of course\" : \"yes of course\"} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf("yes of course", "yes of course")), Exp.mapBin("mapBin1"))); - parseExpressionAndCompare("{\"yes\" : [\"yes\", \"of course\"]} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{\"yes\" : [\"yes\", \"of course\"]} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(treeMapOf("yes", List.of("yes", "of course"))), Exp.mapBin("mapBin1"))); } @@ -290,68 +290,68 @@ void mapComparison_constantOnLeftSide() { void mapComparison_constantOnLeftSide_NegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> - parseExpressionAndCompare("{yes, of course} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{yes, of course} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("of course", "yes")))) ) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Could not parse given input, wrong syntax"); + .isInstanceOf(DslParseException.class) + .hasMessage("Could not parse given DSL expression input"); assertThatThrownBy(() -> - parseExpressionAndCompare("['yes', 'of course'] == $.mapBin1.get(type: MAP)", // incorrect: must be {} + parseExpAndCompare("['yes', 'of course'] == $.mapBin1.get(type: MAP)", // incorrect: must be {} Exp.eq(Exp.val(List.of("yes", "of course")), Exp.mapBin("mapBin1"))) ) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Cannot compare LIST to MAP"); + .isInstanceOf(DslParseException.class) + .hasMessage("Cannot compare MAP to LIST"); // Map key can only be Integer or String assertThatThrownBy(() -> - parseExpressionAndCompare("{[100]:[100]} == $.mapBin1.get(type: MAP)", + parseExpAndCompare("{[100]:[100]} == $.mapBin1.get(type: MAP)", Exp.eq(Exp.val(List.of("yes", "of course")), Exp.mapBin("mapBin1"))) ) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Could not parse given input, wrong syntax"); + .isInstanceOf(DslParseException.class) + .hasMessage("Could not parse given DSL expression input"); } @Test void twoStringBinsComparison() { - parseExpressionAndCompare("$.stringBin1.get(type: STRING) == $.stringBin2.get(type: STRING)", + parseExpAndCompare("$.stringBin1.get(type: STRING) == $.stringBin2.get(type: STRING)", Exp.eq(Exp.stringBin("stringBin1"), Exp.stringBin("stringBin2"))); } @Test void twoIntegerBinsComparison() { - parseExpressionAndCompare("$.intBin1.get(type: INT) == $.intBin2.get(type: INT)", + parseExpAndCompare("$.intBin1.get(type: INT) == $.intBin2.get(type: INT)", Exp.eq(Exp.intBin("intBin1"), Exp.intBin("intBin2"))); } @Test void twoFloatBinsComparison() { - parseExpressionAndCompare("$.floatBin1.get(type: FLOAT) == $.floatBin2.get(type: FLOAT)", + parseExpAndCompare("$.floatBin1.get(type: FLOAT) == $.floatBin2.get(type: FLOAT)", Exp.eq(Exp.floatBin("floatBin1"), Exp.floatBin("floatBin2"))); } @Test void twoBlobBinsComparison() { - parseExpressionAndCompare("$.blobBin1.get(type: BLOB) == $.blobBin2.get(type: BLOB)", + parseExpAndCompare("$.blobBin1.get(type: BLOB) == $.blobBin2.get(type: BLOB)", Exp.eq(Exp.blobBin("blobBin1"), Exp.blobBin("blobBin2"))); } @Test void negativeTwoDifferentBinTypesComparison() { - assertThatThrownBy(() -> parseExpression("$.stringBin1.get(type: STRING) == $.floatBin2.get(type: FLOAT)")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("$.stringBin1.get(type: STRING) == $.floatBin2.get(type: FLOAT)")) + .isInstanceOf(DslParseException.class) .hasMessageContaining("Cannot compare STRING to FLOAT"); } @Test void secondDegreeExplicitFloat() { - parseExpressionAndCompare("($.apples.get(type: FLOAT) + $.bananas.get(type: FLOAT)) > 10.5", + parseExpAndCompare("($.apples.get(type: FLOAT) + $.bananas.get(type: FLOAT)) > 10.5", Exp.gt(Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), Exp.val(10.5))); } @Test void forthDegreeComplicatedExplicitFloat() { - parseExpressionAndCompare("(($.apples.get(type: FLOAT) + $.bananas.get(type: FLOAT))" + + parseExpAndCompare("(($.apples.get(type: FLOAT) + $.bananas.get(type: FLOAT))" + " + ($.oranges.get(type: FLOAT) + $.acai.get(type: FLOAT))) > 10.5", Exp.gt( Exp.add( @@ -382,7 +382,7 @@ void complicatedWhenExplicitTypeIntDefault() { ) ); - parseExpressionAndCompare("$.a.get(type: INT) == " + + parseExpAndCompare("$.a.get(type: INT) == " + "(when($.b.get(type: INT) == 1 => $.a1.get(type: INT)," + " $.b.get(type: INT) == 2 => $.a2.get(type: INT)," + " $.b.get(type: INT) == 3 => $.a3.get(type: INT)," + @@ -411,7 +411,7 @@ void complicatedWhenExplicitTypeString() { ) ); - parseExpressionAndCompare("$.a.get(type: STRING) == " + + parseExpAndCompare("$.a.get(type: STRING) == " + "(when($.b == 1 => $.a1.get(type: STRING)," + " $.b == 2 => $.a2.get(type: STRING)," + " $.b == 3 => $.a3.get(type: STRING)," + diff --git a/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java index 641f6aa..2d657bb 100644 --- a/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java @@ -3,23 +3,23 @@ import com.aerospike.client.exp.Exp; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; public class ImplicitTypesTests { @Test void floatComparison() { - parseExpressionAndCompare("$.floatBin1 >= 100.25", + parseExpAndCompare("$.floatBin1 >= 100.25", Exp.ge(Exp.floatBin("floatBin1"), Exp.val(100.25))); } @Test void booleanComparison() { - parseExpressionAndCompare("$.boolBin1 == true", + parseExpAndCompare("$.boolBin1 == true", Exp.eq(Exp.boolBin("boolBin1"), Exp.val(true))); - parseExpressionAndCompare("false == $.boolBin1", + parseExpAndCompare("false == $.boolBin1", Exp.eq(Exp.val(false), Exp.boolBin("boolBin1"))); - parseExpressionAndCompare("$.boolBin1 != false", + parseExpAndCompare("$.boolBin1 != false", Exp.ne(Exp.boolBin("boolBin1"), Exp.val(false))); } @@ -27,31 +27,31 @@ void booleanComparison() { // this can also be an expression that evaluates to a boolean result @Test void binBooleanImplicitLogicalComparison() { - parseExpressionAndCompare("$.boolBin1 and $.boolBin2", - Exp.and(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); - parseExpressionAndCompare("$.boolBin1 or $.boolBin2", - Exp.or(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); - parseExpressionAndCompare("not($.boolBin1)", +// parseFilterExpAndCompare("$.boolBin1 and $.boolBin2", +// Exp.and(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); +// parseFilterExpAndCompare("$.boolBin1 or $.boolBin2", +// Exp.or(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); + parseExpAndCompare("not($.boolBin1)", Exp.not(Exp.boolBin("boolBin1"))); - parseExpressionAndCompare("exclusive($.boolBin1, $.boolBin2)", - Exp.exclusive(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); +// parseFilterExpAndCompare("exclusive($.boolBin1, $.boolBin2)", +// Exp.exclusive(Exp.boolBin("boolBin1"), Exp.boolBin("boolBin2"))); } @Test void implicitDefaultIntComparison() { - parseExpressionAndCompare("$.intBin1 < $.intBin2", + parseExpAndCompare("$.intBin1 < $.intBin2", Exp.lt(Exp.intBin("intBin1"), Exp.intBin("intBin2"))); } @Test void secondDegreeImplicitCastingFloat() { - parseExpressionAndCompare("($.apples + $.bananas) > 10.5", + parseExpAndCompare("($.apples + $.bananas) > 10.5", Exp.gt(Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), Exp.val(10.5))); } @Test void secondDegreeComplicatedFloatFirstImplicitCastingFloat() { - parseExpressionAndCompare("($.apples + $.bananas) > 10.5 and ($.oranges + $.grapes) <= 5", + parseExpAndCompare("($.apples + $.bananas) > 10.5 and ($.oranges + $.grapes) <= 5", Exp.and( Exp.gt(Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), Exp.val(10.5)), Exp.le(Exp.add(Exp.intBin("oranges"), Exp.intBin("grapes")), Exp.val(5))) @@ -60,7 +60,7 @@ void secondDegreeComplicatedFloatFirstImplicitCastingFloat() { @Test void secondDegreeComplicatedIntFirstImplicitCastingFloat() { - parseExpressionAndCompare("($.apples + $.bananas) > 5 and ($.oranges + $.grapes) <= 10.5", + parseExpAndCompare("($.apples + $.bananas) > 5 and ($.oranges + $.grapes) <= 10.5", Exp.and( Exp.gt(Exp.add(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.val(5)), Exp.le(Exp.add(Exp.floatBin("oranges"), Exp.floatBin("grapes")), Exp.val(10.5))) @@ -69,7 +69,7 @@ void secondDegreeComplicatedIntFirstImplicitCastingFloat() { @Test void thirdDegreeComplicatedDefaultInt() { - parseExpressionAndCompare("(($.apples + $.bananas) + $.oranges) > 10", + parseExpAndCompare("(($.apples + $.bananas) + $.oranges) > 10", Exp.gt( Exp.add(Exp.add(Exp.intBin("apples"), Exp.intBin("bananas")), Exp.intBin("oranges")), Exp.val(10)) @@ -78,7 +78,7 @@ void thirdDegreeComplicatedDefaultInt() { @Test void thirdDegreeComplicatedImplicitCastingFloat() { - parseExpressionAndCompare("(($.apples + $.bananas) + $.oranges) > 10.5", + parseExpAndCompare("(($.apples + $.bananas) + $.oranges) > 10.5", Exp.gt( Exp.add(Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), Exp.floatBin("oranges")), Exp.val(10.5)) @@ -87,7 +87,7 @@ void thirdDegreeComplicatedImplicitCastingFloat() { @Test void forthDegreeComplicatedDefaultInt() { - parseExpressionAndCompare("(($.apples + $.bananas) + ($.oranges + $.acai)) > 10", + parseExpAndCompare("(($.apples + $.bananas) + ($.oranges + $.acai)) > 10", Exp.gt( Exp.add( Exp.add(Exp.intBin("apples"), Exp.intBin("bananas")), @@ -98,7 +98,7 @@ void forthDegreeComplicatedDefaultInt() { @Test void forthDegreeComplicatedImplicitCastingFloat() { - parseExpressionAndCompare("(($.apples + $.bananas) + ($.oranges + $.acai)) > 10.5", + parseExpAndCompare("(($.apples + $.bananas) + ($.oranges + $.acai)) > 10.5", Exp.gt( Exp.add( Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), @@ -128,7 +128,7 @@ void complicatedWhenImplicitTypeInt() { ) ); - parseExpressionAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => $.a4+1))", + parseExpAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => $.a4+1))", expected); } @@ -154,7 +154,7 @@ void complicatedWhenImplicitTypeString() { ) ); - parseExpressionAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => \"hello\"))", + parseExpAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => \"hello\"))", expected); } } diff --git a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java index fa6f36c..1f5d96f 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -4,13 +4,13 @@ import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; import java.util.List; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; class ListExpressionsTests { @@ -26,10 +26,10 @@ void listByIndexInteger() { ), Exp.val(100)); // Implicit detect as Int - parseExpressionAndCompare("$.listBin1.[0] == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: INT) == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].asInt() == 100", expected); + parseExpAndCompare("$.listBin1.[0] == 100", expected); + parseExpAndCompare("$.listBin1.[0].get(type: INT) == 100", expected); + parseExpAndCompare("$.listBin1.[0].get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.listBin1.[0].asInt() == 100", expected); } @Test @@ -43,9 +43,9 @@ void listByIndexOtherTypes() { ), Exp.val("stringVal")); // Implicit detect as string - parseExpressionAndCompare("$.listBin1.[0] == \"stringVal\"", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: STRING) == \"stringVal\"", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: STRING, return: VALUE) == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[0] == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[0].get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[0].get(type: STRING, return: VALUE) == \"stringVal\"", expected); expected = Exp.eq( ListExp.getByIndex( @@ -56,9 +56,9 @@ void listByIndexOtherTypes() { ), Exp.val(true)); // Implicit detect as boolean - parseExpressionAndCompare("$.listBin1.[0] == true", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: BOOL) == true", expected); - parseExpressionAndCompare("$.listBin1.[0].get(type: BOOL, return: VALUE) == true", expected); + parseExpAndCompare("$.listBin1.[0] == true", expected); + parseExpAndCompare("$.listBin1.[0].get(type: BOOL) == true", expected); + parseExpAndCompare("$.listBin1.[0].get(type: BOOL, return: VALUE) == true", expected); } @Test @@ -70,10 +70,10 @@ void listByValue() { Exp.listBin("listBin1") ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[=100] == 100", expected); - parseExpressionAndCompare("$.listBin1.[=100].get(type: INT) == 100", expected); - parseExpressionAndCompare("$.listBin1.[=100].get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.listBin1.[=100].asInt() == 100", expected); + parseExpAndCompare("$.listBin1.[=100] == 100", expected); + parseExpAndCompare("$.listBin1.[=100].get(type: INT) == 100", expected); + parseExpAndCompare("$.listBin1.[=100].get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.listBin1.[=100].asInt() == 100", expected); } @Test @@ -84,8 +84,8 @@ void listByValueCount() { Exp.listBin("listBin1")), Exp.val(0) ); - parseExpressionAndCompare("$.listBin1.[=100].count() > 0", expected); - parseExpressionAndCompare("$.listBin1.[=100].[].count() > 0", expected); + parseExpAndCompare("$.listBin1.[=100].count() > 0", expected); + parseExpAndCompare("$.listBin1.[=100].[].count() > 0", expected); } @Test @@ -98,10 +98,10 @@ void listByRank() { Exp.listBin("listBin1") ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[#-1] == 100", expected); - parseExpressionAndCompare("$.listBin1.[#-1].get(type: INT) == 100", expected); - parseExpressionAndCompare("$.listBin1.[#-1].get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.listBin1.[#-1].asInt() == 100", expected); + parseExpAndCompare("$.listBin1.[#-1] == 100", expected); + parseExpAndCompare("$.listBin1.[#-1].get(type: INT) == 100", expected); + parseExpAndCompare("$.listBin1.[#-1].get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.listBin1.[#-1].asInt() == 100", expected); } @Test @@ -116,9 +116,9 @@ void listBinElementEquals_Nested() { CTX.listIndex(0) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[0].[0].[0] == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].[0].[0].get(type: INT) == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].[0].[0].get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.listBin1.[0].[0].[0] == 100", expected); + parseExpAndCompare("$.listBin1.[0].[0].[0].get(type: INT) == 100", expected); + parseExpAndCompare("$.listBin1.[0].[0].[0].get(type: INT, return: VALUE) == 100", expected); } @Test @@ -126,10 +126,10 @@ void listSize() { Exp expected = Exp.eq( ListExp.size(Exp.listBin("listBin1")), Exp.val(1)); - parseExpressionAndCompare("$.listBin1.[].count() == 1", expected); + parseExpAndCompare("$.listBin1.[].count() == 1", expected); // the default behaviour for count() without List '[]' or Map '{}' designators is List - parseExpressionAndCompare("$.listBin1.count() == 1", expected); + parseExpAndCompare("$.listBin1.count() == 1", expected); } @Test @@ -143,10 +143,10 @@ void nestedListSize() { Exp.listBin("listBin1")) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[1].[].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].[].count() == 100", expected); // the default behaviour for count() without List '[]' or Map '{}' designators is List - parseExpressionAndCompare("$.listBin1.[1].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].count() == 100", expected); } @@ -162,10 +162,10 @@ void nestedListSizeWithContext() { CTX.listIndex(1)) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[1].[2].[].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].[2].[].count() == 100", expected); // the default behaviour for count() without List '[]' or Map '{}' designators is List - parseExpressionAndCompare("$.listBin1.[1].[2].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].[2].count() == 100", expected); } @Test @@ -179,7 +179,7 @@ void nestedLists() { CTX.listIndex(5) ), Exp.val("stringVal")); - parseExpressionAndCompare("$.listBin1.[5].[1].get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[5].[1].get(type: STRING) == \"stringVal\"", expected); } @Test @@ -195,8 +195,8 @@ void nestedListsWithDifferentContextTypes() { ), Exp.val("stringVal")); // Implicit detect as String - parseExpressionAndCompare("$.listBin1.[5].[#-1] == \"stringVal\"", expected); - parseExpressionAndCompare("$.listBin1.[5].[#-1].get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[5].[#-1] == \"stringVal\"", expected); + parseExpAndCompare("$.listBin1.[5].[#-1].get(type: STRING) == \"stringVal\"", expected); // Nested List Rank Value expected = Exp.eq( @@ -209,7 +209,7 @@ void nestedListsWithDifferentContextTypes() { ), Exp.val(200)); // Implicit detect as Int - parseExpressionAndCompare("$.listBin1.[5].[#-1].[=100] == 200", expected); + parseExpAndCompare("$.listBin1.[5].[#-1].[=100] == 200", expected); } @Test @@ -223,21 +223,21 @@ void listBinElementCount() { ), Exp.val(100) ); - parseExpressionAndCompare("$.listBin1.[0].count() == 100", expected); - parseExpressionAndCompare("$.listBin1.[0].[].count() == 100", expected); + parseExpAndCompare("$.listBin1.[0].count() == 100", expected); + parseExpAndCompare("$.listBin1.[0].[].count() == 100", expected); } @Test void negativeSyntaxList() { // TODO: throw meaningful exception (by ANTLR?) - assertThatThrownBy(() -> parseExpression("$.listBin1.[stringValue] == 100")) - .isInstanceOf(AerospikeDSLException.class); + assertThatThrownBy(() -> parseExp("$.listBin1.[stringValue] == 100")) + .isInstanceOf(DslParseException.class); } //@Test void negativeTypeComparisonList() { // TODO: should fail? Exp is successfully created but comparing int to a string value (validations on List) - assertThatThrownBy(() -> parseExpression("$.listBin1.[#-1].get(type: INT) == \"stringValue\"")) + assertThatThrownBy(() -> parseExp("$.listBin1.[#-1].get(type: INT) == \"stringValue\"")) .isInstanceOf(NullPointerException.class); } @@ -248,7 +248,7 @@ void listIndexRange() { Exp.val(1), Exp.val(2), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[1:3]", expected); + parseExpAndCompare("$.listBin1.[1:3]", expected); // Negative expected = ListExp.getByIndexRange( @@ -256,7 +256,7 @@ void listIndexRange() { Exp.val(-3), Exp.val(4), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[-3:1]", expected); + parseExpAndCompare("$.listBin1.[-3:1]", expected); // Inverted expected = ListExp.getByIndexRange( @@ -264,14 +264,14 @@ void listIndexRange() { Exp.val(2), Exp.val(2), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[!2:4]", expected); + parseExpAndCompare("$.listBin1.[!2:4]", expected); // From start till the end expected = ListExp.getByIndexRange( ListReturnType.VALUE, Exp.val(1), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[1:]", expected); + parseExpAndCompare("$.listBin1.[1:]", expected); } @Test @@ -280,23 +280,23 @@ void listValueList() { ListReturnType.VALUE, Exp.val(List.of("a", "b", "c")), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[=a,b,c]", expected); - parseExpressionAndCompare("$.listBin1.[=\"a\",\"b\",\"c\"]", expected); + parseExpAndCompare("$.listBin1.[=a,b,c]", expected); + parseExpAndCompare("$.listBin1.[=\"a\",\"b\",\"c\"]", expected); // Integer expected = ListExp.getByValueList( ListReturnType.VALUE, Exp.val(List.of(1, 2, 3)), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[=1,2,3]", expected); + parseExpAndCompare("$.listBin1.[=1,2,3]", expected); // Inverted expected = ListExp.getByValueList( ListReturnType.VALUE | ListReturnType.INVERTED, Exp.val(List.of("a", "b", "c")), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[!=a,b,c]", expected); - parseExpressionAndCompare("$.listBin1.[!=\"a\",\"b\",\"c\"]", expected); + parseExpAndCompare("$.listBin1.[!=a,b,c]", expected); + parseExpAndCompare("$.listBin1.[!=\"a\",\"b\",\"c\"]", expected); } @Test @@ -307,7 +307,7 @@ void listValueRange() { Exp.val(111), Exp.val(334), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[=111:334]", expected); + parseExpAndCompare("$.listBin1.[=111:334]", expected); // Inverted expected = ListExp.getByValueRange( @@ -315,7 +315,7 @@ void listValueRange() { Exp.val(10), Exp.val(20), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[!=10:20]", expected); + parseExpAndCompare("$.listBin1.[!=10:20]", expected); // From start till the end expected = ListExp.getByValueRange( @@ -323,7 +323,7 @@ void listValueRange() { Exp.val(111), null, Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[=111:]", expected); + parseExpAndCompare("$.listBin1.[=111:]", expected); } @Test @@ -333,7 +333,7 @@ void listRankRange() { Exp.val(0), Exp.val(3), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[#0:3]", expected); + parseExpAndCompare("$.listBin1.[#0:3]", expected); // Inverted expected = ListExp.getByRankRange( @@ -341,14 +341,14 @@ void listRankRange() { Exp.val(0), Exp.val(3), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[!#0:3]", expected); + parseExpAndCompare("$.listBin1.[!#0:3]", expected); // From start till the end expected = ListExp.getByRankRange( ListReturnType.VALUE, Exp.val(-3), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[#-3:]", expected); + parseExpAndCompare("$.listBin1.[#-3:]", expected); // From start till the end with context expected = ListExp.getByRankRange( @@ -356,7 +356,7 @@ void listRankRange() { Exp.val(-3), Exp.listBin("listBin1"), CTX.listIndex(5)); - parseExpressionAndCompare("$.listBin1.[5].[#-3:]", expected); + parseExpAndCompare("$.listBin1.[5].[#-3:]", expected); } @Test @@ -367,7 +367,7 @@ void listRankRangeRelative() { Exp.val("b"), Exp.val(2), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[#-3:-1~b]", expected); + parseExpAndCompare("$.listBin1.[#-3:-1~b]", expected); // Inverted expected = ListExp.getByValueRelativeRankRange( @@ -376,7 +376,7 @@ void listRankRangeRelative() { Exp.val("b"), Exp.val(2), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[!#-3:-1~b]", expected); + parseExpAndCompare("$.listBin1.[!#-3:-1~b]", expected); // From start till the end expected = ListExp.getByValueRelativeRankRange( @@ -384,7 +384,7 @@ void listRankRangeRelative() { Exp.val(-3), Exp.val("b"), Exp.listBin("listBin1")); - parseExpressionAndCompare("$.listBin1.[#-3:~b]", expected); + parseExpAndCompare("$.listBin1.[#-3:~b]", expected); } @Test @@ -398,7 +398,7 @@ void listReturnTypes() { ), Exp.val(5)); // Implicit detect as Int - parseExpressionAndCompare("$.listBin1.[0].get(return: COUNT) == 5", expected); + parseExpAndCompare("$.listBin1.[0].get(return: COUNT) == 5", expected); expected = Exp.eq( ListExp.getByIndex( @@ -409,7 +409,7 @@ void listReturnTypes() { ), Exp.val(true)); // Implicit detect as Int - parseExpressionAndCompare("$.listBin1.[0].get(return: EXISTS) == true", expected); + parseExpAndCompare("$.listBin1.[0].get(return: EXISTS) == true", expected); expected = Exp.eq( ListExp.getByIndex( @@ -420,6 +420,6 @@ void listReturnTypes() { ), Exp.val(1)); // Implicit detect as Int - parseExpressionAndCompare("$.listBin1.[0].get(return: INDEX) == 1", expected); + parseExpAndCompare("$.listBin1.[0].get(return: INDEX) == 1", expected); } } diff --git a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java index 28a7b86..ff6c344 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -1,12 +1,12 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; import org.opentest4j.AssertionFailedError; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class LogicalExpressionsTests { @@ -15,7 +15,7 @@ public class LogicalExpressionsTests { void binLogicalAndOrCombinations() { Exp expected1 = Exp.and(Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), Exp.gt(Exp.intBin("intBin2"), Exp.val(100))); - parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", expected1); + parseExpAndCompare("$.intBin1 > 100 and $.intBin2 > 100", expected1); Exp expected2 = Exp.or( Exp.and( @@ -25,8 +25,8 @@ void binLogicalAndOrCombinations() { Exp.lt(Exp.intBin("intBin3"), Exp.val(100)) ); // TODO: what should be the default behaviour with no parentheses? - parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 < 100", expected2); - parseExpressionAndCompare("($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 < 100", expected2); + parseExpAndCompare("$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 < 100", expected2); + parseExpAndCompare("($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 < 100", expected2); Exp expected3 = Exp.and( Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), @@ -35,27 +35,27 @@ void binLogicalAndOrCombinations() { Exp.lt(Exp.intBin("intBin3"), Exp.val(100)) ) ); - parseExpressionAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected3); + parseExpAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected3); // check that parentheses make difference - assertThatThrownBy(() -> parseExpressionAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected2)) + assertThatThrownBy(() -> parseExpAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected2)) .isInstanceOf(AssertionFailedError.class); } @Test void logicalNot() { - parseExpressionAndCompare("not($.keyExists())", Exp.not(Exp.keyExists())); + parseExpAndCompare("not($.keyExists())", Exp.not(Exp.keyExists())); } @Test void binLogicalExclusive() { - parseExpressionAndCompare("exclusive($.hand == \"hook\", $.leg == \"peg\")", + parseExpAndCompare("exclusive($.hand == \"hook\", $.leg == \"peg\")", Exp.exclusive( Exp.eq(Exp.stringBin("hand"), Exp.val("hook")), Exp.eq(Exp.stringBin("leg"), Exp.val("peg")))); // More than 2 expressions exclusive - parseExpressionAndCompare("exclusive($.a == \"aVal\", $.b == \"bVal\", $.c == \"cVal\", $.d == 4)", + parseExpAndCompare("exclusive($.a == \"aVal\", $.b == \"bVal\", $.c == \"cVal\", $.d == 4)", Exp.exclusive( Exp.eq(Exp.stringBin("a"), Exp.val("aVal")), Exp.eq(Exp.stringBin("b"), Exp.val("bVal")), @@ -66,7 +66,7 @@ void binLogicalExclusive() { //TODO: FMWK-488 //@Test void flatHierarchyAnd() { - parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 < 100", + parseExpAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 < 100", Exp.and( Exp.gt( Exp.intBin("intBin1"), @@ -81,29 +81,29 @@ void flatHierarchyAnd() { @Test void negativeSyntaxLogicalOperators() { - assertThatThrownBy(() -> parseExpression("($.intBin1 > 100 and ($.intBin2 > 100) or")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Could not parse given input"); + assertThatThrownBy(() -> parseExp("($.intBin1 > 100 and ($.intBin2 > 100) or")) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); - assertThatThrownBy(() -> parseExpression("and ($.intBin1 > 100 and ($.intBin2 > 100)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Could not parse given input"); + assertThatThrownBy(() -> parseExp("and ($.intBin1 > 100 and ($.intBin2 > 100)")) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); - assertThatThrownBy(() -> parseExpression("($.intBin1 > 100 and ($.intBin2 > 100) not")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Could not parse given input"); + assertThatThrownBy(() -> parseExp("($.intBin1 > 100 and ($.intBin2 > 100) not")) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); - assertThatThrownBy(() -> parseExpression("($.intBin1 > 100 and ($.intBin2 > 100) exclusive")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Could not parse given input"); + assertThatThrownBy(() -> parseExp("($.intBin1 > 100 and ($.intBin2 > 100) exclusive")) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); } @Test void negativeBinLogicalExclusiveWithOneParam() { - assertThatThrownBy(() -> parseExpressionAndCompare("exclusive($.hand == \"hook\")", + assertThatThrownBy(() -> parseExpAndCompare("exclusive($.hand == \"hook\")", Exp.exclusive( Exp.eq(Exp.stringBin("hand"), Exp.val("hook"))))) - .isInstanceOf(AerospikeDSLException.class) + .isInstanceOf(DslParseException.class) .hasMessage("Exclusive logical operator requires 2 or more expressions"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java index 5bff0a5..0c2926d 100644 --- a/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java @@ -9,7 +9,7 @@ import com.aerospike.client.exp.MapExp; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; public class MapAndListExpressionsTests { @@ -24,7 +24,7 @@ void listInsideAMap() { CTX.mapKey(Value.get("a")) ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.a.[0] == 100", expected); + parseExpAndCompare("$.mapBin1.a.[0] == 100", expected); expected = Exp.gt( ListExp.getByIndex( @@ -35,7 +35,7 @@ void listInsideAMap() { CTX.mapKey(Value.get("a")), CTX.mapKey(Value.get("cc")) ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.a.cc.[2].get(type: INT) > 100", expected); + parseExpAndCompare("$.mapBin1.a.cc.[2].get(type: INT) > 100", expected); } @Test @@ -50,7 +50,7 @@ void mapListList() { CTX.listIndex(0) ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.a.[0].[0] == 100", expected); + parseExpAndCompare("$.mapBin1.a.[0].[0] == 100", expected); } @Test @@ -63,7 +63,7 @@ void mapInsideAList() { Exp.listBin("listBin1"), CTX.listIndex(2) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[2].cc.get(type: INT) > 100", expected); + parseExpAndCompare("$.listBin1.[2].cc.get(type: INT) > 100", expected); } @Test @@ -77,8 +77,8 @@ void listMapMap() { CTX.listIndex(2), CTX.mapKey(Value.get("aa")) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[2].aa.cc > 100", expected); - parseExpressionAndCompare("$.listBin1.[2].aa.cc.get(type: INT) > 100", expected); + parseExpAndCompare("$.listBin1.[2].aa.cc > 100", expected); + parseExpAndCompare("$.listBin1.[2].aa.cc.get(type: INT) > 100", expected); } @Test @@ -93,7 +93,7 @@ void listMapList() { CTX.mapKey(Value.get("a")) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[1].a.[0] == 100", expected); + parseExpAndCompare("$.listBin1.[1].a.[0] == 100", expected); } @Test @@ -110,8 +110,8 @@ void listMapListSize() { ) ), Exp.val(100)); - parseExpressionAndCompare("$.listBin1.[1].a.[0].count() == 100", expected); - parseExpressionAndCompare("$.listBin1.[1].a.[0].[].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].a.[0].count() == 100", expected); + parseExpAndCompare("$.listBin1.[1].a.[0].[].count() == 100", expected); } @Test @@ -125,8 +125,8 @@ void mapListMap() { CTX.mapKey(Value.get("a")), CTX.listIndex(0) ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.a.[0].cc > 100", expected); - parseExpressionAndCompare("$.mapBin1.a.[0].cc.get(type: INT) > 100", expected); + parseExpAndCompare("$.mapBin1.a.[0].cc > 100", expected); + parseExpAndCompare("$.mapBin1.a.[0].cc.get(type: INT) > 100", expected); } //@Test diff --git a/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java index 68f36fb..806f5eb 100644 --- a/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java @@ -11,7 +11,7 @@ import java.util.List; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; public class MapExpressionsTests { @@ -27,10 +27,10 @@ void mapOneLevelExpressions() { ), Exp.val(200)); // Implicit detect as Int - parseExpressionAndCompare("$.mapBin1.a == 200", expected); - parseExpressionAndCompare("$.mapBin1.a.get(type: INT) == 200", expected); - parseExpressionAndCompare("$.mapBin1.a.get(type: INT, return: VALUE) == 200", expected); - parseExpressionAndCompare("$.mapBin1.a.asInt() == 200", expected); + parseExpAndCompare("$.mapBin1.a == 200", expected); + parseExpAndCompare("$.mapBin1.a.get(type: INT) == 200", expected); + parseExpAndCompare("$.mapBin1.a.get(type: INT, return: VALUE) == 200", expected); + parseExpAndCompare("$.mapBin1.a.asInt() == 200", expected); // String expected = Exp.eq( @@ -42,9 +42,9 @@ void mapOneLevelExpressions() { ), Exp.val("stringVal")); // Implicit detect as String - parseExpressionAndCompare("$.mapBin1.a == \"stringVal\"", expected); - parseExpressionAndCompare("$.mapBin1.a.get(type: STRING) == \"stringVal\"", expected); - parseExpressionAndCompare("$.mapBin1.a.get(type: STRING, return: VALUE) == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a.get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a.get(type: STRING, return: VALUE) == \"stringVal\"", expected); } @Test @@ -58,9 +58,9 @@ void mapNestedLevelExpressions() { CTX.mapKey(Value.get("a")), CTX.mapKey(Value.get("bb")) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc > 200", expected); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc.get(type: INT) > 200", expected); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc.get(type: INT, return: VALUE) > 200", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc > 200", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc.get(type: INT, return: VALUE) > 200", expected); // String expected = Exp.eq( @@ -73,9 +73,9 @@ void mapNestedLevelExpressions() { ), Exp.val("stringVal")); // Implicit detect as String - parseExpressionAndCompare("$.mapBin1.a.bb.bcc == \"stringVal\"", expected); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc.get(type: STRING) == \"stringVal\"", expected); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc.get(type: STRING, return: VALUE) == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc.get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc.get(type: STRING, return: VALUE) == \"stringVal\"", expected); } @Test @@ -89,9 +89,9 @@ void quotedStringInExpressionPath() { CTX.mapKey(Value.get("a")), CTX.mapKey(Value.get("bb")) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.bb.bcc.get(type: INT) > 200", expected); - parseExpressionAndCompare("$.mapBin1.a.\"bb\".bcc.get(type: INT) > 200", expected); - parseExpressionAndCompare("$.mapBin1.a.'bb'.bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.a.bb.bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.a.\"bb\".bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.a.'bb'.bcc.get(type: INT) > 200", expected); expected = Exp.gt( MapExp.getByKey( @@ -102,8 +102,8 @@ void quotedStringInExpressionPath() { CTX.mapKey(Value.get("127.0.0.1")) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.\"127.0.0.1\".bcc.get(type: INT) > 200", expected); - parseExpressionAndCompare("$.mapBin1.'127.0.0.1'.bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.\"127.0.0.1\".bcc.get(type: INT) > 200", expected); + parseExpAndCompare("$.mapBin1.'127.0.0.1'.bcc.get(type: INT) > 200", expected); } @Test @@ -113,7 +113,7 @@ void mapSize() { Exp.mapBin("mapBin1") ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.{}.count() > 200", expected); + parseExpAndCompare("$.mapBin1.{}.count() > 200", expected); // the default behaviour for count() without List '[]' or Map '{}' designators is List Exp expected2 = Exp.gt( @@ -121,7 +121,7 @@ void mapSize() { Exp.listBin("mapBin1") ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.count() > 200", expected2); + parseExpAndCompare("$.mapBin1.count() > 200", expected2); } @Test @@ -135,7 +135,7 @@ void nestedMapSize() { Exp.mapBin("mapBin1")) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.{}.count() == 200", expected); + parseExpAndCompare("$.mapBin1.a.{}.count() == 200", expected); // the default behaviour for count() without Map '{}' or List '[]' designators is List Exp expected2 = Exp.eq( @@ -146,7 +146,7 @@ void nestedMapSize() { Exp.mapBin("mapBin1")) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.count() == 200", expected2); + parseExpAndCompare("$.mapBin1.a.count() == 200", expected2); } @Test @@ -161,7 +161,7 @@ void nestedMapSizeWithContext() { CTX.mapKey(Value.get("a"))) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.b.{}.count() == 200", expected); + parseExpAndCompare("$.mapBin1.a.b.{}.count() == 200", expected); // the default behaviour for count() without Map '{}' or List '[]' designators is List Exp expected2 = Exp.eq( @@ -173,7 +173,7 @@ void nestedMapSizeWithContext() { CTX.mapKey(Value.get("a"))) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.a.b.count() == 200", expected2); + parseExpAndCompare("$.mapBin1.a.b.count() == 200", expected2); } @Test @@ -186,10 +186,10 @@ void mapByIndex() { Exp.mapBin("mapBin1") ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.{0} == 100", expected); - parseExpressionAndCompare("$.mapBin1.{0}.get(type: INT) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{0}.get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{0}.asInt() == 100", expected); + parseExpAndCompare("$.mapBin1.{0} == 100", expected); + parseExpAndCompare("$.mapBin1.{0}.get(type: INT) == 100", expected); + parseExpAndCompare("$.mapBin1.{0}.get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.mapBin1.{0}.asInt() == 100", expected); } @Test @@ -201,10 +201,10 @@ void mapByValue() { Exp.mapBin("mapBin1") ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.{=100} == 100", expected); - parseExpressionAndCompare("$.mapBin1.{=100}.get(type: INT) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{=100}.get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{=100}.asInt() == 100", expected); + parseExpAndCompare("$.mapBin1.{=100} == 100", expected); + parseExpAndCompare("$.mapBin1.{=100}.get(type: INT) == 100", expected); + parseExpAndCompare("$.mapBin1.{=100}.get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.mapBin1.{=100}.asInt() == 100", expected); } @Test @@ -215,8 +215,8 @@ void mapByValueCount() { Exp.mapBin("mapBin1")), Exp.val(0) ); - parseExpressionAndCompare("$.mapBin1.{=100}.count() > 0", expected); - parseExpressionAndCompare("$.mapBin1.{=100}.{}.count() > 0", expected); + parseExpAndCompare("$.mapBin1.{=100}.count() > 0", expected); + parseExpAndCompare("$.mapBin1.{=100}.{}.count() > 0", expected); } @Test @@ -229,10 +229,10 @@ void mapByRank() { Exp.mapBin("mapBin1") ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.{#-1} == 100", expected); - parseExpressionAndCompare("$.mapBin1.{#-1}.get(type: INT) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{#-1}.get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.mapBin1.{#-1}.asInt() == 100", expected); + parseExpAndCompare("$.mapBin1.{#-1} == 100", expected); + parseExpAndCompare("$.mapBin1.{#-1}.get(type: INT) == 100", expected); + parseExpAndCompare("$.mapBin1.{#-1}.get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.mapBin1.{#-1}.asInt() == 100", expected); } @Test @@ -246,10 +246,10 @@ void mapByRankWithNesting() { CTX.mapKey(Value.get("a")) ), Exp.val(100)); - parseExpressionAndCompare("$.mapBin1.a.{#-1} == 100", expected); - parseExpressionAndCompare("$.mapBin1.a.{#-1}.get(type: INT) == 100", expected); - parseExpressionAndCompare("$.mapBin1.a.{#-1}.get(type: INT, return: VALUE) == 100", expected); - parseExpressionAndCompare("$.mapBin1.a.{#-1}.asInt() == 100", expected); + parseExpAndCompare("$.mapBin1.a.{#-1} == 100", expected); + parseExpAndCompare("$.mapBin1.a.{#-1}.get(type: INT) == 100", expected); + parseExpAndCompare("$.mapBin1.a.{#-1}.get(type: INT, return: VALUE) == 100", expected); + parseExpAndCompare("$.mapBin1.a.{#-1}.asInt() == 100", expected); } @Test @@ -265,8 +265,8 @@ void nestedListsWithDifferentContextTypes() { ), Exp.val("stringVal")); // Implicit detect as String - parseExpressionAndCompare("$.mapBin1.{5}.{#-1} == \"stringVal\"", expected); - parseExpressionAndCompare("$.mapBin1.{5}.{#-1}.get(type: STRING) == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.{5}.{#-1} == \"stringVal\"", expected); + parseExpAndCompare("$.mapBin1.{5}.{#-1}.get(type: STRING) == \"stringVal\"", expected); // Nested List Rank Value expected = Exp.eq( @@ -278,7 +278,7 @@ void nestedListsWithDifferentContextTypes() { CTX.mapRank(-1) ), Exp.val(200)); - parseExpressionAndCompare("$.mapBin1.{5}.{#-1}.{=100} == 200", expected); + parseExpAndCompare("$.mapBin1.{5}.{#-1}.{=100} == 200", expected); } @Test @@ -288,8 +288,8 @@ void mapKeyRange() { Exp.val("a"), Exp.val("c"), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{a-c}", expected); - parseExpressionAndCompare("$.mapBin1.{\"a\"-\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{a-c}", expected); + parseExpAndCompare("$.mapBin1.{\"a\"-\"c\"}", expected); // Inverted expected = MapExp.getByKeyRange( @@ -297,8 +297,8 @@ void mapKeyRange() { Exp.val("a"), Exp.val("c"), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!a-c}", expected); - parseExpressionAndCompare("$.mapBin1.{!\"a\"-\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{!a-c}", expected); + parseExpAndCompare("$.mapBin1.{!\"a\"-\"c\"}", expected); // From start till the end expected = MapExp.getByKeyRange( @@ -306,8 +306,8 @@ void mapKeyRange() { Exp.val("a"), null, Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{a-}", expected); - parseExpressionAndCompare("$.mapBin1.{\"a\"-}", expected); + parseExpAndCompare("$.mapBin1.{a-}", expected); + parseExpAndCompare("$.mapBin1.{\"a\"-}", expected); } @Test @@ -316,16 +316,16 @@ void mapKeyList() { MapReturnType.VALUE, Exp.val(List.of("a", "b", "c")), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{a,b,c}", expected); - parseExpressionAndCompare("$.mapBin1.{\"a\",\"b\",\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{a,b,c}", expected); + parseExpAndCompare("$.mapBin1.{\"a\",\"b\",\"c\"}", expected); // Inverted expected = MapExp.getByKeyList( MapReturnType.VALUE | MapReturnType.INVERTED, Exp.val(List.of("a", "b", "c")), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!a,b,c}", expected); - parseExpressionAndCompare("$.mapBin1.{!\"a\",\"b\",\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{!a,b,c}", expected); + parseExpAndCompare("$.mapBin1.{!\"a\",\"b\",\"c\"}", expected); } @Test @@ -335,7 +335,7 @@ void mapIndexRange() { Exp.val(1), Exp.val(2), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{1:3}", expected); + parseExpAndCompare("$.mapBin1.{1:3}", expected); // Negative expected = MapExp.getByIndexRange( @@ -343,7 +343,7 @@ void mapIndexRange() { Exp.val(-3), Exp.val(4), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{-3:1}", expected); + parseExpAndCompare("$.mapBin1.{-3:1}", expected); // Inverted expected = MapExp.getByIndexRange( @@ -351,14 +351,14 @@ void mapIndexRange() { Exp.val(2), Exp.val(2), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!2:4}", expected); + parseExpAndCompare("$.mapBin1.{!2:4}", expected); // From start till the end expected = MapExp.getByIndexRange( MapReturnType.VALUE, Exp.val(1), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{1:}", expected); + parseExpAndCompare("$.mapBin1.{1:}", expected); } @Test @@ -367,23 +367,23 @@ void mapValueList() { MapReturnType.VALUE, Exp.val(List.of("a", "b", "c")), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{=a,b,c}", expected); - parseExpressionAndCompare("$.mapBin1.{=\"a\",\"b\",\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{=a,b,c}", expected); + parseExpAndCompare("$.mapBin1.{=\"a\",\"b\",\"c\"}", expected); // Integer expected = MapExp.getByValueList( MapReturnType.VALUE, Exp.val(List.of(1, 2, 3)), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{=1,2,3}", expected); + parseExpAndCompare("$.mapBin1.{=1,2,3}", expected); // Inverted expected = MapExp.getByValueList( MapReturnType.VALUE | MapReturnType.INVERTED, Exp.val(List.of("a", "b", "c")), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!=a,b,c}", expected); - parseExpressionAndCompare("$.mapBin1.{!=\"a\",\"b\",\"c\"}", expected); + parseExpAndCompare("$.mapBin1.{!=a,b,c}", expected); + parseExpAndCompare("$.mapBin1.{!=\"a\",\"b\",\"c\"}", expected); } @Test @@ -393,7 +393,7 @@ void mapValueRange() { Exp.val(111), Exp.val(334), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{=111:334}", expected); + parseExpAndCompare("$.mapBin1.{=111:334}", expected); // Inverted expected = MapExp.getByValueRange( @@ -401,7 +401,7 @@ void mapValueRange() { Exp.val(10), Exp.val(20), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!=10:20}", expected); + parseExpAndCompare("$.mapBin1.{!=10:20}", expected); // From start till the end expected = MapExp.getByValueRange( @@ -409,7 +409,7 @@ void mapValueRange() { Exp.val(111), null, Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{=111:}", expected); + parseExpAndCompare("$.mapBin1.{=111:}", expected); } @Test @@ -419,7 +419,7 @@ void mapRankRange() { Exp.val(0), Exp.val(3), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{#0:3}", expected); + parseExpAndCompare("$.mapBin1.{#0:3}", expected); // Inverted expected = MapExp.getByRankRange( @@ -427,14 +427,14 @@ void mapRankRange() { Exp.val(0), Exp.val(3), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!#0:3}", expected); + parseExpAndCompare("$.mapBin1.{!#0:3}", expected); // From start till the end expected = MapExp.getByRankRange( MapReturnType.VALUE, Exp.val(-3), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{#-3:}", expected); + parseExpAndCompare("$.mapBin1.{#-3:}", expected); // From start till the end with context expected = MapExp.getByRankRange( @@ -442,7 +442,7 @@ void mapRankRange() { Exp.val(-3), Exp.mapBin("mapBin1"), CTX.mapIndex(5)); - parseExpressionAndCompare("$.mapBin1.{5}.{#-3:}", expected); + parseExpAndCompare("$.mapBin1.{5}.{#-3:}", expected); } @Test @@ -453,7 +453,7 @@ void mapRankRangeRelative() { Exp.val(10), Exp.val(2), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{#-1:1~10}", expected); + parseExpAndCompare("$.mapBin1.{#-1:1~10}", expected); // Inverted expected = MapExp.getByValueRelativeRankRange( @@ -462,7 +462,7 @@ void mapRankRangeRelative() { Exp.val(10), Exp.val(2), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!#-1:1~10}", expected); + parseExpAndCompare("$.mapBin1.{!#-1:1~10}", expected); // From start till the end expected = MapExp.getByValueRelativeRankRange( @@ -470,7 +470,7 @@ void mapRankRangeRelative() { Exp.val(-2), Exp.val(10), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{#-2:~10}", expected); + parseExpAndCompare("$.mapBin1.{#-2:~10}", expected); } @Test @@ -481,7 +481,7 @@ void mapIndexRangeRelative() { Exp.val(0), Exp.val(1), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{0:1~a}", expected); + parseExpAndCompare("$.mapBin1.{0:1~a}", expected); // Inverted expected = MapExp.getByKeyRelativeIndexRange( @@ -490,7 +490,7 @@ void mapIndexRangeRelative() { Exp.val(0), Exp.val(1), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{!0:1~a}", expected); + parseExpAndCompare("$.mapBin1.{!0:1~a}", expected); // From start till the end expected = MapExp.getByKeyRelativeIndexRange( @@ -498,7 +498,7 @@ void mapIndexRangeRelative() { Exp.val("a"), Exp.val(0), Exp.mapBin("mapBin1")); - parseExpressionAndCompare("$.mapBin1.{0:~a}", expected); + parseExpAndCompare("$.mapBin1.{0:~a}", expected); } @Test @@ -512,7 +512,7 @@ void mapReturnTypes() { ), Exp.val(5)); // Implicit detect as Int - parseExpressionAndCompare("$.mapBin1.a.get(type: INT, return: COUNT) == 5", expected); + parseExpAndCompare("$.mapBin1.a.get(type: INT, return: COUNT) == 5", expected); expected = MapExp.getByKey( MapReturnType.ORDERED_MAP, @@ -521,7 +521,7 @@ void mapReturnTypes() { Exp.mapBin("mapBin1") ); // Implicit detect as Int - parseExpressionAndCompare("$.mapBin1.a.get(return: ORDERED_MAP)", expected); + parseExpAndCompare("$.mapBin1.a.get(return: ORDERED_MAP)", expected); expected = Exp.eq( MapExp.getByKey( @@ -532,6 +532,6 @@ void mapReturnTypes() { ), Exp.val(5)); // Implicit detect as Int - parseExpressionAndCompare("$.mapBin1.a.get(type: INT, return: RANK) == 5", expected); + parseExpAndCompare("$.mapBin1.a.get(type: INT, return: RANK) == 5", expected); } } diff --git a/src/test/java/com/aerospike/dsl/expression/RecordMetadataTests.java b/src/test/java/com/aerospike/dsl/expression/RecordMetadataTests.java index ca59c28..5f7557e 100644 --- a/src/test/java/com/aerospike/dsl/expression/RecordMetadataTests.java +++ b/src/test/java/com/aerospike/dsl/expression/RecordMetadataTests.java @@ -1,11 +1,11 @@ package com.aerospike.dsl.expression; import com.aerospike.client.exp.Exp; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseExpression; -import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseExp; +import static com.aerospike.dsl.util.TestUtils.parseExpAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; class RecordMetadataTests { @@ -15,7 +15,7 @@ void deviceSize() { // Expression to find records that occupy more than 1 MiB of storage space String input = "$.deviceSize() > 1048576"; Exp expected = Exp.gt(Exp.deviceSize(), Exp.val(1024 * 1024)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -23,7 +23,7 @@ void memorySize() { // Expression to find records that occupy more than 1 MiB of memory String input = "$.memorySize() > 1048576"; Exp expected = Exp.gt(Exp.memorySize(), Exp.val(1024 * 1024)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -31,7 +31,7 @@ void recordSize() { // Expression to find records that occupy more than 1 MiB of memory String input = "$.recordSize() > 1048576"; Exp expected = Exp.gt(Exp.recordSize(), Exp.val(1024 * 1024)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -39,12 +39,12 @@ void digestModulo() { // Expression to find records where digest mod 3 equals 0 String input = "$.digestModulo(3) == 0"; Exp expected = Exp.eq(Exp.digestModulo(3), Exp.val(0)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); // Expression to find records where digest mod 3 equals the value stored in the bin called "digestModulo" String input2 = "$.digestModulo(3) == $.digestModulo"; Exp expected2 = Exp.eq(Exp.digestModulo(3), Exp.intBin("digestModulo")); - parseExpressionAndCompare(input2, expected2); + parseExpAndCompare(input2, expected2); } @Test @@ -52,7 +52,7 @@ void isTombstone() { // Expression to find records that are tombstones String input = "$.isTombstone()"; Exp expected = Exp.isTombstone(); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -60,7 +60,7 @@ void keyExists() { // Expression to find records that has a stored key String input = "$.keyExists()"; Exp expected = Exp.keyExists(); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } // Comparing Metadata to a Bin @@ -69,12 +69,12 @@ void lastUpdate() { // Expression to find records where the last-update-time is less than bin 'updateBy' String inputMetadataLeft = "$.lastUpdate() < $.updateBy"; Exp expectedLeft = Exp.lt(Exp.lastUpdate(), Exp.intBin("updateBy")); - parseExpressionAndCompare(inputMetadataLeft, expectedLeft); + parseExpAndCompare(inputMetadataLeft, expectedLeft); // Expression to find records where the last-update-time is less than bin 'updateBy' String inputMetadataRight = "$.updateBy > $.lastUpdate()"; Exp expectedRight = Exp.gt(Exp.intBin("updateBy"), Exp.lastUpdate()); - parseExpressionAndCompare(inputMetadataRight, expectedRight); + parseExpAndCompare(inputMetadataRight, expectedRight); } @Test @@ -82,22 +82,22 @@ void sinceUpdate() { // Expression to find records that were updated within the last 2 hours String input = "$.sinceUpdate() < 7200000"; Exp expected = Exp.lt(Exp.sinceUpdate(), Exp.val(2 * 60 * 60 * 1000)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); // Expression to find records that were update within the value stored in the bin called "intBin" String input2 = "$.sinceUpdate() < $.intBin"; Exp expected2 = Exp.lt(Exp.sinceUpdate(), Exp.intBin("intBin")); - parseExpressionAndCompare(input2, expected2); + parseExpAndCompare(input2, expected2); // Expression to find records that were updated within the value stored in the bin called "sinceUpdate" String input3 = "$.sinceUpdate() < $.sinceUpdate"; Exp expected3 = Exp.lt(Exp.sinceUpdate(), Exp.intBin("sinceUpdate")); - parseExpressionAndCompare(input3, expected3); + parseExpAndCompare(input3, expected3); // Expression to find records that were updated within the value stored in the bin called "sinceUpdate" String input4 = "$.sinceUpdate > $.sinceUpdate()"; Exp expected4 = Exp.gt(Exp.intBin("sinceUpdate"), Exp.sinceUpdate()); - parseExpressionAndCompare(input4, expected4); + parseExpAndCompare(input4, expected4); } @Test @@ -108,12 +108,12 @@ void setName() { Exp.eq(Exp.setName(), Exp.val("groupA")), Exp.eq(Exp.setName(), Exp.val("groupB")) ); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); // set name compared with String Bin input = "$.mySetBin == $.setName()"; expected = Exp.eq(Exp.stringBin("mySetBin"), Exp.setName()); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -121,14 +121,14 @@ void ttl() { // Expression to find records that will expire within 24 hours String input = "$.ttl() <= 86400"; Exp expected = Exp.le(Exp.ttl(), Exp.val(24 * 60 * 60)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } //@Test void negativeTtlAsDifferentType() { // TODO: should be supported when adding operator + metadata validations (requires a refactor) - assertThatThrownBy(() -> parseExpression("$.ttl() == true")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseExp("$.ttl() == true")) + .isInstanceOf(DslParseException.class) .hasMessageContaining("Expecting non-bin operand, got BOOL_OPERAND"); } @@ -137,7 +137,7 @@ void voidTime() { // Expression to find records where the void-time is set to 'never expire' String input = "$.voidTime() == -1"; Exp expected = Exp.eq(Exp.voidTime(), Exp.val(-1)); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } @Test @@ -148,7 +148,7 @@ void metadataWithLogicalOperatorsExpressions() { Exp.gt(Exp.deviceSize(), Exp.val(1024)), Exp.lt(Exp.ttl(), Exp.val(300)) ); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); // test OR String input2 = "$.deviceSize() > 1024 or $.ttl() < 300"; @@ -156,7 +156,7 @@ void metadataWithLogicalOperatorsExpressions() { Exp.gt(Exp.deviceSize(), Exp.val(1024)), Exp.lt(Exp.ttl(), Exp.val(300)) ); - parseExpressionAndCompare(input2, expected2); + parseExpAndCompare(input2, expected2); } @Test @@ -166,13 +166,13 @@ void metadataAsExpressionWithLogicalOperator() { Exp.isTombstone(), Exp.lt(Exp.ttl(), Exp.val(300)) ); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); input = "$.ttl() < 300 or $.keyExists()"; expected = Exp.or( Exp.lt(Exp.ttl(), Exp.val(300)), Exp.keyExists() ); - parseExpressionAndCompare(input, expected); + parseExpAndCompare(input, expected); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java index c5e3cef..f2aa56d 100644 --- a/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java @@ -1,376 +1,319 @@ package com.aerospike.dsl.filter; import com.aerospike.client.query.Filter; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.client.query.IndexType; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.IndexContext; +import com.aerospike.dsl.DslParseException; import org.junit.jupiter.api.Test; +import java.util.Collection; import java.util.List; -import static com.aerospike.dsl.util.TestUtils.parseFilters; -import static com.aerospike.dsl.util.TestUtils.parseFiltersAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseFilter; +import static com.aerospike.dsl.util.TestUtils.parseFilterAndCompare; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ArithmeticFiltersTests { + String NAMESPACE = "test1"; + Collection INDEXES = List.of( + Index.builder().namespace("test1").bin("apples").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("bananas").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + ); + IndexContext INDEX_FILTER_INPUT = IndexContext.of(NAMESPACE, INDEXES); + @Test void add() { - assertThatThrownBy(() -> parseFilters("($.apples + $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - parseFiltersAndCompare("($.apples + 5) > 10", - List.of(Filter.range("apples", 10 - 5 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples + 5) >= 10", - List.of(Filter.range("apples", 10 - 5, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples + 5) < 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 - 5 - 1))); - parseFiltersAndCompare("($.apples + 5) <= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 - 5))); - - parseFiltersAndCompare("(9 + $.bananas) > 10", - List.of(Filter.range("bananas", 10 - 9 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("(9 + $.bananas) >= 10", - List.of(Filter.range("bananas", 10 - 9, Long.MAX_VALUE))); - parseFiltersAndCompare("(9 + $.bananas) < 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 10 - 9 - 1))); - parseFiltersAndCompare("(9 + $.bananas) <= 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 10 - 9))); - - assertThatThrownBy(() -> parseFilters("(5.2 + $.bananas) > 10.2")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("($.apples + $.bananas + 5) > 10")) - .isInstanceOf(AerospikeDSLException.class) // not supported by the current grammar - .hasMessageContaining("Could not parse given input, wrong syntax"); + // not supported by secondary index filter + assertThat(parseFilter("($.apples + $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); + + parseFilterAndCompare("($.apples + 5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 - 5 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples + 5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 - 5, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples + 5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 - 5 - 1)); + parseFilterAndCompare("($.apples + 5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 - 5)); + + parseFilterAndCompare("(9 + $.bananas) > 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 10 - 9 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("(9 + $.bananas) >= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 10 - 9, Long.MAX_VALUE)); + parseFilterAndCompare("(9 + $.bananas) < 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 10 - 9 - 1)); + parseFilterAndCompare("(9 + $.bananas) <= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 10 - 9)); + + assertThat(parseFilter("(5.2 + $.bananas) > 10.2")).isNull(); // not supported by secondary index filter + assertThatThrownBy(() -> parseFilter("($.apples + $.bananas + 5) > 10")) + .isInstanceOf(DslParseException.class) // not supported by the current grammar + .hasMessageContaining("Could not parse given DSL expression input"); } @Test void sub() { - assertThatThrownBy(() -> parseFilters("($.apples - $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - parseFiltersAndCompare("($.apples - 5) > 10", - List.of(Filter.range("apples", 10 + 5 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples - 5) >= 10", - List.of(Filter.range("apples", 10 + 5, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples - 5) < 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 + 5 - 1))); - parseFiltersAndCompare("($.apples - 5) <= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 + 5))); - - parseFiltersAndCompare("($.apples - 5) > -10", - List.of(Filter.range("apples", -10 + 5 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples - 5) >= -10", - List.of(Filter.range("apples", -10 + 5, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples - 5) < -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -10 + 5 - 1))); - parseFiltersAndCompare("($.apples - 5) <= -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -10 + 5))); - - parseFiltersAndCompare("(9 - $.bananas) > 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 9 - 10 - 1))); - parseFiltersAndCompare("(9 - $.bananas) >= 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 9 - 10))); - parseFiltersAndCompare("(9 - $.bananas) < 10", - List.of(Filter.range("bananas", 9 - 10 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("(9 - $.bananas) <= 10", - List.of(Filter.range("bananas", 9 - 10, Long.MAX_VALUE))); - - assertThatThrownBy(() -> parseFilters("($.apples - $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("($.apples - $.bananas - 5) > 10")) - .isInstanceOf(AerospikeDSLException.class) // not supported by the current grammar - .hasMessageContaining("Could not parse given input, wrong syntax"); + assertThat(parseFilter("($.apples - $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // not supported by secondary index filter + + parseFilterAndCompare("($.apples - 5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 + 5 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples - 5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 + 5, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples - 5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 + 5 - 1)); + parseFilterAndCompare("($.apples - 5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 + 5)); + + parseFilterAndCompare("($.apples - 5) > -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 + 5 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples - 5) >= -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 + 5, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples - 5) < -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -10 + 5 - 1)); + parseFilterAndCompare("($.apples - 5) <= -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -10 + 5)); + + parseFilterAndCompare("(9 - $.bananas) > 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 9 - 10 - 1)); + parseFilterAndCompare("(9 - $.bananas) >= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 9 - 10)); + parseFilterAndCompare("(9 - $.bananas) < 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 9 - 10 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("(9 - $.bananas) <= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 9 - 10, Long.MAX_VALUE)); + + assertThat(parseFilter("($.apples - $.bananas) > 10")).isNull(); // not supported by secondary index filter + assertThatThrownBy(() -> parseFilter("($.apples - $.bananas - 5) > 10")) + .isInstanceOf(DslParseException.class) // not supported by the current grammar + .hasMessageContaining("Could not parse given DSL expression input"); } @Test void mul() { - assertThatThrownBy(() -> parseFilters("($.apples * $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - parseFiltersAndCompare("($.apples * 5) > 10", - List.of(Filter.range("apples", 10 / 5 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples * 5) >= 10", - List.of(Filter.range("apples", 10 / 5, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples * 5) < 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 / 5 - 1))); - parseFiltersAndCompare("($.apples * 5) <= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 / 5))); - - parseFiltersAndCompare("(9 * $.bananas) > 10", - List.of(Filter.range("bananas", 10 / 9 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("(9 * $.bananas) >= 10", - List.of(Filter.range("bananas", 10 / 9, Long.MAX_VALUE))); - parseFiltersAndCompare("(9 * $.bananas) < 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 10 / 9))); - parseFiltersAndCompare("(9 * $.bananas) <= 10", - List.of(Filter.range("bananas", Long.MIN_VALUE, 10 / 9))); - - parseFiltersAndCompare("($.apples * -5) > 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 / -5 - 1))); - parseFiltersAndCompare("($.apples * -5) >= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 10 / -5))); - parseFiltersAndCompare("($.apples * -5) < 10", - List.of(Filter.range("apples", 10 / -5 + 1, Long.MAX_VALUE))); - parseFiltersAndCompare("($.apples * -5) <= 10", - List.of(Filter.range("apples", 10 / -5, Long.MAX_VALUE))); - - assertThatThrownBy(() -> parseFilters("(0 * $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) // not supported by the current grammar - .hasMessageContaining("Cannot divide by zero"); - - parseFiltersAndCompare("(9 * $.bananas) > 0", - List.of(Filter.range("bananas", 0 / 9 + 1, Long.MAX_VALUE))); - - assertThatThrownBy(() -> parseFilters("($.apples * $.bananas - 5) > 10")) - .isInstanceOf(AerospikeDSLException.class) // not supported by the current grammar - .hasMessageContaining("Could not parse given input, wrong syntax"); + assertThat(parseFilter("($.apples * $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // not supported by secondary index filter + + parseFilterAndCompare("($.apples * 5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 / 5 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples * 5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 / 5, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples * 5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 / 5 - 1)); + parseFilterAndCompare("($.apples * 5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 / 5)); + + parseFilterAndCompare("(9 * $.bananas) > 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 10 / 9 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("(9 * $.bananas) >= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 10 / 9, Long.MAX_VALUE)); + parseFilterAndCompare("(9 * $.bananas) < 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 10 / 9)); + parseFilterAndCompare("(9 * $.bananas) <= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", Long.MIN_VALUE, 10 / 9)); + + parseFilterAndCompare("($.apples * -5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 / -5 - 1)); + parseFilterAndCompare("($.apples * -5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 10 / -5)); + parseFilterAndCompare("($.apples * -5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 / -5 + 1, Long.MAX_VALUE)); + parseFilterAndCompare("($.apples * -5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 10 / -5, Long.MAX_VALUE)); + + assertThat(parseFilter("(0 * $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // Cannot divide by zero + + parseFilterAndCompare("(9 * $.bananas) > 0", INDEX_FILTER_INPUT, + Filter.range("bananas", 0 / 9 + 1, Long.MAX_VALUE)); + + assertThatThrownBy(() -> parseFilter("($.apples * $.bananas - 5) > 10")) + .isInstanceOf(DslParseException.class) // not supported by the current grammar + .hasMessageContaining("Could not parse given DSL expression input"); } @Test void div_twoBins() { - assertThatThrownBy(() -> parseFilters("($.apples / $.bananas) <= 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); + assertThat(parseFilter("($.apples / $.bananas) <= 10", INDEX_FILTER_INPUT)).isNull(); // not supported by secondary index filter } @Test void div_binIsDivided_leftNumberIsLarger() { - parseFiltersAndCompare("($.apples / 50) > 10", - List.of(Filter.range("apples", 50 * 10 + 1, Long.MAX_VALUE))); // [501, 2^63 - 1] - parseFiltersAndCompare("($.apples / 50) >= 10", - List.of(Filter.range("apples", 50 * 10, Long.MAX_VALUE))); // [500, 2^63 - 1] - parseFiltersAndCompare("($.apples / 50) < 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 50 * 10 - 1))); // [-2^63, 499] - parseFiltersAndCompare("($.apples / 50) <= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 50 * 10))); // [-2^63, 500] - - parseFiltersAndCompare("($.apples / -50) > 10", - List.of(Filter.range("apples", Long.MIN_VALUE, -50 * 10 - 1))); // [-2^63, -501] - parseFiltersAndCompare("($.apples / -50) >= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, -50 * 10))); // [-2^63, -500] - parseFiltersAndCompare("($.apples / -50) < 10", - List.of(Filter.range("apples", -50 * 10 + 1, Long.MAX_VALUE))); // [-499, 2^63 - 1] - parseFiltersAndCompare("($.apples / -50) <= 10", - List.of(Filter.range("apples", -50 * 10, Long.MAX_VALUE))); // [-500, 2^63 - 1] - - parseFiltersAndCompare("($.apples / 50) > -10", - List.of(Filter.range("apples", 50 * -10 + 1, Long.MAX_VALUE))); // [-499, 2^63 - 1] - parseFiltersAndCompare("($.apples / 50) >= -10", - List.of(Filter.range("apples", 50 * -10, Long.MAX_VALUE))); // [-500, 2^63 - 1] - parseFiltersAndCompare("($.apples / 50) < -10", - List.of(Filter.range("apples", Long.MIN_VALUE, 50 * -10 - 1))); // [-2^63, -501] - parseFiltersAndCompare("($.apples / 50) <= -10", - List.of(Filter.range("apples", Long.MIN_VALUE, 50 * -10))); // [-2^63, -500] - - parseFiltersAndCompare("($.apples / -50) > -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -50 * -10 - 1))); // [-2^63, 499] - parseFiltersAndCompare("($.apples / -50) >= -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -10 * -50))); // [-2^63, 500] - parseFiltersAndCompare("($.apples / -50) < -10", - List.of(Filter.range("apples", -10 * -50 + 1, Long.MAX_VALUE))); // [501, 2^63 - 1] - parseFiltersAndCompare("($.apples / -50) <= -10", - List.of(Filter.range("apples", -10 * -50, Long.MAX_VALUE))); // [500, 2^63 - 1] + parseFilterAndCompare("($.apples / 50) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", 50 * 10 + 1, Long.MAX_VALUE)); // [501, 2^63 - 1] + parseFilterAndCompare("($.apples / 50) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 50 * 10, Long.MAX_VALUE)); // [500, 2^63 - 1] + parseFilterAndCompare("($.apples / 50) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 50 * 10 - 1)); // [-2^63, 499] + parseFilterAndCompare("($.apples / 50) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 50 * 10)); // [-2^63, 500] + + parseFilterAndCompare("($.apples / -50) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -50 * 10 - 1)); // [-2^63, -501] + parseFilterAndCompare("($.apples / -50) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -50 * 10)); // [-2^63, -500] + parseFilterAndCompare("($.apples / -50) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", -50 * 10 + 1, Long.MAX_VALUE)); // [-499, 2^63 - 1] + parseFilterAndCompare("($.apples / -50) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", -50 * 10, Long.MAX_VALUE)); // [-500, 2^63 - 1] + + parseFilterAndCompare("($.apples / 50) > -10", INDEX_FILTER_INPUT, + Filter.range("apples", 50 * -10 + 1, Long.MAX_VALUE)); // [-499, 2^63 - 1] + parseFilterAndCompare("($.apples / 50) >= -10", INDEX_FILTER_INPUT, + Filter.range("apples", 50 * -10, Long.MAX_VALUE)); // [-500, 2^63 - 1] + parseFilterAndCompare("($.apples / 50) < -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 50 * -10 - 1)); // [-2^63, -501] + parseFilterAndCompare("($.apples / 50) <= -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 50 * -10)); // [-2^63, -500] + + parseFilterAndCompare("($.apples / -50) > -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -50 * -10 - 1)); // [-2^63, 499] + parseFilterAndCompare("($.apples / -50) >= -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -10 * -50)); // [-2^63, 500] + parseFilterAndCompare("($.apples / -50) < -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 * -50 + 1, Long.MAX_VALUE)); // [501, 2^63 - 1] + parseFilterAndCompare("($.apples / -50) <= -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 * -50, Long.MAX_VALUE)); // [500, 2^63 - 1] } @Test void div_binIsDivided_leftNumberIsSmaller() { - parseFiltersAndCompare("($.apples / 5) > 10", - List.of(Filter.range("apples", 5 * 10 + 1, Long.MAX_VALUE))); // [51, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) >= 10", - List.of(Filter.range("apples", 5 * 10, Long.MAX_VALUE))); // [50, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) < 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * 10 - 1))); // [-2^63, 49] - parseFiltersAndCompare("($.apples / 5) <= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * 10))); // [-2^63, 50] - - parseFiltersAndCompare("($.apples / -5) > 10", - List.of(Filter.range("apples", Long.MIN_VALUE, -5 * 10 - 1))); // [-2^63, -51] - parseFiltersAndCompare("($.apples / -5) >= 10", - List.of(Filter.range("apples", Long.MIN_VALUE, -5 * 10))); // [-2^63, -50] - parseFiltersAndCompare("($.apples / -5) < 10", - List.of(Filter.range("apples", -5 * 10 + 1, Long.MAX_VALUE))); // [-49, 2^63 - 1] - parseFiltersAndCompare("($.apples / -5) <= 10", - List.of(Filter.range("apples", -5 * 10, Long.MAX_VALUE))); // [-50, 2^63 - 1] - - parseFiltersAndCompare("($.apples / 5) > -10", - List.of(Filter.range("apples", 5 * -10 + 1, Long.MAX_VALUE))); // [-49, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) >= -10", - List.of(Filter.range("apples", 5 * -10, Long.MAX_VALUE))); // [-50, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) < -10", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * -10 - 1))); // [-2^63, -51] - parseFiltersAndCompare("($.apples / 5) <= -10", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * -10))); // [-2^63, -50] - - parseFiltersAndCompare("($.apples / -5) > -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -5 * -10 - 1))); // [-2^63, 49] - parseFiltersAndCompare("($.apples / -5) >= -10", - List.of(Filter.range("apples", Long.MIN_VALUE, -10 * -5))); // [-2^63, 50] - parseFiltersAndCompare("($.apples / -5) < -10", - List.of(Filter.range("apples", -10 * -5 + 1, Long.MAX_VALUE))); // [51, 2^63 - 1] - parseFiltersAndCompare("($.apples / -5) <= -10", - List.of(Filter.range("apples", -10 * -5, Long.MAX_VALUE))); // [50, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * 10 + 1, Long.MAX_VALUE)); // [51, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * 10, Long.MAX_VALUE)); // [50, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * 10 - 1)); // [-2^63, 49] + parseFilterAndCompare("($.apples / 5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * 10)); // [-2^63, 50] + + parseFilterAndCompare("($.apples / -5) > 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -5 * 10 - 1)); // [-2^63, -51] + parseFilterAndCompare("($.apples / -5) >= 10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -5 * 10)); // [-2^63, -50] + parseFilterAndCompare("($.apples / -5) < 10", INDEX_FILTER_INPUT, + Filter.range("apples", -5 * 10 + 1, Long.MAX_VALUE)); // [-49, 2^63 - 1] + parseFilterAndCompare("($.apples / -5) <= 10", INDEX_FILTER_INPUT, + Filter.range("apples", -5 * 10, Long.MAX_VALUE)); // [-50, 2^63 - 1] + + parseFilterAndCompare("($.apples / 5) > -10", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * -10 + 1, Long.MAX_VALUE)); // [-49, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) >= -10", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * -10, Long.MAX_VALUE)); // [-50, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) < -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * -10 - 1)); // [-2^63, -51] + parseFilterAndCompare("($.apples / 5) <= -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * -10)); // [-2^63, -50] + + parseFilterAndCompare("($.apples / -5) > -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -5 * -10 - 1)); // [-2^63, 49] + parseFilterAndCompare("($.apples / -5) >= -10", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -10 * -5)); // [-2^63, 50] + parseFilterAndCompare("($.apples / -5) < -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 * -5 + 1, Long.MAX_VALUE)); // [51, 2^63 - 1] + parseFilterAndCompare("($.apples / -5) <= -10", INDEX_FILTER_INPUT, + Filter.range("apples", -10 * -5, Long.MAX_VALUE)); // [50, 2^63 - 1] } @Test void div_binIsDivided_leftNumberEqualsRight() { - parseFiltersAndCompare("($.apples / 5) > 5", - List.of(Filter.range("apples", 5 * 5 + 1, Long.MAX_VALUE))); // [26, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) >= 5", - List.of(Filter.range("apples", 5 * 5, Long.MAX_VALUE))); // [25, 2^63 - 1] - parseFiltersAndCompare("($.apples / 5) < 5", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * 5 - 1))); // [-2^63, 24] - parseFiltersAndCompare("($.apples / 5) <= 5", - List.of(Filter.range("apples", Long.MIN_VALUE, 5 * 5))); // [-2^63, 25] - - parseFiltersAndCompare("($.apples / -5) > -5", - List.of(Filter.range("apples", Long.MIN_VALUE, -5 * -5 - 1))); // [-2^63, 24] - parseFiltersAndCompare("($.apples / -5) >= -5", - List.of(Filter.range("apples", Long.MIN_VALUE, -5 * -5))); // [-2^63, 25] - parseFiltersAndCompare("($.apples / -5) < -5", - List.of(Filter.range("apples", -5 * -5 + 1, Long.MAX_VALUE))); // [26, 2^63 - 1] - parseFiltersAndCompare("($.apples / -5) <= -5", - List.of(Filter.range("apples", -5 * -5, Long.MAX_VALUE))); // [25, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) > 5", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * 5 + 1, Long.MAX_VALUE)); // [26, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) >= 5", INDEX_FILTER_INPUT, + Filter.range("apples", 5 * 5, Long.MAX_VALUE)); // [25, 2^63 - 1] + parseFilterAndCompare("($.apples / 5) < 5", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * 5 - 1)); // [-2^63, 24] + parseFilterAndCompare("($.apples / 5) <= 5", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, 5 * 5)); // [-2^63, 25] + + parseFilterAndCompare("($.apples / -5) > -5", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -5 * -5 - 1)); // [-2^63, 24] + parseFilterAndCompare("($.apples / -5) >= -5", INDEX_FILTER_INPUT, + Filter.range("apples", Long.MIN_VALUE, -5 * -5)); // [-2^63, 25] + parseFilterAndCompare("($.apples / -5) < -5", INDEX_FILTER_INPUT, + Filter.range("apples", -5 * -5 + 1, Long.MAX_VALUE)); // [26, 2^63 - 1] + parseFilterAndCompare("($.apples / -5) <= -5", INDEX_FILTER_INPUT, + Filter.range("apples", -5 * -5, Long.MAX_VALUE)); // [25, 2^63 - 1] } @Test void div_binIsDivisor_leftNumberIsLarger() { - parseFiltersAndCompare("(90 / $.bananas) > 10", - List.of(Filter.range("bananas", 1, 90 / 10 - 1))); // [1,8] - parseFiltersAndCompare("(90 / $.bananas) >= 10", - List.of(Filter.range("bananas", 1, 90 / 10))); // [1,9] - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) < 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) <= 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) > -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) >= -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - parseFiltersAndCompare("(90 / $.bananas) < -10", - List.of(Filter.range("bananas", 90 / -10 + 1, -1))); // [-8, -1] - parseFiltersAndCompare("(90 / $.bananas) <= -10", - List.of(Filter.range("bananas", 90 / -10, -1))); // [-8, -1] - - parseFiltersAndCompare("(-90 / $.bananas) > 10", - List.of(Filter.range("bananas", -90 / 10 + 1, -1))); // [-8, -1] - parseFiltersAndCompare("(90 / $.bananas) >= 10", - List.of(Filter.range("bananas", 1, 90 / 10))); // [1,9] - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) < 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) <= 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) > -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) >= -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - parseFiltersAndCompare("(-90 / $.bananas) < -10", - List.of(Filter.range("bananas", 1L, -90 / -10 - 1))); // [1, 8] - parseFiltersAndCompare("(-90 / $.bananas) <= -10", - List.of(Filter.range("bananas", 1L, -90 / -10))); // [1, 9] + parseFilterAndCompare("(90 / $.bananas) > 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 1, 90 / 10 - 1)); // [1,8] + parseFilterAndCompare("(90 / $.bananas) >= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 1, 90 / 10)); // [1,9] + + // Not supported by secondary index filter + assertThat(parseFilter("(90 / $.bananas) < 10", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("(90 / $.bananas) <= 10", INDEX_FILTER_INPUT)).isNull(); + + // Not supported by secondary index filter + assertThat(parseFilter("(90 / $.bananas) > -10", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("(90 / $.bananas) >= -10", INDEX_FILTER_INPUT)).isNull(); + parseFilterAndCompare("(90 / $.bananas) < -10", INDEX_FILTER_INPUT, + Filter.range("bananas", 90 / -10 + 1, -1)); // [-8, -1] + parseFilterAndCompare("(90 / $.bananas) <= -10", INDEX_FILTER_INPUT, + Filter.range("bananas", 90 / -10, -1)); // [-8, -1] + + parseFilterAndCompare("(-90 / $.bananas) > 10", INDEX_FILTER_INPUT, + Filter.range("bananas", -90 / 10 + 1, -1)); // [-8, -1] + parseFilterAndCompare("(90 / $.bananas) >= 10", INDEX_FILTER_INPUT, + Filter.range("bananas", 1, 90 / 10)); // [1,9] + // Not supported by secondary index filter + assertThat(parseFilter("(-90 / $.bananas) < 10", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("(-90 / $.bananas) <= 10", INDEX_FILTER_INPUT)).isNull(); + + // Not supported by secondary index filter + assertThat(parseFilter("(-90 / $.bananas) > -10", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("(-90 / $.bananas) >= -10", INDEX_FILTER_INPUT)).isNull(); + parseFilterAndCompare("(-90 / $.bananas) < -10", INDEX_FILTER_INPUT, + Filter.range("bananas", 1L, -90 / -10 - 1)); // [1, 8] + parseFilterAndCompare("(-90 / $.bananas) <= -10", INDEX_FILTER_INPUT, + Filter.range("bananas", 1L, -90 / -10)); // [1, 9] } @Test void div_binIsDivisor_leftNumberIsSmaller() { - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) > 10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) >= 10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) < 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) <= 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) > -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) >= -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) < -10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) <= -10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) > 10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) >= 10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) < 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) <= 10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) > -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) >= -10")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) < -10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-9 / $.bananas) <= -10")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(0 / $.bananas) > 10")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(9 / $.bananas) > 0")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Cannot divide by zero"); + // Not supported by secondary index filter + assertThat(parseFilter("(9 / $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(9 / $.bananas) >= 10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(9 / $.bananas) < 10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(9 / $.bananas) <= 10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + + assertThat(parseFilter("(9 / $.bananas) > -10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(9 / $.bananas) >= -10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(9 / $.bananas) < -10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(9 / $.bananas) <= -10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + + assertThat(parseFilter("(-9 / $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(-9 / $.bananas) >= 10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(-9 / $.bananas) < 10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(-9 / $.bananas) <= 10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + + assertThat(parseFilter("(-9 / $.bananas) > -10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(-9 / $.bananas) >= -10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(-9 / $.bananas) < -10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + assertThat(parseFilter("(-9 / $.bananas) <= -10", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + + // Not supported by secondary index Filter + assertThat(parseFilter("(0 / $.bananas) > 10", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + + // Not supported by secondary index Filter, cannot divide by zero + assertThat(parseFilter("(9 / $.bananas) > 0", INDEX_FILTER_INPUT)).isNull(); // no integer numbers } @Test void div_binIsDivisor_leftNumberEqualsRight() { - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) > 90")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - parseFiltersAndCompare("(90 / $.bananas) >= 90", - List.of(Filter.range("bananas", 90 / 90, 90 / 90))); // [1, 1] - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) < 90")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(90 / $.bananas) <= 90")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) > -90")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) >= -90")) // maximal range is all numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("(-90 / $.bananas) < -90")) // no integer numbers - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - parseFiltersAndCompare("(-90 / $.bananas) <= -90", - List.of(Filter.range("bananas", 1L, 90 / 90))); // [1, 1] + // Not supported by secondary index filter + assertThat(parseFilter("(90 / $.bananas) > 90", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + parseFilterAndCompare("(90 / $.bananas) >= 90", INDEX_FILTER_INPUT, + Filter.range("bananas", 90 / 90, 90 / 90)); // [1, 1] + assertThat(parseFilter("(90 / $.bananas) < 90", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(90 / $.bananas) <= 90", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + + assertThat(parseFilter("(-90 / $.bananas) > -90", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(-90 / $.bananas) >= -90", INDEX_FILTER_INPUT)).isNull(); // maximal range is all numbers + assertThat(parseFilter("(-90 / $.bananas) < -90", INDEX_FILTER_INPUT)).isNull(); // no integer numbers + parseFilterAndCompare("(-90 / $.bananas) <= -90", INDEX_FILTER_INPUT, + Filter.range("bananas", 1L, 90 / 90)); // [1, 1] } } diff --git a/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java index 1161383..091cc94 100644 --- a/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java @@ -1,105 +1,109 @@ package com.aerospike.dsl.filter; import com.aerospike.client.query.Filter; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.client.query.IndexType; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.IndexContext; import org.junit.jupiter.api.Test; import java.util.List; -import static com.aerospike.dsl.util.TestUtils.parseFilters; -import static com.aerospike.dsl.util.TestUtils.parseFiltersAndCompare; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static com.aerospike.dsl.util.TestUtils.parseFilter; +import static com.aerospike.dsl.util.TestUtils.parseFilterAndCompare; +import static org.assertj.core.api.Assertions.assertThat; class BinFiltersTests { + String NAMESPACE = "test1"; + List INDEXES = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("stringBin1").indexType(IndexType.STRING).binValuesRatio(1).build() + ); + IndexContext INDEX_FILTER_INPUT = IndexContext.of(NAMESPACE, INDEXES); + @Test void binGT() { - parseFiltersAndCompare("$.intBin1 > 100", - List.of(Filter.range("intBin1", 101, Long.MAX_VALUE))); - parseFiltersAndCompare("$.intBin1 > -100", - List.of(Filter.range("intBin1", -99, Long.MAX_VALUE))); - - assertThatThrownBy(() -> parseFilters("$.stringBin1 > 'text'")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Operand type not supported"); - assertThatThrownBy(() -> parseFilters("$.stringBin1 > \"text\"")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Operand type not supported"); + parseFilterAndCompare("$.intBin1 > 100", INDEX_FILTER_INPUT, + Filter.range("intBin1", 101, Long.MAX_VALUE)); + parseFilterAndCompare("$.intBin1 > -100", INDEX_FILTER_INPUT, + Filter.range("intBin1", -99, Long.MAX_VALUE)); + + // Comparing Strings is not supported by secondary index Filters + assertThat(parseFilter("$.stringBin1 > 'text'", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("$.stringBin1 > \"text\"", INDEX_FILTER_INPUT)).isNull(); // "$.intBin1 > 100" and "100 < $.intBin1" represent identical Filters - parseFiltersAndCompare("100 < $.intBin1", - List.of(Filter.range("intBin1", 101, Long.MAX_VALUE))); - - assertThatThrownBy(() -> parseFilters("'text' > $.stringBin1")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Operand type not supported"); - assertThatThrownBy(() -> parseFilters("\"text\" > $.stringBin1")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("Operand type not supported"); + parseFilterAndCompare("100 < $.intBin1", INDEX_FILTER_INPUT, + Filter.range("intBin1", 101, Long.MAX_VALUE)); + + // Comparing Strings is not supported by secondary index Filters + assertThat(parseFilter("'text' > $.stringBin1", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("\"text\" > $.stringBin1", INDEX_FILTER_INPUT)).isNull(); + } + + @Test + void binGT_logical_combinations() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + ); + parseFilterAndCompare("$.intBin1 > 100 and $.intBin2 < 1000", IndexContext.of(NAMESPACE, indexes), + Filter.range("intBin2", Long.MIN_VALUE, 999)); + + parseFilterAndCompare("$.intBin1 > 100 and $.intBin2 < 1000", + null); } @Test void binGE() { - parseFiltersAndCompare("$.intBin1 >= 100", - List.of(Filter.range("intBin1", 100, Long.MAX_VALUE))); + parseFilterAndCompare("$.intBin1 >= 100", INDEX_FILTER_INPUT, + Filter.range("intBin1", 100, Long.MAX_VALUE)); // "$.intBin1 >= 100" and "100 <= $.intBin1" represent identical Filters - parseFiltersAndCompare("100 <= $.intBin1", - List.of(Filter.range("intBin1", 100, Long.MAX_VALUE))); + parseFilterAndCompare("100 <= $.intBin1", INDEX_FILTER_INPUT, + Filter.range("intBin1", 100, Long.MAX_VALUE)); } @Test void binLT() { - parseFiltersAndCompare("$.intBin1 < 100", - List.of(Filter.range("intBin1", Long.MIN_VALUE, 99))); + parseFilterAndCompare("$.intBin1 < 100", INDEX_FILTER_INPUT, + Filter.range("intBin1", Long.MIN_VALUE, 99)); - parseFiltersAndCompare("100 > $.intBin1", - List.of(Filter.range("intBin1", Long.MIN_VALUE, 99))); + parseFilterAndCompare("100 > $.intBin1", INDEX_FILTER_INPUT, + Filter.range("intBin1", Long.MIN_VALUE, 99)); } @Test void binLE() { - parseFiltersAndCompare("$.intBin1 <= 100", - List.of(Filter.range("intBin1", Long.MIN_VALUE, 100))); + parseFilterAndCompare("$.intBin1 <= 100", INDEX_FILTER_INPUT, + Filter.range("intBin1", Long.MIN_VALUE, 100)); - parseFiltersAndCompare("100 >= $.intBin1", - List.of(Filter.range("intBin1", Long.MIN_VALUE, 100))); + parseFilterAndCompare("100 >= $.intBin1", INDEX_FILTER_INPUT, + Filter.range("intBin1", Long.MIN_VALUE, 100)); } @Test void binEQ() { - parseFiltersAndCompare("$.intBin1 == 100", - List.of(Filter.equal("intBin1", 100))); - parseFiltersAndCompare("100 == $.intBin1", - List.of(Filter.equal("intBin1", 100))); - - parseFiltersAndCompare("$.stringBin1 == 'text'", - List.of(Filter.equal("stringBin1", "text"))); - parseFiltersAndCompare("$.stringBin1 == \"text\"", - List.of(Filter.equal("stringBin1", "text"))); + parseFilterAndCompare("$.intBin1 == 100", INDEX_FILTER_INPUT, + Filter.equal("intBin1", 100)); + parseFilterAndCompare("100 == $.intBin1", INDEX_FILTER_INPUT, + Filter.equal("intBin1", 100)); + + parseFilterAndCompare("$.stringBin1 == 'text'", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "text")); + parseFilterAndCompare("$.stringBin1 == \"text\"", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "text")); } @Test void binNOTEQ() { // NOT EQUAL is not supported by secondary index filter - assertThatThrownBy(() -> parseFilters("$.intBin1 != 100")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("$.stringBin1 != 'text'")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("$.stringBin1 != \"text\"")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - - assertThatThrownBy(() -> parseFilters("100 != $.intBin1")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("100 != 'text'")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); - assertThatThrownBy(() -> parseFilters("100 != \"text\"")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessageContaining("The operation is not supported by secondary index filter"); + assertThat(parseFilter("$.intBin1 != 100", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("$.stringBin1 != 'text'", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("$.stringBin1 != \"text\"", INDEX_FILTER_INPUT)).isNull(); + + assertThat(parseFilter("100 != $.intBin1", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("100 != 'text'", INDEX_FILTER_INPUT)).isNull(); + assertThat(parseFilter("100 != \"text\"", INDEX_FILTER_INPUT)).isNull(); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java index 8fcf9db..f46d98e 100644 --- a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java @@ -1,47 +1,62 @@ package com.aerospike.dsl.filter; import com.aerospike.client.query.Filter; -import com.aerospike.dsl.exception.AerospikeDSLException; +import com.aerospike.client.query.IndexType; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.IndexContext; +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.util.TestUtils; import org.junit.jupiter.api.Test; import java.util.Base64; import java.util.List; -import static com.aerospike.dsl.util.TestUtils.parseFilters; -import static com.aerospike.dsl.util.TestUtils.parseFiltersAndCompare; +import static com.aerospike.dsl.util.TestUtils.parseFilter; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ExplicitTypesFiltersTests { + String NAMESPACE = "test1"; + List INDEXES = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("stringBin1").indexType(IndexType.STRING).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("blobBin1").indexType(IndexType.BLOB).binValuesRatio(1).build() + ); + IndexContext INDEX_FILTER_INPUT = IndexContext.of(NAMESPACE, INDEXES); + @Test void integerComparison() { - parseFiltersAndCompare("$.intBin1.get(type: INT) > 5", - List.of(Filter.range("intBin1", 6, Long.MAX_VALUE))); + // Namespace and indexes must be given to create a Filter + TestUtils.parseFilterAndCompare("$.intBin1.get(type: INT) > 5", null); + + TestUtils.parseFilterAndCompare("$.intBin1.get(type: INT) > 5", INDEX_FILTER_INPUT, + Filter.range("intBin1", 6, Long.MAX_VALUE)); - parseFiltersAndCompare("5 < $.intBin1.get(type: INT)", - List.of(Filter.range("intBin1", 6, Long.MAX_VALUE))); + TestUtils.parseFilterAndCompare("5 < $.intBin1.get(type: INT)", INDEX_FILTER_INPUT, + Filter.range("intBin1", 6, Long.MAX_VALUE)); } @Test void stringComparison() { - parseFiltersAndCompare("$.stringBin1.get(type: STRING) == \"yes\"", - List.of(Filter.equal("stringBin1", "yes"))); + TestUtils.parseFilterAndCompare("$.stringBin1.get(type: STRING) == \"yes\"", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "yes")); - parseFiltersAndCompare("$.stringBin1.get(type: STRING) == 'yes'", - List.of(Filter.equal("stringBin1", "yes"))); + TestUtils.parseFilterAndCompare("$.stringBin1.get(type: STRING) == 'yes'", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "yes")); - parseFiltersAndCompare("\"yes\" == $.stringBin1.get(type: STRING)", - List.of(Filter.equal("stringBin1", "yes"))); + TestUtils.parseFilterAndCompare("\"yes\" == $.stringBin1.get(type: STRING)", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "yes")); - parseFiltersAndCompare("'yes' == $.stringBin1.get(type: STRING)", - List.of(Filter.equal("stringBin1", "yes"))); + TestUtils.parseFilterAndCompare("'yes' == $.stringBin1.get(type: STRING)", INDEX_FILTER_INPUT, + Filter.equal("stringBin1", "yes")); } @Test void stringComparisonNegativeTest() { // A String constant must be quoted - assertThatThrownBy(() -> parseFilters("$.stringBin1.get(type: STRING) == yes")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseFilter("$.stringBin1.get(type: STRING) == yes")) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse right operand"); } @@ -49,133 +64,108 @@ void stringComparisonNegativeTest() { void blobComparison() { byte[] data = new byte[]{1, 2, 3}; String encodedString = Base64.getEncoder().encodeToString(data); - parseFiltersAndCompare("$.blobBin1.get(type: BLOB) == \"" + encodedString + "\"", - List.of(Filter.equal("blobBin1", data))); + TestUtils.parseFilterAndCompare("$.blobBin1.get(type: BLOB) == \"" + encodedString + "\"", INDEX_FILTER_INPUT, + Filter.equal("blobBin1", data)); // Reverse - parseFiltersAndCompare("\"" + encodedString + "\" == $.blobBin1.get(type: BLOB)", - List.of(Filter.equal("blobBin1", data))); + TestUtils.parseFilterAndCompare("\"" + encodedString + "\" == $.blobBin1.get(type: BLOB)", INDEX_FILTER_INPUT, + Filter.equal("blobBin1", data)); } @Test void floatComparison() { // No float support in secondary index filter - assertThatThrownBy(() -> parseFilters("$.floatBin1.get(type: FLOAT) == 1.5")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: FLOAT_OPERAND"); - - assertThatThrownBy(() -> parseFilters("1.5 == $.floatBin1.get(type: FLOAT)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: FLOAT_OPERAND"); + assertThat(parseFilter("$.floatBin1.get(type: FLOAT) == 1.5")).isNull(); + assertThat(parseFilter("1.5 == $.floatBin1.get(type: FLOAT)")).isNull(); } @Test void booleanComparison() { // No boolean support in secondary index filter - assertThatThrownBy(() -> parseFilters("$.boolBin1.get(type: BOOL) == true")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BOOL_OPERAND"); - - assertThatThrownBy(() -> parseFilters("true == $.boolBin1.get(type: BOOL)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BOOL_OPERAND"); + assertThat(parseFilter("$.boolBin1.get(type: BOOL) == true")).isNull(); + assertThat(parseFilter("true == $.boolBin1.get(type: BOOL)")).isNull(); } @Test void negativeBooleanComparison() { - assertThatThrownBy(() -> parseFilters("$.boolBin1.get(type: BOOL) == 5")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseFilter("$.boolBin1.get(type: BOOL) == 5")) + .isInstanceOf(DslParseException.class) .hasMessage("Cannot compare BOOL to INT"); } @Test void listComparison_constantOnRightSide() { - assertThatThrownBy(() -> parseFilters("$.listBin1.get(type: LIST) == [100]")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: LIST_OPERAND"); + // Not supported by secondary index filter + assertThat(parseFilter("$.listBin1.get(type: LIST) == [100]")).isNull(); } @Test void listComparison_constantOnRightSide_NegativeTest() { - assertThatThrownBy(() -> parseFilters("$.listBin1.get(type: LIST) == [yes, of course]")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseFilter("$.listBin1.get(type: LIST) == [yes, of course]")) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse list operand"); } @Test void listComparison_constantOnLeftSide() { - assertThatThrownBy(() -> parseFilters("[100] == $.listBin1.get(type: LIST)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: LIST_OPERAND"); + assertThat(parseFilter("[100] == $.listBin1.get(type: LIST)")).isNull(); } @Test void listComparison_constantOnLeftSide_NegativeTest() { - assertThatThrownBy(() -> parseFilters("[yes, of course] == $.listBin1.get(type: LIST)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Could not parse given input, wrong syntax"); + assertThatThrownBy(() -> parseFilter("[yes, of course] == $.listBin1.get(type: LIST)")) + .isInstanceOf(DslParseException.class) + .hasMessage("Could not parse given DSL expression input"); } @Test void mapComparison_constantOnRightSide() { - assertThatThrownBy(() -> parseFilters("$.mapBin1.get(type: MAP) == {100:100}")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: MAP_OPERAND"); + assertThat(parseFilter("$.mapBin1.get(type: MAP) == {100:100}")).isNull(); } @Test void mapComparison_constantOnRightSide_NegativeTest() { - assertThatThrownBy(() -> parseFilters("$.mapBin1.get(type: MAP) == {yes, of course}")) - .isInstanceOf(AerospikeDSLException.class) + assertThatThrownBy(() -> parseFilter("$.mapBin1.get(type: MAP) == {yes, of course}")) + .isInstanceOf(DslParseException.class) .hasMessage("Unable to parse map operand"); } @Test void mapComparison_constantOnLeftSide() { - assertThatThrownBy(() -> parseFilters("{100:100} == $.mapBin1.get(type: MAP)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: MAP_OPERAND"); + assertThat(parseFilter("{100:100} == $.mapBin1.get(type: MAP)")).isNull(); } @Test void mapComparison_constantOnLeftSide_NegativeTest() { - assertThatThrownBy(() -> parseFilters("{yes, of course} == $.mapBin1.get(type: MAP)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Could not parse given input, wrong syntax"); + assertThatThrownBy(() -> parseFilter("{yes, of course} == $.mapBin1.get(type: MAP)")) + .isInstanceOf(DslParseException.class) + .hasMessage("Could not parse given DSL expression input"); } @Test void twoStringBinsComparison() { - assertThatThrownBy(() -> parseFilters("$.stringBin1.get(type: STRING) == $.stringBin2.get(type: STRING)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThat(parseFilter("$.stringBin1.get(type: STRING) == $.stringBin2.get(type: STRING)")).isNull(); } @Test void twoIntegerBinsComparison() { - assertThatThrownBy(() -> parseFilters("$.intBin1.get(type: INT) == $.intBin2.get(type: INT)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThat(parseFilter("$.intBin1.get(type: INT) == $.intBin2.get(type: INT)")).isNull(); } @Test void twoFloatBinsComparison() { - assertThatThrownBy(() -> parseFilters("$.floatBin1.get(type: FLOAT) == $.floatBin2.get(type: FLOAT)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThat(parseFilter("$.floatBin1.get(type: FLOAT) == $.floatBin2.get(type: FLOAT)")).isNull(); } @Test void twoBlobBinsComparison() { - assertThatThrownBy(() -> parseFilters("$.blobBin1.get(type: BLOB) == $.blobBin2.get(type: BLOB)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThat(parseFilter("$.blobBin1.get(type: BLOB) == $.blobBin2.get(type: BLOB)")).isNull(); } @Test void negativeTwoDifferentBinTypesComparison() { - assertThatThrownBy(() -> parseFilters("$.stringBin1.get(type: STRING) == $.floatBin2.get(type: FLOAT)")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThatThrownBy(() -> parseFilter("$.stringBin1.get(type: STRING) == $.floatBin2.get(type: FLOAT)")) + .isInstanceOf(DslParseException.class) + .hasMessage("Cannot compare STRING to FLOAT"); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ImplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ImplicitTypesFiltersTests.java index 15f4cb5..79690f8 100644 --- a/src/test/java/com/aerospike/dsl/filter/ImplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ImplicitTypesFiltersTests.java @@ -1,31 +1,24 @@ package com.aerospike.dsl.filter; -import com.aerospike.dsl.exception.AerospikeDSLException; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseFilters; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static com.aerospike.dsl.util.TestUtils.parseFilter; +import static org.assertj.core.api.Assertions.assertThat; public class ImplicitTypesFiltersTests { @Test void implicitDefaultIntComparison() { - assertThatThrownBy(() -> parseFilters("$.intBin1 < $.intBin2")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BIN_PART"); + assertThat(parseFilter("$.intBin1 < $.intBin2")).isNull(); } @Test void floatComparison() { - assertThatThrownBy(() -> parseFilters("$.floatBin1 >= 100.25")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: FLOAT_OPERAND"); + assertThat(parseFilter("$.floatBin1 >= 100.25")).isNull(); } @Test void booleanComparison() { - assertThatThrownBy(() -> parseFilters("$.boolBin1 == true")) - .isInstanceOf(AerospikeDSLException.class) - .hasMessage("Operand type not supported: BOOL_OPERAND"); + assertThat(parseFilter("$.boolBin1 == true")).isNull(); } } diff --git a/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java b/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java new file mode 100644 index 0000000..9b545df --- /dev/null +++ b/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java @@ -0,0 +1,253 @@ +package com.aerospike.dsl.parsedExpression; + +import com.aerospike.client.exp.Exp; +import com.aerospike.client.query.Filter; +import com.aerospike.client.query.IndexType; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.IndexContext; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.aerospike.dsl.util.TestUtils.parseExpressionAndCompare; + +public class LogicalParsedExpressionTests { + + @Test + void binLogical_AND_2_no_indexes() { + Filter filter = null; + Exp exp = Exp.and(Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100))); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp); + } + + @Test + void binLogical_AND_2_all_indexes() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + ); + String namespace = "test1"; + Filter filter = Filter.range("intBin2", 101, Long.MAX_VALUE); + Exp exp = Exp.gt(Exp.intBin("intBin1"), Exp.val(100)); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_2_all_indexes_no_cardinality() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).build() + ); + String namespace = "test1"; + // Filter is chosen alphabetically because no cardinality is given + Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); + Exp exp = Exp.gt(Exp.intBin("intBin2"), Exp.val(100)); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_2_one_index() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build()); + String namespace = "test1"; + Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); + Exp exp = Exp.gt(Exp.intBin("intBin2"), Exp.val(100)); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_3_all_indexes() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + Filter filter = Filter.range("intBin2", 101, Long.MAX_VALUE); + Exp exp = Exp.and( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_3_all_indexes_same_cardinality() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(100).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(100).build(), + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(100).build() + ); + String namespace = "test1"; + // Filter is chosen alphabetically because the same cardinality is given + Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); + Exp exp = Exp.and( + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_3_all_indexes_no_cardinality() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).build() + ); + String namespace = "test1"; + // Filter is chosen alphabetically because no cardinality is given + Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); + Exp exp = Exp.and( + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_3_all_indexes_partial_data() { + List indexes = List.of( + Index.builder().bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.STRING).binValuesRatio(0).build(), + // The only matching index with full data + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + // The only matching index with full data is for intBin3 + Filter filter = Filter.range("intBin3", 101, Long.MAX_VALUE); + Exp exp = Exp.and( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_AND_3_two_indexes() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + Filter filter = Filter.range("intBin2", 101, Long.MAX_VALUE); + Exp exp = Exp.and( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_OR_2_no_indexes() { + Filter filter = null; + Exp exp = Exp.or(Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100))); + parseExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100", filter, exp); + } + + @Test + void binLogical_OR_2_all_indexes() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + ); + String namespace = "test1"; + Filter filter = null; + Exp exp = Exp.or( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_OR_2_one_index() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + Filter filter = null; + Exp exp = Exp.or( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_OR_3_all_indexes() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + Filter filter = null; + Exp exp = Exp.or( + Exp.or( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100 or $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Test + void binLogical_prioritizedAND_OR_indexed() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + ); + String namespace = "test1"; + Filter filter = null; + Exp exp = Exp.or( + Exp.and( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + parseExpressionAndCompare("($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 > 100", filter, exp, + IndexContext.of(namespace, indexes)); + } + + @Disabled // TODO: complex logical structures, different grouping + @Test + void binLogical_AND_prioritizedOR_indexed() { + List indexes = List.of( + Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + ); + String namespace = "test1"; + Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); + Exp exp = Exp.or( + Exp.and( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) + ), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + parseExpressionAndCompare("$.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 > 100)", filter, exp, + IndexContext.of(namespace, indexes)); + } +} diff --git a/src/test/java/com/aerospike/dsl/util/TestUtils.java b/src/test/java/com/aerospike/dsl/util/TestUtils.java index b85a950..75fd3e1 100644 --- a/src/test/java/com/aerospike/dsl/util/TestUtils.java +++ b/src/test/java/com/aerospike/dsl/util/TestUtils.java @@ -4,33 +4,56 @@ import com.aerospike.client.exp.Expression; import com.aerospike.client.query.Filter; import com.aerospike.dsl.DSLParserImpl; +import com.aerospike.dsl.IndexContext; +import com.aerospike.dsl.ParsedExpression; import lombok.experimental.UtilityClass; -import java.util.List; - import static org.junit.jupiter.api.Assertions.assertEquals; @UtilityClass public class TestUtils { private final DSLParserImpl parser = new DSLParserImpl(); - - public static void parseExpression(String input) { - parser.parseExpression(input); + + public static Expression parseExp(String input) { + return Exp.build(parser.parseExpression(input).getResult().getExp()); } - public static void parseExpressionAndCompare(String input, Exp expected) { - Expression actualExpression = parser.parseExpression(input); + public static void parseExpAndCompare(String input, Exp expected) { + Expression actualExpression = Exp.build(parser.parseExpression(input).getResult().getExp()); Expression expectedExpression = Exp.build(expected); assertEquals(actualExpression, expectedExpression); } - public static void parseFilters(String input) { - parser.parseFilters(input); + public static Filter parseFilter(String input) { + return parser.parseExpression(input).getResult().getFilter(); + } + + public static Filter parseFilter(String input, IndexContext indexContext) { + return parser.parseExpression(input, indexContext).getResult().getFilter(); } - public static void parseFiltersAndCompare(String input, List expected) { - List actualFilter = parser.parseFilters(input); + public static void parseFilterAndCompare(String input, Filter expected) { + Filter actualFilter = parseFilter(input); assertEquals(actualFilter, expected); } + + public static void parseFilterAndCompare(String input, IndexContext indexContext, Filter expected) { + Filter actualFilter = parseFilter(input, indexContext); + assertEquals(actualFilter, expected); + } + + public static void parseExpressionAndCompare(String input, Filter filter, Exp exp) { + ParsedExpression actualExpression = parser.parseExpression(input); + assertEquals(actualExpression.getResult().getFilter(), filter); + Exp actualExp = actualExpression.getResult().getExp(); + assertEquals(actualExp == null ? null : Exp.build(actualExp), exp == null ? null : Exp.build(exp)); + } + + public static void parseExpressionAndCompare(String input, Filter filter, Exp exp, IndexContext indexContext) { + ParsedExpression actualExpression = parser.parseExpression(input, indexContext); + assertEquals(actualExpression.getResult().getFilter(), filter); + Exp actualExp = actualExpression.getResult().getExp(); + assertEquals(actualExp == null ? null : Exp.build(actualExp), exp == null ? null : Exp.build(exp)); + } }