diff --git a/src/main/antlr4/com/aerospike/dsl/Condition.g4 b/src/main/antlr4/com/aerospike/dsl/Condition.g4 index 94e5894..745d800 100644 --- a/src/main/antlr4/com/aerospike/dsl/Condition.g4 +++ b/src/main/antlr4/com/aerospike/dsl/Condition.g4 @@ -7,35 +7,61 @@ grammar Condition; parse: expression; expression - // Declaration and Control Expressions - : 'with' '(' variableDefinition (',' variableDefinition)* ')' 'do' '(' expression ')' # WithExpression + : logicalOrExpression + ; + +logicalOrExpression + : logicalAndExpression ('or' logicalAndExpression)* # OrExpression + ; + +logicalAndExpression + : basicExpression ('and' basicExpression)* # AndExpression + ; + +basicExpression + : 'not' '(' expression ')' # NotExpression + | 'exclusive' '(' expression (',' expression)+ ')' # ExclusiveExpression + | 'with' '(' variableDefinition (',' variableDefinition)* ')' 'do' '(' expression ')' # WithExpression | 'when' '(' expressionMapping (',' expressionMapping)* ',' 'default' '=>' expression ')' # WhenExpression - // Logical Expressions - | expression 'and' expression # AndExpression - | expression 'or' expression # OrExpression - | 'not' '(' expression ')' # NotExpression - | 'exclusive' '(' expression (',' expression)+ ')' # ExclusiveExpression - // Comparison Expressions - | operand '>' operand # GreaterThanExpression - | operand '>=' operand # GreaterThanOrEqualExpression - | operand '<' operand # LessThanExpression - | operand '<=' operand # LessThanOrEqualExpression - | operand '==' operand # EqualityExpression - | operand '!=' operand # InequalityExpression - // Arithmetic Expressions - | operand '+' operand # AddExpression - | operand '-' operand # SubExpression - | operand '*' operand # MulExpression - | operand '/' operand # DivExpression - | operand '%' operand # ModExpression - | operand '&' operand # IntAndExpression - | operand '|' operand # IntOrExpression - | operand '^' operand # IntXorExpression - | '~' operand # IntNotExpression - | operand '<<' operand # IntLShiftExpression - | operand '>>' operand # IntRShiftExpression - // Base Operand - | operand # OperandExpression + | comparisonExpression # ComparisonExpressionWrapper + ; + +comparisonExpression + : additiveExpression '>' additiveExpression # GreaterThanExpression + | additiveExpression '>=' additiveExpression # GreaterThanOrEqualExpression + | additiveExpression '<' additiveExpression # LessThanExpression + | additiveExpression '<=' additiveExpression # LessThanOrEqualExpression + | additiveExpression '==' additiveExpression # EqualityExpression + | additiveExpression '!=' additiveExpression # InequalityExpression + | additiveExpression # AdditiveExpressionWrapper + ; + +// Rest of the grammar rules remain the same +additiveExpression + : multiplicativeExpression # MultiplicativeExpressionWrapper + | additiveExpression '+' multiplicativeExpression # AddExpression + | additiveExpression '-' multiplicativeExpression # SubExpression + ; + +multiplicativeExpression + : bitwiseExpression # BitwiseExpressionWrapper + | multiplicativeExpression '*' bitwiseExpression # MulExpression + | multiplicativeExpression '/' bitwiseExpression # DivExpression + | multiplicativeExpression '%' bitwiseExpression # ModExpression + ; + +bitwiseExpression + : shiftExpression # ShiftExpressionWrapper + | bitwiseExpression '&' shiftExpression # IntAndExpression + | bitwiseExpression '|' shiftExpression # IntOrExpression + | bitwiseExpression '^' shiftExpression # IntXorExpression + | '~' shiftExpression # IntNotExpression + ; + +shiftExpression + : operand # OperandExpression + | shiftExpression '<<' operand # IntLShiftExpression + | shiftExpression '>>' operand # IntRShiftExpression ; variableDefinition diff --git a/src/main/java/com/aerospike/dsl/parts/AbstractPart.java b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java index e0ae3c8..d5cb098 100644 --- a/src/main/java/com/aerospike/dsl/parts/AbstractPart.java +++ b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java @@ -35,6 +35,8 @@ public enum PartType { WITH_STRUCTURE, WHEN_STRUCTURE, EXCLUSIVE_STRUCTURE, + AND_STRUCTURE, + OR_STRUCTURE, BASE_PATH, BIN_PART, LIST_PART, diff --git a/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java index 7f86c92..91f40bc 100644 --- a/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java +++ b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java @@ -65,6 +65,8 @@ public enum ExprPartsOperation { LTEQ, WITH_STRUCTURE, // unary WHEN_STRUCTURE, // unary - EXCLUSIVE_STRUCTURE // unary + EXCLUSIVE_STRUCTURE, // unary + AND_STRUCTURE, + OR_STRUCTURE } } diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java index b8d7cb1..3e4ec86 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListTypeDesignator.java @@ -24,8 +24,8 @@ public static ListTypeDesignator from() { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - List partsUpToDesignator = !basePath.getParts().isEmpty() - ? basePath.getParts().subList(0, basePath.getParts().size() - 1) + List partsUpToDesignator = !basePath.getCdtParts().isEmpty() + ? basePath.getCdtParts().subList(0, basePath.getCdtParts().size() - 1) : Collections.emptyList(); BasePath basePathUntilDesignator = new BasePath(basePath.getBinPart(), partsUpToDesignator); diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java index 226ba60..b4983e9 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapTypeDesignator.java @@ -20,7 +20,7 @@ public static MapTypeDesignator from() { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - var partsUpToDesignator = basePath.getParts().subList(0, basePath.getParts().size() - 1); + var partsUpToDesignator = basePath.getCdtParts().subList(0, basePath.getCdtParts().size() - 1); BasePath basePathUntilDesignator = new BasePath(basePath.getBinPart(), partsUpToDesignator); int partsUpToDesignatorSize = partsUpToDesignator.size(); diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/AndStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/AndStructure.java new file mode 100644 index 0000000..b351e74 --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/AndStructure.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 AndStructure extends AbstractPart { + + private final List operands; + + public AndStructure(List operands) { + super(PartType.AND_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/OrStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/OrStructure.java new file mode 100644 index 0000000..edf472c --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/OrStructure.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 OrStructure extends AbstractPart { + + private final List operands; + + public OrStructure(List operands) { + super(PartType.OR_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/path/BasePath.java b/src/main/java/com/aerospike/dsl/parts/path/BasePath.java index d5b39cd..b6547e6 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/BasePath.java +++ b/src/main/java/com/aerospike/dsl/parts/path/BasePath.java @@ -10,18 +10,18 @@ public class BasePath extends AbstractPart { private final BinPart binPart; - private final List parts; + private final List cdtParts; - public BasePath(BinPart binOperand, List parts) { + public BasePath(BinPart binPart, List cdtParts) { super(PartType.BASE_PATH); - this.binPart = binOperand; - this.parts = parts; + this.binPart = binPart; + this.cdtParts = cdtParts; } // Bin type is determined by the base path's first element public Exp.Type getBinType() { - if (!parts.isEmpty()) { - return switch (parts.get(0).getPartType()) { + if (!cdtParts.isEmpty()) { + return switch (cdtParts.get(0).getPartType()) { case MAP_PART -> Exp.Type.MAP; case LIST_PART -> Exp.Type.LIST; default -> null; diff --git a/src/main/java/com/aerospike/dsl/parts/path/BinPart.java b/src/main/java/com/aerospike/dsl/parts/path/BinPart.java index 4f0b9cb..aad9324 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/BinPart.java +++ b/src/main/java/com/aerospike/dsl/parts/path/BinPart.java @@ -3,17 +3,20 @@ import com.aerospike.client.exp.Exp; import com.aerospike.dsl.parts.ExpressionContainer; import lombok.Getter; +import lombok.Setter; @Getter public class BinPart extends ExpressionContainer { private final String binName; + @Setter + private boolean isTypeExplicitlySet; public BinPart(String binName) { super(); this.binName = binName; this.partType = PartType.BIN_PART; - this.expType = null; // Exp type unknown + this.expType = Exp.Type.INT; // Set INT by default } public void updateExp(Exp.Type expType) { diff --git a/src/main/java/com/aerospike/dsl/parts/path/Path.java b/src/main/java/com/aerospike/dsl/parts/path/Path.java index 239083c..03d9661 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/Path.java +++ b/src/main/java/com/aerospike/dsl/parts/path/Path.java @@ -23,7 +23,7 @@ public Path(BasePath basePath, PathFunction pathFunction) { } public Exp processPath(BasePath basePath, PathFunction pathFunction) { - List parts = basePath.getParts(); + List parts = basePath.getCdtParts(); updateWithCdtTypeDesignator(basePath, pathFunction); AbstractPart lastPathPart = !parts.isEmpty() ? parts.get(parts.size() - 1) : null; pathFunction = processPathFunction(basePath, lastPathPart, pathFunction); diff --git a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java index 64f046e..d51985c 100644 --- a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java +++ b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java @@ -28,6 +28,16 @@ @UtilityClass public class PathOperandUtils { + /** + * Processes value type based on the last part of a path and a {@link PathFunction}. + * If the {@link PathFunction}'s binary type is not null, it's used directly. + * Otherwise, it checks for list or map type designators, or finds the value type based on the last path part and + * function type. + * + * @param lastPathPart The last {@link AbstractPart} in the path + * @param pathFunction The {@link PathFunction} associated with the path + * @return The determined {@link Exp.Type} for the value + */ public static Exp.Type processValueType(AbstractPart lastPathPart, PathFunction pathFunction) { // there is always a path function with non-null function type and return param if (pathFunction.getBinType() == null) { @@ -42,6 +52,17 @@ public static Exp.Type processValueType(AbstractPart lastPathPart, PathFunction return Exp.Type.valueOf(pathFunction.getBinType().toString()); } + /** + * Processes and potentially modifies a {@link PathFunction} based on the {@link BasePath} and the last + * {@link AbstractPart} in the path. + * This method applies default values for {@link PathFunction} if it's null, or adjusts its type (e.g., from COUNT + * to SIZE) based on the context of the path parts. + * + * @param basePath The {@link BasePath} containing the bin and CDT parts + * @param lastPathPart The last {@link AbstractPart} in the path + * @param pathFunction The {@link PathFunction} to be processed + * @return The processed {@link PathFunction} + */ public static PathFunction processPathFunction(BasePath basePath, AbstractPart lastPathPart, PathFunction pathFunction) { if (pathFunction == null) { @@ -53,7 +74,7 @@ public static PathFunction processPathFunction(BasePath basePath, AbstractPart l PathFunction.ReturnParam defaultReturnParam = PathFunction.ReturnParam.VALUE; if (pathFunction.getPathFunctionType() == COUNT) { // If there is only a bin or only a CDT designator - if (basePath.getParts().isEmpty() || containOnlyCdtDesignator(basePath.getParts())) { + if (basePath.getCdtParts().isEmpty() || containOnlyCdtDesignator(basePath.getCdtParts())) { PathFunction.PathFunctionType type = COUNT; if (basePath.getBinType() == Exp.Type.LIST || basePath.getBinType() == Exp.Type.MAP) { type = SIZE; @@ -64,7 +85,7 @@ public static PathFunction processPathFunction(BasePath basePath, AbstractPart l // If the last path part is a CDT type designator, get the previous part if (isListTypeDesignator(lastPathPart) || isMapTypeDesignator(lastPathPart)) { AbstractPart partBeforeDesignator = - getPartOrNull(basePath.getParts(), basePath.getParts().size() - 2); + getPartOrNull(basePath.getCdtParts(), basePath.getCdtParts().size() - 2); if (partBeforeDesignator != null) lastPathPart = partBeforeDesignator; } @@ -96,14 +117,37 @@ public static PathFunction processPathFunction(BasePath basePath, AbstractPart l return pathFunction; } + /** + * Retrieves an {@link AbstractPart} from a list of parts at a specified index, returning {@code null} if the index + * is out of bounds. + * + * @param parts The list of {@link AbstractPart} objects + * @param idx The index of the part to retrieve + * @return The {@link AbstractPart} at the specified index, or {@code null} if the index is negative + */ private static AbstractPart getPartOrNull(List parts, int idx) { return idx >= 0 ? parts.get(idx) : null; } + /** + * Checks if a list of {@link AbstractPart} objects contains only a CDT (Collection Data Type) designator. + * A CDT designator is either a list type designator or a map type designator. + * + * @param parts The list of {@link AbstractPart} objects to check + * @return {@code true} if the list contains only one part which is a CDT designator, {@code false} otherwise + */ private static boolean containOnlyCdtDesignator(List parts) { return parts.size() == 1 && (isListTypeDesignator(parts.get(0)) || isMapTypeDesignator(parts.get(0))); } + /** + * Checks if the previous CDT (Collection Data Type) part is ambiguous. + * An ambiguous part is one whose type (e.g., MapPart.MapPartType.INDEX, ListPart.ListPartType.VALUE) + * could lead to different interpretations without further context. + * + * @param lastPart The last {@link AbstractPart} to check for ambiguity + * @return {@code true} if the last part is an ambiguous CDT part, {@code false} otherwise + */ private static boolean isPrevCdtPartAmbiguous(AbstractPart lastPart) { if (lastPart.getPartType() == MAP_PART) { // check that lastPart is CDT Map // check relevant types @@ -119,6 +163,17 @@ private static boolean isPrevCdtPartAmbiguous(AbstractPart lastPart) { return false; } + /** + * Processes a "get" operation for a path, constructing the appropriate {@link Exp} based on the last path part's + * type. This method supports {@code LIST_PART} and {@code MAP_PART} types. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The last {@link AbstractPart} in the path, which determines the type of get operation + * @param valueType The expected {@link Exp.Type} of the value being retrieved + * @param cdtReturnType The CDT return type + * @return An {@link Exp} representing the "get" operation + * @throws UnsupportedOperationException If the path part type is not supported + */ public static Exp processGet(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { if (lastPathPart.getPartType() == LIST_PART) { return doProcessCdtGet(basePath, lastPathPart, valueType, cdtReturnType, (ListPart) lastPathPart); @@ -129,6 +184,18 @@ public static Exp processGet(BasePath basePath, AbstractPart lastPathPart, Exp.T String.format("Path part type %s is not supported", lastPathPart.getPartType())); } + /** + * Helper method to process CDT (Collection Data Type) "get" operations. + * It constructs the appropriate {@link Exp} for list or map type designators, + * or for specific CDT parts with their context. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The last {@link AbstractPart} in the path + * @param valueType The expected {@link Exp.Type} of the value being retrieved + * @param cdtReturnType The CDT return type + * @param cdtPart The {@link CdtPart} being processed + * @return An {@link Exp} representing the CDT "get" operation + */ private static Exp doProcessCdtGet(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType, CdtPart cdtPart) { // list type designator "[]" can be either after bin name or after path @@ -137,19 +204,39 @@ private static Exp doProcessCdtGet(BasePath basePath, AbstractPart lastPathPart, } // Context can be empty - CTX[] context = getContextArray(basePath.getParts(), false); + CTX[] context = getContextArray(basePath.getCdtParts(), false); return ((CdtPart) lastPathPart).constructExp(basePath, valueType, cdtReturnType, context); } + /** + * Checks if an {@link AbstractPart} is a list type designator (e.g., "[]"). + * + * @param cdtPart The {@link AbstractPart} to check + * @return {@code true} if the part is a list type designator, {@code false} otherwise + */ private static boolean isListTypeDesignator(AbstractPart cdtPart) { return cdtPart.getPartType() == LIST_PART && ((ListPart) cdtPart).getListPartType().equals(LIST_TYPE_DESIGNATOR); } + /** + * Checks if an {@link AbstractPart} is a map type designator (e.g., "{}"). + * + * @param cdtPart The {@link AbstractPart} to check + * @return {@code true} if the part is a map type designator, {@code false} otherwise + */ private static boolean isMapTypeDesignator(AbstractPart cdtPart) { return cdtPart.getPartType() == MAP_PART && ((MapPart) cdtPart).getMapPartType().equals(MAP_TYPE_DESIGNATOR); } + /** + * Constructs an array of {@link CTX} (context) objects from a list of {@link AbstractPart} objects. + * This is used to build the context for nested CDT operations. + * + * @param parts The list of {@link AbstractPart} objects representing path parts + * @param includeLast A boolean indicating whether the last part should be included in the context array + * @return An array of {@link CTX} objects + */ private static CTX[] getContextArray(List parts, boolean includeLast) { // Nested (Context) map key access List context = new ArrayList<>(); @@ -165,6 +252,17 @@ private static CTX[] getContextArray(List parts, boolean includeLa return context.toArray(new CTX[0]); } + /** + * Processes a "size" operation for a path, constructing the appropriate {@link Exp} based on the last path part's type. + * This method supports {@code LIST_PART} and {@code MAP_PART} types. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The last {@link AbstractPart} in the path, which determines the type of size operation + * @param valueType The expected {@link Exp.Type} of the value whose size is being retrieved + * @param cdtReturnType The CDT return type + * @return An {@link Exp} representing the "size" operation + * @throws UnsupportedOperationException If the path part type is not supported + */ public static Exp processSize(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { if (lastPathPart.getPartType() == LIST_PART) { return processListPartSize(basePath, lastPathPart, valueType, cdtReturnType); @@ -175,6 +273,16 @@ public static Exp processSize(BasePath basePath, AbstractPart lastPathPart, Exp. String.format("Path part type %s is not supported", lastPathPart.getPartType())); } + /** + * Processes the "size" operation for a {@link ListPart}. + * It handles list type designators and constructs the appropriate {@link ListExp#size} expression. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The {@link ListPart} representing the last part of the path + * @param valueType The expected {@link Exp.Type} of the list elements + * @param cdtReturnType The CDT return type + * @return An {@link Exp} representing the list size operation + */ private static Exp processListPartSize(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { BinPart bin = basePath.getBinPart(); @@ -185,10 +293,20 @@ private static Exp processListPartSize(BasePath basePath, AbstractPart lastPathP return getCdtExpFunction(ListExp::size, basePath, lastPathPart, valueType, cdtReturnType); } // In size() the last element is considered context - CTX[] context = getContextArray(basePath.getParts(), true); + CTX[] context = getContextArray(basePath.getCdtParts(), true); return ListExp.size(Exp.bin(bin.getBinName(), basePath.getBinType()), context); } + /** + * Processes the "size" operation for a {@link MapPart}. + * It handles map type designators and constructs the appropriate {@link MapExp#size} expression. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The {@link MapPart} representing the last part of the path + * @param valueType The expected {@link Exp.Type} of the map elements + * @param cdtReturnType The CDT return type + * @return An {@link Exp} representing the map size operation + */ private static Exp processMapPartSize(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { BinPart bin = basePath.getBinPart(); @@ -198,19 +316,37 @@ private static Exp processMapPartSize(BasePath basePath, AbstractPart lastPathPa return getCdtExpFunction(MapExp::size, basePath, lastPathPart, valueType, cdtReturnType); } // In size() the last element is considered context - CTX[] context = getContextArray(basePath.getParts(), true); + CTX[] context = getContextArray(basePath.getCdtParts(), true); return MapExp.size(Exp.bin(bin.getBinName(), basePath.getBinType()), context); } + /** + * Updates the {@link BasePath} with a CDT (Collection Data Type) type designator if necessary. + * This is typically done for operations like {@code list.count()} or {@code map.count()} when no explicit + * designator (e.g., {@code []} for List, {@code {}} for Map) is present, and the last CDT part is ambiguous. + * + * @param basePath The {@link BasePath} to be updated + * @param pathFunction The {@link PathFunction} associated with the operation + */ public static void updateWithCdtTypeDesignator(BasePath basePath, PathFunction pathFunction) { - if (mustHaveCdtDesignator(pathFunction, basePath.getParts())) { + if (mustHaveCdtDesignator(pathFunction, basePath.getCdtParts())) { // 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); + basePath.getCdtParts().add(lastPathPart); } } + /** + * Determines if a CDT (Collection Data Type) designator must be present based on the {@link PathFunction} + * and the list of {@link AbstractPart} objects. + * A designator is required if the function type is {@code SIZE} or {@code COUNT}, or if it's a {@code GET} with + * {@code COUNT} return parameter, and the parts are empty or the previous CDT part is ambiguous. + * + * @param pathFunction The {@link PathFunction} to evaluate + * @param parts The list of {@link AbstractPart} objects representing the path + * @return {@code true} if a CDT designator is mandatory, {@code false} otherwise + */ private static boolean mustHaveCdtDesignator(PathFunction pathFunction, List parts) { // if existing path function type is SIZE or COUNT @@ -221,28 +357,65 @@ private static boolean mustHaveCdtDesignator(PathFunction pathFunction, && (parts.isEmpty() || (isPrevCdtPartAmbiguous(parts.get(parts.size() - 1)))); } + /** + * Checks if the given {@link PathFunction} represents a "GET" operation with a "COUNT" return parameter. + * + * @param pathFunction The {@link PathFunction} to check + * @return {@code true} if it's a GET with COUNT return parameter, {@code false} otherwise.\ + */ private static boolean pathFunctionIsGetWithCount(PathFunction pathFunction) { return pathFunction.getPathFunctionType() == GET && pathFunction.getReturnParam() == PathFunction.ReturnParam.COUNT; } + /** + * Constructs a CDT (Collection Data Type) expression. + * This method builds the context array from the path parts (excluding the last one if it's a designator) + * and then uses the last path part to construct the final expression. + * + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The last {@link AbstractPart} in the path + * @param valueType The expected {@link Exp.Type} of the value + * @param cdtReturnType The CDT return type + * @return An {@link Exp} representing the constructed CDT expression + */ private static Exp constructCdtExp(BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { // Context can be empty - List partsUpToDesignator = basePath.getParts().isEmpty() + List partsUpToDesignator = basePath.getCdtParts().isEmpty() ? new ArrayList<>() - : basePath.getParts().subList(0, basePath.getParts().size() - 1); + : basePath.getCdtParts().subList(0, basePath.getCdtParts().size() - 1); CTX[] context = getContextArray(partsUpToDesignator, false); return ((CdtPart) lastPathPart).constructExp(basePath, valueType, cdtReturnType, context); } + /** + * Applies a unary operator to a constructed CDT (Collection Data Type) expression. + * This is a generic method to apply functions like {@code size()} to CDT expressions. + * + * @param operator The {@link UnaryOperator} to apply (e.g., {@code ListExp::size}, {@code MapExp::size}) + * @param basePath The {@link BasePath} of the expression + * @param lastPathPart The last {@link AbstractPart} in the path + * @param valueType The expected {@link Exp.Type} of the value + * @param cdtReturnType The CDT return type + * @return An {@link Exp} resulting from applying the operator to the CDT expression + */ private static Exp getCdtExpFunction(UnaryOperator operator, BasePath basePath, AbstractPart lastPathPart, Exp.Type valueType, int cdtReturnType) { Exp cdtExp = constructCdtExp(basePath, lastPathPart, valueType, cdtReturnType); return operator.apply(cdtExp); } + /** + * Determines the value type based on the last path part and the path function type. + * It prioritizes the explicit type of the last path part, then checks for count-related types, + * and finally falls back to a default type. + * + * @param lastPathPart The last {@link AbstractPart} in the path + * @param pathFunctionType The {@link PathFunction.PathFunctionType} of the operation + * @return The determined {@link Exp.Type} for the value + */ private static Exp.Type findValueType(AbstractPart lastPathPart, PathFunction.PathFunctionType pathFunctionType) { /* Determine valueType based on @@ -258,6 +431,15 @@ private static Exp.Type findValueType(AbstractPart lastPathPart, PathFunction.Pa return TypeUtils.getDefaultType(lastPathPart); } + /** + * Determines the value type specifically for a "COUNT" operation based on the last path part. + * If the last path part is a {@link ListTypeDesignator}, it returns {@code Exp.Type.LIST}. + * If it's a {@link MapTypeDesignator}, it returns {@code Exp.Type.MAP}. + * Otherwise, it returns the default type for count operations. + * + * @param lastPathPart The last {@link AbstractPart} in the path + * @return The {@link Exp.Type} appropriate for a count operation + */ private static Exp.Type getValueTypeForCount(AbstractPart lastPathPart) { if (lastPathPart instanceof ListTypeDesignator) { return Exp.Type.LIST; diff --git a/src/main/java/com/aerospike/dsl/util/TypeUtils.java b/src/main/java/com/aerospike/dsl/util/TypeUtils.java index 3a140e5..dc33d60 100644 --- a/src/main/java/com/aerospike/dsl/util/TypeUtils.java +++ b/src/main/java/com/aerospike/dsl/util/TypeUtils.java @@ -8,6 +8,14 @@ @UtilityClass public class TypeUtils { + /** + * Returns the default {@link Exp.Type} for a given {@link AbstractPart}. + * For {@link AbstractPart.PartType#MAP_PART} that is not a {@link MapTypeDesignator}, the default type is {@code STRING}. + * Otherwise, the default type is {@code INT}. + * + * @param part The {@link AbstractPart} for which to determine the default type + * @return The default {@link Exp.Type} for the given part + */ public static Exp.Type getDefaultType(AbstractPart part) { if (part.getPartType() == AbstractPart.PartType.MAP_PART // MapTypeDesignator is usually combined with int based operations such as size diff --git a/src/main/java/com/aerospike/dsl/util/ValidationUtils.java b/src/main/java/com/aerospike/dsl/util/ValidationUtils.java index f707b9c..bfb261b 100644 --- a/src/main/java/com/aerospike/dsl/util/ValidationUtils.java +++ b/src/main/java/com/aerospike/dsl/util/ValidationUtils.java @@ -7,6 +7,15 @@ @UtilityClass public class ValidationUtils { + /** + * Validates if two {@link Exp.Type} instances are comparable. + * Comparison is allowed if both types are the same, or if one is {@code INT} and the other is {@code FLOAT}. + * If the types are not comparable, a {@link DslParseException} is thrown. + * + * @param leftType The {@link Exp.Type} of the left operand. Can be {@code null} + * @param rightType The {@link Exp.Type} of the right operand. Can be {@code null} + * @throws DslParseException If both types are not {@code null} and are not comparable + */ public static void validateComparableTypes(Exp.Type leftType, Exp.Type rightType) { if (leftType != null && rightType != null) { boolean isIntAndFloat = diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index 1ef198e..7bad4cf 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -6,9 +6,19 @@ 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.list.ListIndex; +import com.aerospike.dsl.parts.cdt.list.ListIndexRange; +import com.aerospike.dsl.parts.cdt.list.ListRank; +import com.aerospike.dsl.parts.cdt.list.ListRankRange; +import com.aerospike.dsl.parts.cdt.list.ListRankRangeRelative; +import com.aerospike.dsl.parts.cdt.list.ListTypeDesignator; +import com.aerospike.dsl.parts.cdt.list.ListValue; +import com.aerospike.dsl.parts.cdt.list.ListValueList; +import com.aerospike.dsl.parts.cdt.list.ListValueRange; import com.aerospike.dsl.parts.cdt.map.*; +import com.aerospike.dsl.parts.controlstructure.AndStructure; import com.aerospike.dsl.parts.controlstructure.ExclusiveStructure; +import com.aerospike.dsl.parts.controlstructure.OrStructure; import com.aerospike.dsl.parts.controlstructure.WhenStructure; import com.aerospike.dsl.parts.controlstructure.WithStructure; import com.aerospike.dsl.parts.operand.*; @@ -16,7 +26,6 @@ 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; @@ -25,17 +34,8 @@ 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.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; +import static com.aerospike.dsl.util.ParsingUtils.*; +import static com.aerospike.dsl.visitor.VisitorUtils.*; public class ExpressionConditionVisitor extends ConditionBaseVisitor { @@ -72,20 +72,40 @@ public AbstractPart visitWhenExpression(ConditionParser.WhenExpressionContext ct @Override public AbstractPart visitAndExpression(ConditionParser.AndExpressionContext ctx) { - ExpressionContainer left = (ExpressionContainer) visit(ctx.expression(0)); - ExpressionContainer right = (ExpressionContainer) visit(ctx.expression(1)); + // If there's only one basicExpression and no 'and' operators, just pass through + if (ctx.basicExpression().size() == 1) { + return visit(ctx.basicExpression(0)); + } + + List expressions = new ArrayList<>(); + // iterate through each sub-expression + for (ConditionParser.BasicExpressionContext ec : ctx.basicExpression()) { + ExpressionContainer expr = (ExpressionContainer) visit(ec); + if (expr == null) return null; - logicalSetBinsAsBooleanExpr(left, right); - return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.AND); + logicalSetBinAsBooleanExpr(expr); + expressions.add(expr); + } + return new ExpressionContainer(new AndStructure(expressions), ExpressionContainer.ExprPartsOperation.AND_STRUCTURE); } @Override public AbstractPart visitOrExpression(ConditionParser.OrExpressionContext ctx) { - ExpressionContainer left = (ExpressionContainer) visit(ctx.expression(0)); - ExpressionContainer right = (ExpressionContainer) visit(ctx.expression(1)); + // If there's only one andExpression and no 'or' operators, just pass through + if (ctx.logicalAndExpression().size() == 1) { + return visit(ctx.logicalAndExpression(0)); + } - logicalSetBinsAsBooleanExpr(left, right); - return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.OR); + List expressions = new ArrayList<>(); + // iterate through each sub-expression + for (ConditionParser.LogicalAndExpressionContext ec : ctx.logicalAndExpression()) { + ExpressionContainer expr = (ExpressionContainer) visit(ec); + if (expr == null) return null; + + logicalSetBinAsBooleanExpr(expr); + expressions.add(expr); + } + return new ExpressionContainer(new OrStructure(expressions), ExpressionContainer.ExprPartsOperation.OR_STRUCTURE); } @Override @@ -102,7 +122,7 @@ public AbstractPart visitExclusiveExpression(ConditionParser.ExclusiveExpression throw new DslParseException("Exclusive logical operator requires 2 or more expressions"); } List expressions = new ArrayList<>(); - // iterate through each definition + // iterate through each sub-expression for (ConditionParser.ExpressionContext ec : ctx.expression()) { ExpressionContainer expr = (ExpressionContainer) visit(ec); logicalSetBinAsBooleanExpr(expr); @@ -112,137 +132,179 @@ public AbstractPart visitExclusiveExpression(ConditionParser.ExclusiveExpression ExpressionContainer.ExprPartsOperation.EXCLUSIVE_STRUCTURE); } + @Override + public AbstractPart visitComparisonExpressionWrapper(ConditionParser.ComparisonExpressionWrapperContext ctx) { + // Pass through the wrapper + return visit(ctx.comparisonExpression()); + } + + @Override + public AbstractPart visitAdditiveExpressionWrapper(ConditionParser.AdditiveExpressionWrapperContext ctx) { + // Pass through the wrapper + return visit(ctx.additiveExpression()); + } + + @Override + public AbstractPart visitMultiplicativeExpressionWrapper(ConditionParser.MultiplicativeExpressionWrapperContext ctx) { + // Pass through the wrapper + return visit(ctx.multiplicativeExpression()); + } + + @Override + public AbstractPart visitBitwiseExpressionWrapper(ConditionParser.BitwiseExpressionWrapperContext ctx) { + // Pass through the wrapper + return visit(ctx.bitwiseExpression()); + } + + @Override + public AbstractPart visitShiftExpressionWrapper(ConditionParser.ShiftExpressionWrapperContext ctx) { + // Pass through the wrapper + return visit(ctx.shiftExpression()); + } + @Override public AbstractPart visitGreaterThanExpression(ConditionParser.GreaterThanExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.GT); } @Override public AbstractPart visitGreaterThanOrEqualExpression(ConditionParser.GreaterThanOrEqualExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.GTEQ); } @Override public AbstractPart visitLessThanExpression(ConditionParser.LessThanExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.LT); } @Override public AbstractPart visitLessThanOrEqualExpression(ConditionParser.LessThanOrEqualExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.LTEQ); } @Override public AbstractPart visitEqualityExpression(ConditionParser.EqualityExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.EQ); } @Override public AbstractPart visitInequalityExpression(ConditionParser.InequalityExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression(0)); + AbstractPart right = visit(ctx.additiveExpression(1)); + + overrideTypeInfo(left, right); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.NOTEQ); } @Override public AbstractPart visitAddExpression(ConditionParser.AddExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression()); + AbstractPart right = visit(ctx.multiplicativeExpression()); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.ADD); } @Override public AbstractPart visitSubExpression(ConditionParser.SubExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.additiveExpression()); + AbstractPart right = visit(ctx.multiplicativeExpression()); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.SUB); } @Override public AbstractPart visitMulExpression(ConditionParser.MulExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.multiplicativeExpression()); + AbstractPart right = visit(ctx.bitwiseExpression()); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.MUL); } @Override public AbstractPart visitDivExpression(ConditionParser.DivExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.multiplicativeExpression()); + AbstractPart right = visit(ctx.bitwiseExpression()); return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.DIV); } @Override public AbstractPart visitModExpression(ConditionParser.ModExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.multiplicativeExpression()); // first operand + AbstractPart right = visit(ctx.bitwiseExpression()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.MOD); } @Override public AbstractPart visitIntAndExpression(ConditionParser.IntAndExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.bitwiseExpression()); // first operand + AbstractPart right = visit(ctx.shiftExpression()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_AND); } @Override public AbstractPart visitIntOrExpression(ConditionParser.IntOrExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.bitwiseExpression()); // first operand + AbstractPart right = visit(ctx.shiftExpression()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_OR); } @Override public AbstractPart visitIntXorExpression(ConditionParser.IntXorExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.bitwiseExpression()); // first operand + AbstractPart right = visit(ctx.shiftExpression()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.INT_XOR); } @Override public AbstractPart visitIntNotExpression(ConditionParser.IntNotExpressionContext ctx) { - AbstractPart operand = visit(ctx.operand()); + AbstractPart operand = visit(ctx.shiftExpression()); return new ExpressionContainer(operand, ExpressionContainer.ExprPartsOperation.INT_NOT); } @Override public AbstractPart visitIntLShiftExpression(ConditionParser.IntLShiftExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.shiftExpression()); // first operand + AbstractPart right = visit(ctx.operand()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.L_SHIFT); } @Override public AbstractPart visitIntRShiftExpression(ConditionParser.IntRShiftExpressionContext ctx) { - AbstractPart left = visit(ctx.operand(0)); - AbstractPart right = visit(ctx.operand(1)); + AbstractPart left = visit(ctx.shiftExpression()); // first operand + AbstractPart right = visit(ctx.operand()); // second operand return new ExpressionContainer(left, right, ExpressionContainer.ExprPartsOperation.R_SHIFT); } @@ -402,7 +464,7 @@ public AbstractPart visitBooleanOperand(ConditionParser.BooleanOperandContext ct @Override public AbstractPart visitBasePath(ConditionParser.BasePathContext ctx) { BinPart binPart = null; - List parts = new ArrayList<>(); + List cdtParts = new ArrayList<>(); List ctxChildrenExclDots = ctx.children.stream() .filter(tree -> !tree.getText().equals(".")) .toList(); @@ -410,8 +472,8 @@ public AbstractPart visitBasePath(ConditionParser.BasePathContext ctx) { for (ParseTree child : ctxChildrenExclDots) { AbstractPart part = visit(child); switch (part.getPartType()) { - case BIN_PART -> binPart = (BinPart) overrideType(part, ctx); - case LIST_PART, MAP_PART -> parts.add(overrideType(part, ctx)); + case BIN_PART -> binPart = (BinPart) part; + case LIST_PART, MAP_PART -> cdtParts.add(part); default -> throw new DslParseException("Unexpected path part: %s".formatted(part.getPartType())); } } @@ -420,7 +482,7 @@ public AbstractPart visitBasePath(ConditionParser.BasePathContext ctx) { throw new DslParseException("Expecting bin to be the first path part from the left"); } - return new BasePath(binPart, parts); + return new BasePath(binPart, cdtParts); } @Override @@ -429,48 +491,14 @@ public AbstractPart visitVariable(ConditionParser.VariableContext ctx) { return new VariableOperand(extractVariableNameOrFail(text)); } - private AbstractPart overrideType(AbstractPart part, ParseTree ctx) { - ConditionParser.PathFunctionContext pathFunctionContext = - ((ConditionParser.PathContext) ctx.getParent()).pathFunction(); - - // Override with Path Function (explicit get or cast) - if (pathFunctionContext != null) { - PathFunction pathFunction = (PathFunction) visit(pathFunctionContext); - - if (pathFunction != null) { - Exp.Type type = pathFunction.getBinType(); - if (type != null) { - if (part.getPartType() == AbstractPart.PartType.BIN_PART) { - ((BinPart) part).updateExp(type); - } else { - part.setExpType(type); - } - } - } - } else { // Override using Implicit type detection - Exp.Type implicitType = detectImplicitTypeFromUpperTree(ctx); - if (part.getPartType() == AbstractPart.PartType.BIN_PART) { - if (implicitType == null) { - implicitType = Exp.Type.INT; - } - ((BinPart) part).updateExp(implicitType); - } else { // ListPart or MapPart - if (implicitType == null) { - implicitType = TypeUtils.getDefaultType(part); - } - part.setExpType(implicitType); - } - } - return part; - } - @Override public AbstractPart visitPath(ConditionParser.PathContext ctx) { BasePath basePath = (BasePath) visit(ctx.basePath()); - List parts = basePath.getParts(); + List cdtParts = basePath.getCdtParts(); + overrideWithPathFunction(basePath.getBinPart(), ctx); // if there are other parts except bin, get a corresponding Exp - if (!parts.isEmpty() || ctx.pathFunction() != null && ctx.pathFunction().pathFunctionCount() != null) { + if (!cdtParts.isEmpty() || ctx.pathFunction() != null && ctx.pathFunction().pathFunctionCount() != null) { return new Path(basePath, ctx.pathFunction() == null ? null : (PathFunction) visit(ctx.pathFunction())); @@ -478,6 +506,22 @@ public AbstractPart visitPath(ConditionParser.PathContext ctx) { return basePath.getBinPart(); } + private void overrideWithPathFunction(BinPart binPart, ConditionParser.PathContext ctx) { + ConditionParser.PathFunctionContext pathFunctionContext = ctx.pathFunction(); + + // Override with path function (explicit get or cast) + if (pathFunctionContext != null) { + PathFunction pathFunction = (PathFunction) visit(pathFunctionContext); + if (pathFunction != null) { + Exp.Type type = pathFunction.getBinType(); + if (type != null) { + binPart.updateExp(type); + binPart.setTypeExplicitlySet(true); + } + } + } + } + @Override public AbstractPart visitListPart(ConditionParser.ListPartContext ctx) { if (ctx.LIST_TYPE_DESIGNATOR() != null) return ListTypeDesignator.from(); diff --git a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java index 408c4e4..3c53a5d 100644 --- a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java +++ b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java @@ -9,7 +9,9 @@ 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.AndStructure; import com.aerospike.dsl.parts.controlstructure.ExclusiveStructure; +import com.aerospike.dsl.parts.controlstructure.OrStructure; import com.aerospike.dsl.parts.controlstructure.WhenStructure; import com.aerospike.dsl.parts.controlstructure.WithStructure; import com.aerospike.dsl.parts.operand.IntOperand; @@ -17,43 +19,65 @@ import com.aerospike.dsl.parts.operand.StringOperand; import com.aerospike.dsl.parts.operand.WithOperand; import com.aerospike.dsl.parts.path.BinPart; +import com.aerospike.dsl.parts.path.Path; +import com.aerospike.dsl.util.TypeUtils; import lombok.experimental.UtilityClass; import org.antlr.v4.runtime.misc.Pair; import org.antlr.v4.runtime.tree.ParseTree; -import java.util.*; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; 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.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.parts.AbstractPart.PartType.*; +import static com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation.*; import static com.aerospike.dsl.util.ValidationUtils.validateComparableTypes; -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; +import static com.aerospike.dsl.visitor.VisitorUtils.ArithmeticTermType.*; @UtilityClass public class VisitorUtils { + protected enum FilterOperationType { + GT, + GTEQ, + LT, + LTEQ, + EQ, + NOTEQ + } + + protected enum ArithmeticTermType { + ADDEND, + SUBTR, + MIN, + DIVIDEND, + DIVISOR, + MULTIPLICAND, + MULTIPLIER, + } + private final Map expTypeToIndexType = Map.of( Exp.Type.INT, IndexType.NUMERIC, Exp.Type.STRING, IndexType.STRING, Exp.Type.BLOB, IndexType.BLOB ); + final Map partTypeToExpType = Map.of( + AbstractPart.PartType.INT_OPERAND, Exp.Type.INT, + AbstractPart.PartType.FLOAT_OPERAND, Exp.Type.FLOAT, + AbstractPart.PartType.STRING_OPERAND, Exp.Type.STRING, + AbstractPart.PartType.BOOL_OPERAND, Exp.Type.BOOL + ); + /** * Converts an {@link ExprPartsOperation} enum value to its corresponding {@link FilterOperationType}. * @@ -136,54 +160,96 @@ static String extractVariableNameOrFail(String variableReference) { } /** - * 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. + * Overrides Exp type information for both the left and right {@link AbstractPart}s. + * This method ensures that if a part's Exp type is not explicitly set, it attempts to infer and update + * its type based on the opposite part of expression. * - * @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. + * @param left The left {@link AbstractPart} operand, whose type information might be updated + * @param right The right {@link AbstractPart} operand, whose type information might be updated + * @throws DslParseException If either the left or right operand is {@code null} */ - 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 - while (ctx.getParent() != null) { - ctx = ctx.getParent(); - - for (int i = 0; i < ctx.getChildCount(); i++) { - ParseTree child = ctx.getChild(i); - - if (child instanceof ConditionParser.OperandContext operandContext) { - if (operandContext.numberOperand() != null) { - if (operandContext.numberOperand().intOperand() != null) { - return Exp.Type.INT; - } else if (operandContext.numberOperand().floatOperand() != null) { - return Exp.Type.FLOAT; - } - } else if (operandContext.stringOperand() != null) { - return Exp.Type.STRING; - } else if (operandContext.booleanOperand() != null) { - return Exp.Type.BOOL; - } + static void overrideTypeInfo(AbstractPart left, AbstractPart right) { + if (left == null) { + throw new DslParseException("Unable to parse left operand"); + } + if (right == null) { + throw new DslParseException("Unable to parse right operand"); + } + + // Handle left part + overrideTypes(left, right); + // Handle right part + overrideTypes(right, left); + } + + /** + * Overrides Exp type of the {@code left} {@link AbstractPart} based on the {@code right} {@link AbstractPart}. + * This method handles different types of {@link AbstractPart} (e.g., {@code BIN_PART}, {@code PATH_OPERAND}, + * {@code EXPRESSION_CONTAINER}) and applies type overriding logic accordingly. + * + * @param left The {@link AbstractPart} whose type might be overridden + * @param right The {@link AbstractPart} used as a reference for type inference + */ + private static void overrideTypes(AbstractPart left, AbstractPart right) { + switch (left.getPartType()) { + case BIN_PART -> overrideBinType((BinPart) left, right); // For example, in expression "$.intBin1 == 100" + case PATH_OPERAND -> { // For example, in "$.listBin1.[0].get(return: EXISTS) == true" + Path path = (Path) left; + // Update the type of BinPart + overrideBinType(path.getBasePath().getBinPart(), right); + // Update the type of each CDT part + for (AbstractPart cdtPart : path.getBasePath().getCdtParts()) { + overrideType(cdtPart, right); + } + } + case EXPRESSION_CONTAINER -> { // For example, in "(5.2 + $.bananas) > 10.2" + ExpressionContainer container = (ExpressionContainer) left; + // Update the type of left part of container + overrideTypeInfo(container.getLeft(), right); + AbstractPart rightPart = container.getRight(); + // Update the type of right part of container + if (rightPart != null) { + overrideTypeInfo(rightPart, right); } } } - // Could not detect, return null and determine defaults later on - return null; } /** - * Sets the logical bin type for both the left and right expression containers - * to {@link Exp.Type#BOOL} if they represent a bin part. + * Overrides Exp type of the given {@link BinPart} based on the {@code right} {@link AbstractPart} if type is not + * explicitly set already. + * + * @param binPart The {@link BinPart} whose type might be overridden + * @param right The {@link AbstractPart} used as a reference for type inference + */ + private static void overrideBinType(BinPart binPart, AbstractPart right) { + if (!binPart.isTypeExplicitlySet()) { + overrideType(binPart, right); + } + } + + /** + * Overrides Exp type of single {@link AbstractPart} based on the implicit type derived from {@code oppositePart}. + * This method applies type overriding using implicit type detection. + * It handles {@code BIN_PART} and other parts separately. * - * @param left The left {@link ExpressionContainer} - * @param right The right {@link ExpressionContainer} + * @param part The {@link AbstractPart} whose type needs to be overridden + * @param oppositePart The {@link AbstractPart} used to determine the implicit type */ - static void logicalSetBinsAsBooleanExpr(ExpressionContainer left, ExpressionContainer right) { - logicalSetBinAsBooleanExpr(left); - logicalSetBinAsBooleanExpr(right); + private void overrideType(AbstractPart part, AbstractPart oppositePart) { + // Override using Implicit type detection + Exp.Type implicitType = partTypeToExpType.get(oppositePart.getPartType()); + + if (part.getPartType() == BIN_PART) { + if (implicitType != null) { + ((BinPart) part).updateExp(implicitType); + } + } else { // CDT: ListPart or MapPart + if (implicitType == null) { + implicitType = TypeUtils.getDefaultType(part); + } + part.setExpType(implicitType); + } } /** @@ -744,7 +810,7 @@ private static Filter handleBinArithmeticExpression(BinPart bin, AbstractPart op ExprPartsOperation operation, FilterOperationType type, boolean binOnLeft) { // Only support integer arithmetic in filters - if (operand.getPartType() != INT_OPERAND || externalOperand.getPartType() != INT_OPERAND) { + if (operand.getPartType() != AbstractPart.PartType.INT_OPERAND || externalOperand.getPartType() != AbstractPart.PartType.INT_OPERAND) { throw new NoApplicableFilterException( "Only integer operands are supported in arithmetic filter expressions"); } @@ -938,6 +1004,8 @@ private static Exp getFilterExp(ExpressionContainer expr) { if (expr.hasSecondaryIndexFilter()) return null; return switch (expr.getOperationType()) { + case OR_STRUCTURE -> orStructureToExp(expr); + case AND_STRUCTURE -> andStructureToExp(expr); case WITH_STRUCTURE -> withStructureToExp(expr); case WHEN_STRUCTURE -> whenStructureToExp(expr); case EXCLUSIVE_STRUCTURE -> exclStructureToExp(expr); @@ -990,14 +1058,44 @@ private static Exp whenStructureToExp(ExpressionContainer expr) { */ private static Exp exclStructureToExp(ExpressionContainer expr) { List expressions = new ArrayList<>(); - ExclusiveStructure whenOperandsList = (ExclusiveStructure) expr.getLeft(); // extract unary Expr operand - List operands = whenOperandsList.getOperands(); + ExclusiveStructure exclOperandsList = (ExclusiveStructure) expr.getLeft(); // extract unary Expr operand + List operands = exclOperandsList.getOperands(); for (ExpressionContainer part : operands) { expressions.add(getExp(part)); } return Exp.exclusive(expressions.toArray(new Exp[0])); } + private static Exp orStructureToExp(ExpressionContainer expr) { + List expressions = new ArrayList<>(); + List operands = ((OrStructure) expr.getLeft()).getOperands(); + for (ExpressionContainer part : operands) { + expressions.add(getExp(part)); + } + return Exp.or(expressions.toArray(new Exp[0])); + } + + /** + * Generates filter {@link Exp} for an AND structure {@link ExpressionContainer}. + * + * @param expr The {@link ExpressionContainer} representing AND structure + * @return The resulting {@link Exp} expression + */ + private static Exp andStructureToExp(ExpressionContainer expr) { + List expressions = new ArrayList<>(); + List operands = ((AndStructure) expr.getLeft()).getOperands(); + for (ExpressionContainer part : operands) { + Exp exp = getExp(part); + if (exp != null) expressions.add(exp); // Exp can be null if it is already used in secondary index + } + if (expressions.isEmpty()) { + return null; + } else if (expressions.size() > 1) { + return Exp.and(expressions.toArray(new Exp[0])); + } + return expressions.get(0); // When there is only one Exp return it + } + /** * Processes an {@link ExpressionContainer} to generate the corresponding Exp. * @@ -1323,27 +1421,10 @@ public static void traverseTree(AbstractPart part, Consumer visito 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 + if (part.getPartType() == AbstractPart.PartType.AND_STRUCTURE && depth > 0) { + List containerList = ((AndStructure) part).getOperands(); + containerList.forEach(container -> traverseTree(container, visitor, depth - 1, stopCondition)); + } } } diff --git a/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java index bde2688..b132b80 100644 --- a/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ArithmeticExpressionsTests.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import static com.aerospike.dsl.util.TestUtils.parseFilterExp; -import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ArithmeticExpressionsTests { diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index ae218c9..821226d 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -46,6 +46,10 @@ void binEquals() { parseFilterExpressionAndCompare("$.intBin1 == 100", Exp.eq(Exp.intBin("intBin1"), Exp.val(100))); parseFilterExpressionAndCompare("$.strBin == \"yes\"", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); parseFilterExpressionAndCompare("$.strBin == 'yes'", Exp.eq(Exp.stringBin("strBin"), Exp.val("yes"))); + + parseFilterExpressionAndCompare("100 == $.intBin1", Exp.eq(Exp.val(100), Exp.intBin("intBin1"))); + parseFilterExpressionAndCompare("\"yes\" == $.strBin", Exp.eq(Exp.val("yes"), Exp.stringBin("strBin"))); + parseFilterExpressionAndCompare("'yes' == $.strBin", Exp.eq(Exp.val("yes"), Exp.stringBin("strBin"))); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java index 9665f20..059b30f 100644 --- a/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ImplicitTypesTests.java @@ -4,8 +4,6 @@ import com.aerospike.dsl.util.TestUtils; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; - public class ImplicitTypesTests { @Test @@ -81,7 +79,10 @@ void thirdDegreeComplicatedDefaultInt() { void thirdDegreeComplicatedImplicitCastingFloat() { TestUtils.parseFilterExpressionAndCompare("(($.apples + $.bananas) + $.oranges) > 10.5", Exp.gt( - Exp.add(Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), Exp.floatBin("oranges")), + Exp.add( + Exp.add(Exp.floatBin("apples"), Exp.floatBin("bananas")), + Exp.floatBin("oranges") + ), Exp.val(10.5)) ); } @@ -129,7 +130,8 @@ void complicatedWhenImplicitTypeInt() { ) ); - TestUtils.parseFilterExpressionAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => $.a4+1))", + TestUtils.parseFilterExpressionAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, " + + "default => $.a4+1))", expected); } @@ -155,7 +157,8 @@ void complicatedWhenImplicitTypeString() { ) ); - TestUtils.parseFilterExpressionAndCompare("$.a == (when($.b == 1 => $.a1, $.b == 2 => $.a2, $.b == 3 => $.a3, default => \"hello\"))", + TestUtils.parseFilterExpressionAndCompare("$.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 32382d6..f3ed559 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -11,7 +11,6 @@ import java.util.List; import static com.aerospike.dsl.util.TestUtils.parseFilterExp; -import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; class ListExpressionsTests { @@ -398,18 +397,17 @@ void listReturnTypes() { Exp.listBin("listBin1") ), Exp.val(5)); - // Implicit detect as Int TestUtils.parseFilterExpressionAndCompare("$.listBin1.[0].get(return: COUNT) == 5", expected); expected = Exp.eq( ListExp.getByIndex( ListReturnType.EXISTS, - Exp.Type.INT, + Exp.Type.BOOL, Exp.val(0), Exp.listBin("listBin1") ), Exp.val(true)); - // Implicit detect as Int + // Implicit detect as BOOL TestUtils.parseFilterExpressionAndCompare("$.listBin1.[0].get(return: EXISTS) == true", expected); expected = Exp.eq( @@ -420,7 +418,7 @@ void listReturnTypes() { Exp.listBin("listBin1") ), Exp.val(1)); - // Implicit detect as Int + // Implicit detect as INT TestUtils.parseFilterExpressionAndCompare("$.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 18638b7..1c1cbf8 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -7,7 +7,6 @@ import org.opentest4j.AssertionFailedError; import static com.aerospike.dsl.util.TestUtils.parseFilterExp; -import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class LogicalExpressionsTests { @@ -25,7 +24,6 @@ void binLogicalAndOrCombinations() { ), Exp.lt(Exp.intBin("intBin3"), Exp.val(100)) ); - // TODO: what should be the default behaviour with no parentheses? TestUtils.parseFilterExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 < 100", expected2); TestUtils.parseFilterExpressionAndCompare("($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 < 100", expected2); TestUtils.parseFilterExpressionAndCompare("(($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 < 100)", expected2); @@ -39,7 +37,7 @@ void binLogicalAndOrCombinations() { ); TestUtils.parseFilterExpressionAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected3); TestUtils.parseFilterExpressionAndCompare("$.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100)", expected3); - // check that parentheses make difference + // Check that parentheses make difference assertThatThrownBy( () -> TestUtils.parseFilterExpressionAndCompare("($.intBin1 > 100 and ($.intBin2 > 100 or $.intBin3 < 100))", expected2) ).isInstanceOf(AssertionFailedError.class); @@ -66,20 +64,17 @@ void binLogicalExclusive() { Exp.eq(Exp.intBin("d"), Exp.val(4)))); } - //TODO: FMWK-488 - //@Test + @Test void flatHierarchyAnd() { - TestUtils.parseFilterExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 < 100", + TestUtils.parseFilterExpressionAndCompare( + "$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 < 100 and $.intBin4 < 100", Exp.and( - Exp.gt( - Exp.intBin("intBin1"), - Exp.val(100)), - Exp.gt( - Exp.intBin("intBin2"), - Exp.val(100) - ), - Exp.lt(Exp.intBin("intBin3"), - Exp.val(100)))); + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), + Exp.lt(Exp.intBin("intBin3"), Exp.val(100)), + Exp.lt(Exp.intBin("intBin4"), Exp.val(100)) + ) + ); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java index 524f20d..68158b3 100644 --- a/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/MapAndListExpressionsTests.java @@ -10,8 +10,6 @@ import com.aerospike.dsl.util.TestUtils; import org.junit.jupiter.api.Test; -import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; - public class MapAndListExpressionsTests { @Test @@ -130,7 +128,7 @@ void mapListMap() { TestUtils.parseFilterExpressionAndCompare("$.mapBin1.a.[0].cc.get(type: INT) > 100", expected); } - //@Test +// @Test void mapAndListCombinations() { Exp expected = Exp.gt( ListExp.size( @@ -139,11 +137,11 @@ void mapAndListCombinations() { Exp.Type.LIST, Exp.val("shape"), Exp.mapBin("mapBin1") - //CTX.mapKey(Value.get("shape")) +// CTX.mapKey(Value.get("shape")) ) ), Exp.val(2)); - //translateAndCompare("$.mapBin1.shape.[].size() > 2", expected); - //translateAndCompare("$.mapBin1.a.dd.[1].{#0}.get(return: UNORDERED_MAP)", expected); +// TestUtils.parseFilterExpressionAndCompare("$.mapBin1.shape.[].count() > 2", expected); +// TestUtils.parseFilterExpressionAndCompare("$.mapBin1.a.dd.[1].{#0}.get(return: UNORDERED_MAP)", expected); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java index f2aa56d..b0a21a9 100644 --- a/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ArithmeticFiltersTests.java @@ -48,9 +48,7 @@ void add() { 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"); + assertThat(parseFilter("($.apples + $.bananas + 5) > 10")).isNull(); // not supported by the current grammar } @Test @@ -85,9 +83,7 @@ void sub() { 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"); + assertThat(parseFilter("($.apples - $.bananas - 5) > 10")).isNull(); // not supported by the current grammar } @Test @@ -126,9 +122,7 @@ void mul() { 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"); + assertThat(parseFilter("($.apples * $.bananas - 5) > 10")).isNull(); // not supported by the current grammar } @Test diff --git a/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java index 091cc94..aec4b3a 100644 --- a/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/BinFiltersTests.java @@ -16,8 +16,8 @@ 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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("stringBin1").indexType(IndexType.STRING).binValuesRatio(1).build() ); IndexContext INDEX_FILTER_INPUT = IndexContext.of(NAMESPACE, INDEXES); @@ -44,14 +44,13 @@ void binGT() { @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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).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); + parseFilterAndCompare("$.intBin1 > 100 and $.intBin2 < 1000", null); // No indexes given } @Test diff --git a/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java b/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java index 53f0106..c220ae2 100644 --- a/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java +++ b/src/test/java/com/aerospike/dsl/parsedExpression/LogicalParsedExpressionTests.java @@ -14,6 +14,8 @@ public class LogicalParsedExpressionTests { + String NAMESPACE = "test1"; + @Test void binLogical_AND_no_indexes() { Filter filter = null; @@ -25,66 +27,103 @@ void binLogical_AND_no_indexes() { @Test void binLogical_AND_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(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).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)); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).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); + // Complementary Exp is provided for the remaining part of the expression Exp exp = Exp.gt(Exp.intBin("intBin2"), Exp.val(100)); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_one_index() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build()); - String namespace = "test1"; + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build()); Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); Exp exp = Exp.gt(Exp.intBin("intBin2"), Exp.val(100)); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); + } + + @Test + void binLogical_AND_AND_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)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) + ); + TestUtils.parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp); + } + + @Test + void binLogical_AND_OR_OR_no_indexes() { + 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)), + Exp.gt(Exp.intBin("intBin4"), Exp.val(100)) + ); + TestUtils.parseDslExpressionAndCompare( + "$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 > 100 or $.intBin4 > 100", filter, exp); + } + + @Test + void binLogical_OR_AND_AND_no_indexes() { + Filter filter = null; + Exp exp = Exp.or( + Exp.gt(Exp.intBin("intBin1"), Exp.val(100)), + Exp.and( + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin4"), Exp.val(100)) + ) + ); + TestUtils.parseDslExpressionAndCompare( + "$.intBin1 > 100 or $.intBin2 > 100 and $.intBin3 > 100 and $.intBin4 > 100", filter, exp); } @Test void binLogical_AND_AND_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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).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)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_AND_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() + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(100).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(100).build(), + Index.builder().namespace(NAMESPACE).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( @@ -92,17 +131,16 @@ void binLogical_AND_AND_all_indexes_same_cardinality() { Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_AND_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() + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).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( @@ -110,19 +148,18 @@ void binLogical_AND_AND_all_indexes_no_cardinality() { Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_AND_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(), + Index.builder().namespace(NAMESPACE).bin("intBin2").binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).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() + Index.builder().namespace(NAMESPACE).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( @@ -130,23 +167,22 @@ void binLogical_AND_AND_all_indexes_partial_data() { Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_AND_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() + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).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)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 and $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test @@ -160,82 +196,73 @@ void binLogical_OR_no_indexes() { @Test void binLogical_OR_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(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).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)) ); parseDslExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_one_index() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).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)) ); parseDslExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_OR_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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).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("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100 or $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_OR_all_indexes_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).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("intBin1"), Exp.val(100)), + Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 or $.intBin2 > 100 or $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + 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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.and( @@ -245,38 +272,36 @@ void binLogical_prioritizedAND_OR_indexed() { Exp.gt(Exp.intBin("intBin3"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin1 > 100 and $.intBin2 > 100 or $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin1 > 100 and $.intBin2 > 100) or $.intBin3 > 100", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @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(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin3", 101, Long.MAX_VALUE); Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin2"), Exp.val(100)), Exp.gt(Exp.intBin("intBin1"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_prioritizedOR_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; // Cardinality is the same, is it correct that intBin3 is chosen because it is the only one filtered? Filter filter = Filter.range("intBin3", 101, Long.MAX_VALUE); Exp exp = Exp.or( @@ -284,19 +309,18 @@ void binLogical_AND_prioritizedOR_indexed_same_cardinality() { Exp.gt(Exp.intBin("intBin1"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_AND_prioritizedOR_indexed_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(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).build() ); - String namespace = "test1"; // Cardinality is the same, is it correct that intBin3 is chosen because it is the only one filtered? Filter filter = Filter.range("intBin3", 101, Long.MAX_VALUE); Exp exp = Exp.or( @@ -304,19 +328,18 @@ void binLogical_AND_prioritizedOR_indexed_no_cardinality() { Exp.gt(Exp.intBin("intBin1"), Exp.val(100)) ); parseDslExpressionAndCompare("$.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 and ($.intBin2 > 100 or $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_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(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -326,19 +349,18 @@ void binLogical_OR_prioritizedOR_indexed() { ) ); parseDslExpressionAndCompare("$.intBin3 > 100 or ($.intBin2 > 100 or $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 or ($.intBin2 > 100 or $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_prioritizedOR_indexed_same_cardinality() { 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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -348,19 +370,18 @@ void binLogical_OR_prioritizedOR_indexed_same_cardinality() { ) ); parseDslExpressionAndCompare("$.intBin3 > 100 or ($.intBin2 > 100 or $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 or ($.intBin2 > 100 or $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_prioritizedAND_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() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -370,19 +391,18 @@ void binLogical_OR_prioritizedAND_indexed() { ) ); parseDslExpressionAndCompare("$.intBin3 > 100 or ($.intBin2 > 100 and $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 or ($.intBin2 > 100 and $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR_prioritizedAND_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -392,20 +412,19 @@ void binLogical_OR_prioritizedAND_indexed_same_cardinality() { ) ); parseDslExpressionAndCompare("$.intBin3 > 100 or ($.intBin2 > 100 and $.intBin1 > 100)", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("($.intBin3 > 100 or ($.intBin2 > 100 and $.intBin1 > 100))", filter, exp, - IndexContext.of(namespace, indexes)); + IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedAND_OR_prioritizedAND_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(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.and( @@ -418,20 +437,19 @@ void binLogical_prioritizedAND_OR_prioritizedAND_indexed() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 and $.intBin4 > 100) or ($.intBin2 > 100 and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 and $.intBin4 > 100) or ($.intBin2 > 100 and $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedAND_OR_prioritizedAND_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.or( Exp.and( @@ -444,20 +462,19 @@ void binLogical_prioritizedAND_OR_prioritizedAND_indexed_same_cardinality() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 and $.intBin4 > 100) or ($.intBin2 > 100 and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 and $.intBin4 > 100) or ($.intBin2 > 100 and $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_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(1).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.and( Exp.or( @@ -470,20 +487,19 @@ void binLogical_prioritizedOR_AND_prioritizedOR_indexed() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 or $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 or $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_AND_prioritizedOR_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = null; Exp exp = Exp.and( Exp.or( @@ -496,20 +512,19 @@ void binLogical_prioritizedOR_AND_prioritizedOR_indexed_same_cardinality() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 or $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 or $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_AND_prioritizedAND_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(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin2", 101, Long.MAX_VALUE); Exp exp = Exp.and( Exp.or( @@ -519,20 +534,19 @@ void binLogical_prioritizedOR_AND_prioritizedAND_indexed() { Exp.gt(Exp.intBin("intBin1"), Exp.val(100)) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 and $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_AND_prioritizedAND_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); Exp exp = Exp.and( Exp.or( @@ -542,20 +556,19 @@ void binLogical_prioritizedOR_AND_prioritizedAND_indexed_same_cardinality() { Exp.gt(Exp.intBin("intBin2"), Exp.val(100)) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100) and ($.intBin2 > 100 and $.intBin1 > 100))", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withFilter() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).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(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -565,20 +578,19 @@ void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withFilter() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withTheOnlyFilter() { 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(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; // This expression part does not have the index with the largest cardinality, but it is the only applicable // because all other parts participate in an OR-combined query Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); @@ -590,20 +602,19 @@ void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withTheOnlyFilter() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_prioritizedAND_AND_indexed_same_cardinality() { List indexes = List.of( - Index.builder().namespace("test1").bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(1).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); Exp exp = Exp.or( Exp.gt(Exp.intBin("intBin3"), Exp.val(100)), @@ -613,20 +624,19 @@ void binLogical_prioritizedOR_prioritizedAND_AND_indexed_same_cardinality() { ) ); parseDslExpressionAndCompare("($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100 and $.intBin2 > 100) and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withFilterPerCardinality() { 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(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).binValuesRatio(1).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).binValuesRatio(0).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).binValuesRatio(0).build() ); - String namespace = "test1"; // This expression part has the index with the largest cardinality and is applicable for Filter building, // another applicable expression part is "$.intBin1 > 100", but intBin1 has index with lower cardinality Filter filter = Filter.range("intBin2", 101, Long.MAX_VALUE); @@ -638,22 +648,21 @@ void binLogical_prioritizedOR_prioritizedAND_AND_indexed_withFilterPerCardinalit Exp.gt(Exp.intBin("intBin1"), Exp.val(100)) ); parseDslExpressionAndCompare("(($.intBin3 > 100 or $.intBin4 > 100) and $.intBin2 > 100) and $.intBin1 > 100", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); parseDslExpressionAndCompare("((($.intBin3 > 100 or $.intBin4 > 100) and $.intBin2 > 100) and $.intBin1 > 100)", - filter, exp, IndexContext.of(namespace, indexes)); + filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR2_OR1_AND2_AND_AND1_indexed_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(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin5").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin6").indexType(IndexType.NUMERIC).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin5").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin6").indexType(IndexType.NUMERIC).build() ); - String namespace = "test1"; Filter filter = Filter.range("intBin1", 101, Long.MAX_VALUE); Exp exp = Exp.and( Exp.or( @@ -670,23 +679,22 @@ void binLogical_OR2_OR1_AND2_AND_AND1_indexed_no_cardinality() { ); String dslString = "(($.intBin3 > 100 or $.intBin4 > 100) or ($.intBin5 > 100 and $.intBin6 > 100)) " + "and ($.intBin2 > 100 and $.intBin1 > 100)"; - parseDslExpressionAndCompare(dslString, filter, exp, IndexContext.of(namespace, indexes)); - parseDslExpressionAndCompare("(" + dslString + ")", filter, exp, IndexContext.of(namespace, indexes)); + parseDslExpressionAndCompare(dslString, filter, exp, IndexContext.of(NAMESPACE, indexes)); + parseDslExpressionAndCompare("(" + dslString + ")", filter, exp, IndexContext.of(NAMESPACE, indexes)); } @Test void binLogical_OR2_OR1_AND2_AND_AND2_OR1_AND2_indexed_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(), - Index.builder().namespace("test1").bin("intBin3").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin4").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin5").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin6").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin7").indexType(IndexType.NUMERIC).build(), - Index.builder().namespace("test1").bin("intBin8").indexType(IndexType.NUMERIC).build() + Index.builder().namespace(NAMESPACE).bin("intBin1").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin2").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin3").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin4").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin5").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin6").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin7").indexType(IndexType.NUMERIC).build(), + Index.builder().namespace(NAMESPACE).bin("intBin8").indexType(IndexType.NUMERIC).build() ); - String namespace = "test1"; // No Filter can be built as all expression parts participate in OR-combined queries Filter filter = null; Exp exp = Exp.and( @@ -713,7 +721,18 @@ void binLogical_OR2_OR1_AND2_AND_AND2_OR1_AND2_indexed_no_cardinality() { ); String dslString = "(($.intBin3 > 100 or $.intBin4 > 100) or ($.intBin5 > 100 and $.intBin6 > 100)) " + "and (($.intBin2 > 100 and $.intBin1 > 100) or ($.intBin7 > 100 and $.intBin8 > 100))"; - parseDslExpressionAndCompare(dslString, filter, exp, IndexContext.of(namespace, indexes)); - parseDslExpressionAndCompare("(" + dslString + ")", filter, exp, IndexContext.of(namespace, indexes)); + parseDslExpressionAndCompare(dslString, filter, exp, IndexContext.of(NAMESPACE, indexes)); + parseDslExpressionAndCompare("(" + dslString + ")", filter, exp, IndexContext.of(NAMESPACE, indexes)); } + + @Test + void binLogical_EXCL_EXCL_no_indexes() { + Filter filter = null; + Exp exp = Exp.exclusive( + Exp.eq(Exp.stringBin("hand"), Exp.val("stand")), + Exp.eq(Exp.stringBin("pun"), Exp.val("done")) + ); + TestUtils.parseDslExpressionAndCompare("exclusive($.hand == \"stand\", $.pun == \"done\")", filter, exp); + } + } diff --git a/src/test/java/com/aerospike/dsl/util/TestUtils.java b/src/test/java/com/aerospike/dsl/util/TestUtils.java index ce36c79..ff77e7f 100644 --- a/src/test/java/com/aerospike/dsl/util/TestUtils.java +++ b/src/test/java/com/aerospike/dsl/util/TestUtils.java @@ -15,53 +15,124 @@ public class TestUtils { private final DSLParserImpl parser = new DSLParserImpl(); + /** + * Parses the given DSL expression and returns a {@link ParsedExpression} object. + * + * @param input The string input representing DSL expression + * @param indexContext The {@link IndexContext} to be used during parsing + * @return A {@link ParsedExpression} object containing the result of the parsing + */ public static ParsedExpression parseExpression(String input, IndexContext indexContext) { return parser.parseExpression(input, indexContext); } + /** + * Parses the given DSL expression and extracts the resulting {@link Exp} object. + * + * @param input The string input representing DSL expression + * @return The {@link Exp} object derived from the parsed filter expression + */ public static Exp parseFilterExp(String input) { return parser.parseExpression(input).getResult().getExp(); } + /** + * Parses the given DSL expression and builds an {@link Expression} object from the resulting + * {@link Exp}. + * + * @param input The string input representing DSL expression + * @return An {@link Expression} object built from the parsed filter expression + */ public static Expression parseFilterExpression(String input) { return Exp.build(parser.parseExpression(input).getResult().getExp()); } + /** + * Parses the given DSL expression, extracts the resulting {@link Exp} object, converts it to an {@link Expression} object, + * and then asserts that it is equal to the {@code expected} {@link Exp} also built into an {@link Expression}. + * + * @param input The string input representing DSL expression + * @param expected The expected {@link Exp} object to compare against the parsed result + */ public static void parseFilterExpressionAndCompare(String input, Exp expected) { Expression actualExpression = Exp.build(parser.parseExpression(input).getResult().getExp()); Expression expectedExpression = Exp.build(expected); - assertEquals(actualExpression, expectedExpression); + assertEquals(expectedExpression, actualExpression); } + /** + * Parses the given DSL expression and returns the resulting {@link Filter} object. + * This method uses the parser without an {@link IndexContext}. + * + * @param input The string input representing DSL expression + * @return A {@link Filter} object derived from the parsed result + */ public static Filter parseFilter(String input) { return parser.parseExpression(input).getResult().getFilter(); } + /** + * Parses the given DL expression using the provided {@link IndexContext} and returns the resulting {@link Filter} object. + * + * @param input The string input representing DSL expression + * @param indexContext The {@link IndexContext} to be used during parsing + * @return A {@link Filter} object derived from the parsed result + */ public static Filter parseFilter(String input, IndexContext indexContext) { return parser.parseExpression(input, indexContext).getResult().getFilter(); } + /** + * Parses the given DSL expression and asserts that the result is equal to the {@code expected} {@link Filter} object. + * + * @param input The string input representing DSL expression + * @param expected The expected {@link Filter} object to compare against the parsed result + */ public static void parseFilterAndCompare(String input, Filter expected) { Filter actualFilter = parseFilter(input); - assertEquals(actualFilter, expected); + assertEquals(expected, actualFilter); } + /** + * Parses the given DSL expression using the provided {@link IndexContext} and asserts that the result is equal to the {@code expected} {@link Filter} object. + * + * @param input The string input representing DSL expression + * @param indexContext The {@link IndexContext} to be used during parsing + * @param expected The expected {@link Filter} object to compare against the parsed result + */ public static void parseFilterAndCompare(String input, IndexContext indexContext, Filter expected) { Filter actualFilter = parseFilter(input, indexContext); - assertEquals(actualFilter, expected); + assertEquals(expected, actualFilter); } + /** + * Parses the given DSL expression and compares the resulting + * {@link Filter} and {@link Exp} components with the expected {@code filter} and {@code exp}. + * + * @param input The string input representing DSL expression + * @param filter The expected {@link Filter} component of the parsed result + * @param exp The expected {@link Exp} component of the parsed result. Can be {@code null} + */ public static void parseDslExpressionAndCompare(String input, Filter filter, Exp exp) { ParsedExpression actualExpression = parser.parseExpression(input); - assertEquals(actualExpression.getResult().getFilter(), filter); + assertEquals(filter, actualExpression.getResult().getFilter()); Exp actualExp = actualExpression.getResult().getExp(); - assertEquals(actualExp == null ? null : Exp.build(actualExp), exp == null ? null : Exp.build(exp)); + assertEquals(exp == null ? null : Exp.build(exp), actualExp == null ? null : Exp.build(actualExp)); } + /** + * Parses the given DSL expression using the provided {@link IndexContext} + * and compares the resulting {@link Filter} and {@link Exp} components with the expected {@code filter} and {@code exp}. + * + * @param input The string input representing DSL expression + * @param filter The expected {@link Filter} component of the parsed result + * @param exp The expected {@link Exp} component of the parsed result. Can be {@code null} + * @param indexContext The {@link IndexContext} to be used during parsing + */ public static void parseDslExpressionAndCompare(String input, Filter filter, Exp exp, IndexContext indexContext) { ParsedExpression actualExpression = parser.parseExpression(input, indexContext); - assertEquals(actualExpression.getResult().getFilter(), filter); + assertEquals(filter, actualExpression.getResult().getFilter()); Exp actualExp = actualExpression.getResult().getExp(); - assertEquals(actualExp == null ? null : Exp.build(actualExp), exp == null ? null : Exp.build(exp)); + assertEquals(exp == null ? null : Exp.build(exp), actualExp == null ? null : Exp.build(actualExp)); } }