diff --git a/docs/guides/07-conditional-logic-control-structures.md b/docs/guides/07-conditional-logic-control-structures.md index e029817..cadcb3d 100644 --- a/docs/guides/07-conditional-logic-control-structures.md +++ b/docs/guides/07-conditional-logic-control-structures.md @@ -1,6 +1,6 @@ -# Conditional Logic: Control Structures `with` and `when` +# Conditional Logic: Control Structures `let` and `when` -The Expression DSL supports conditional logic through `when` and `with` control structures, similar to a `CASE` statement in SQL. This allows you to build sophisticated expressions that can return different values based on a set of conditions. +The Expression DSL supports conditional logic through `when` and `let` control structures, similar to a `CASE` statement in SQL. This allows you to build sophisticated expressions that can return different values based on a set of conditions. This is particularly useful for server-side data transformation or implementing complex business rules. @@ -86,27 +86,27 @@ Expression filter = Exp.build(parsed.getResult().getExp()); The `when` structure enables you to push complex conditional logic directly to the server, reducing the need to pull data to the client for evaluation and minimizing data transfer. -## Control Structure `with` +## Control Structure `let` -The basic structure of a with expression allows you to declare temporary variables and use them within a subsequent expression: +The basic structure of a let expression allows you to declare temporary variables and use them within a subsequent expression: ``` -with ( +let ( var1 = val1, var2 = val2, ... -) do (expression) +) then (expression) ``` -The server evaluates the variable assignments in order, making each variable available for use in later assignments and in the final expression after the `do` keyword. +The server evaluates the variable assignments in order, making each variable available for use in later assignments and in the final expression after the `then` keyword. ## Simple Example For a simpler illustration of variable usage and calculations: ``` -"with (x = 1, y = ${x} + 1) do (${x} + ${y})" +"let (x = 1, y = ${x} + 1) then (${x} + ${y})" ``` This expression: @@ -115,7 +115,7 @@ This expression: 2. Defines variable y with value ${x} + 1 (which evaluates to 2) 3. Returns the result of ${x} + ${y} (which evaluates to 3) -The `with` structure enables us to create more readable and maintainable expressions by breaking complex logic into named variables. This is especially valuable when the same intermediate calculation is used multiple times in an expression or when the expression logic is complex. +The `let` structure enables us to create more readable and maintainable expressions by breaking complex logic into named variables. This is especially valuable when the same intermediate calculation is used multiple times in an expression or when the expression logic is complex. ## Use Case: More Complex Calculations @@ -129,15 +129,15 @@ Imagine we want to calculate a user's eligibility score based on multiple factor ### DSL String -We can use the `with` construct to make this complex calculation more readable and maintainable: +We can use the `let` construct to make this complex calculation more readable and maintainable: ``` -"with ( +"let ( baseScore = $.accountAgeMonths * 0.5, transactionBonus = $.transactionCount > 100 ? 25 : 0, creditMultiplier = $.creditScore > 700 ? 1.5 : 1.0, finalScore = (${baseScore} + ${transactionBonus}) * ${creditMultiplier} -) do (${finalScore} >= 75 && $.premiumEligible == true)" +) then (${finalScore} >= 75 && $.premiumEligible == true)" ``` Let's break this down: @@ -152,12 +152,12 @@ Let's break this down: ### Using Static DSL String in Java ```java -String dslString = "with (" + +String dslString = "let (" + "baseScore = $.accountAgeMonths * 0.5, " + "transactionBonus = $.transactionCount > 100 ? 25 : 0, " + "creditMultiplier = $.creditScore > 700 ? 1.5 : 1.0, " + "finalScore = (${baseScore} + ${transactionBonus}) * ${creditMultiplier}" + - ") do (${finalScore} >= 75 && $.premiumEligible == true)"; + ") then (${finalScore} >= 75 && $.premiumEligible == true)"; ExpressionContext context = ExpressionContext.of(dslString); ParsedExpression parsed = parser.parseExpression(context); @@ -173,20 +173,20 @@ queryPolicy.filterExp = filter; ### Using DSL String with Placeholders in Java -You can also use placeholders within a with expression for greater flexibility. Placeholders mark the places where values provided separately are matched by indexes. +You can also use placeholders within a let expression for greater flexibility. Placeholders mark the places where values provided separately are matched by indexes. This way the same DSL String can be used multiple times with different values for the same placeholders. For example, let's add placeholders to our previous DSL expression and use the same API for generating an `Expression`: ```java -String dsl = "with (" + +String dsl = "let (" + "baseScore = $.accountAgeMonths * ?0, " + "transactionThreshold = ?1, " + "transactionBonus = $.transactionCount > ${transactionThreshold} ? ?2 : 0, " + "creditThreshold = ?3, " + "creditMultiplier = $.creditScore > ${creditThreshold} ? ?4 : 1.0, " + "finalScore = (${baseScore} + ${transactionBonus}) * ${creditMultiplier}" + - ") do (${finalScore} >= ?5 && $.premiumEligible == true)"; + ") then (${finalScore} >= ?5 && $.premiumEligible == true)"; PlaceholderValues values = PlaceholderValues.of( 0.5, // Age multiplier diff --git a/src/main/antlr4/com/aerospike/dsl/Condition.g4 b/src/main/antlr4/com/aerospike/dsl/Condition.g4 index f8ae60b..f88d32c 100644 --- a/src/main/antlr4/com/aerospike/dsl/Condition.g4 +++ b/src/main/antlr4/com/aerospike/dsl/Condition.g4 @@ -21,7 +21,7 @@ logicalAndExpression basicExpression : 'not' '(' expression ')' # NotExpression | 'exclusive' '(' expression (',' expression)+ ')' # ExclusiveExpression - | 'with' '(' variableDefinition (',' variableDefinition)* ')' 'do' '(' expression ')' # WithExpression + | 'let' '(' variableDefinition (',' variableDefinition)* ')' 'then' '(' expression ')' # LetExpression | 'when' '(' expressionMapping (',' expressionMapping)* ',' 'default' '=>' expression ')' # WhenExpression | comparisonExpression # ComparisonExpressionWrapper ; diff --git a/src/main/java/com/aerospike/dsl/ParsedExpression.java b/src/main/java/com/aerospike/dsl/ParsedExpression.java index f057b43..b0519ce 100644 --- a/src/main/java/com/aerospike/dsl/ParsedExpression.java +++ b/src/main/java/com/aerospike/dsl/ParsedExpression.java @@ -13,7 +13,6 @@ import static com.aerospike.dsl.parts.AbstractPart.PartType.EXPRESSION_CONTAINER; import static com.aerospike.dsl.visitor.VisitorUtils.buildExpr; - /** * A class to build and store the results of DSL expression parsing: parsed {@code expressionTree}, {@code indexesMap} * of given indexes, {@code placeholderValues} to match with placeholders and {@link ParseResult} that holds diff --git a/src/main/java/com/aerospike/dsl/parts/AbstractPart.java b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java index 4f4e1d2..582116b 100644 --- a/src/main/java/com/aerospike/dsl/parts/AbstractPart.java +++ b/src/main/java/com/aerospike/dsl/parts/AbstractPart.java @@ -29,8 +29,8 @@ public enum PartType { STRING_OPERAND, LIST_OPERAND, MAP_OPERAND, - WITH_OPERAND, - WITH_STRUCTURE, + LET_OPERAND, + LET_STRUCTURE, WHEN_STRUCTURE, EXCLUSIVE_STRUCTURE, AND_STRUCTURE, diff --git a/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java index 0f737c0..1abc149 100644 --- a/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java +++ b/src/main/java/com/aerospike/dsl/parts/ExpressionContainer.java @@ -81,7 +81,7 @@ public enum ExprPartsOperation { LT, LTEQ, IN, - WITH_STRUCTURE, // unary + LET_STRUCTURE, // unary WHEN_STRUCTURE, // unary EXCLUSIVE_STRUCTURE, // unary AND_STRUCTURE, diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java index 7ad26e3..565fbb6 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapKeyList.java @@ -49,4 +49,4 @@ public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType return MapExp.getByKeyList(cdtReturnType, Exp.val(keyList), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } -} \ No newline at end of file +} diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/LetStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/LetStructure.java new file mode 100644 index 0000000..faaf0eb --- /dev/null +++ b/src/main/java/com/aerospike/dsl/parts/controlstructure/LetStructure.java @@ -0,0 +1,18 @@ +package com.aerospike.dsl.parts.controlstructure; + +import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.operand.LetOperand; +import lombok.Getter; + +import java.util.List; + +@Getter +public class LetStructure extends AbstractPart { + + private final List operands; + + public LetStructure(List operands) { + super(PartType.LET_STRUCTURE); + this.operands = operands; + } +} diff --git a/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java b/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java deleted file mode 100644 index 6e1c413..0000000 --- a/src/main/java/com/aerospike/dsl/parts/controlstructure/WithStructure.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.aerospike.dsl.parts.controlstructure; - -import com.aerospike.dsl.parts.AbstractPart; -import com.aerospike.dsl.parts.operand.WithOperand; -import lombok.Getter; - -import java.util.List; - -@Getter -public class WithStructure extends AbstractPart { - - private final List operands; - - public WithStructure(List operands) { - super(PartType.WITH_STRUCTURE); - this.operands = operands; - } -} diff --git a/src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java b/src/main/java/com/aerospike/dsl/parts/operand/LetOperand.java similarity index 64% rename from src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java rename to src/main/java/com/aerospike/dsl/parts/operand/LetOperand.java index 6265edf..3c74d3f 100644 --- a/src/main/java/com/aerospike/dsl/parts/operand/WithOperand.java +++ b/src/main/java/com/aerospike/dsl/parts/operand/LetOperand.java @@ -5,22 +5,22 @@ import lombok.Setter; @Getter -public class WithOperand extends AbstractPart { +public class LetOperand extends AbstractPart { private final String string; @Setter private AbstractPart part; private final boolean isLastPart; - public WithOperand(AbstractPart part, String string) { - super(PartType.WITH_OPERAND); + public LetOperand(AbstractPart part, String string) { + super(PartType.LET_OPERAND); this.string = string; this.part = part; this.isLastPart = false; } - public WithOperand(AbstractPart part, boolean isLastPart) { - super(PartType.WITH_OPERAND); + public LetOperand(AbstractPart part, boolean isLastPart) { + super(PartType.LET_OPERAND); this.string = null; this.part = part; this.isLastPart = isLastPart; diff --git a/src/main/java/com/aerospike/dsl/parts/operand/OperandFactory.java b/src/main/java/com/aerospike/dsl/parts/operand/OperandFactory.java index cedea87..2bfb01e 100644 --- a/src/main/java/com/aerospike/dsl/parts/operand/OperandFactory.java +++ b/src/main/java/com/aerospike/dsl/parts/operand/OperandFactory.java @@ -77,4 +77,3 @@ static AbstractPart createOperand(Object value) { } } } - diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index b5175c5..9835ec2 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -20,7 +20,7 @@ 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.controlstructure.LetStructure; import com.aerospike.dsl.parts.operand.*; import com.aerospike.dsl.parts.path.BasePath; import com.aerospike.dsl.parts.path.BinPart; @@ -43,29 +43,29 @@ public class ExpressionConditionVisitor extends ConditionBaseVisitor { - private int withNestingDepth = 0; + private int letNestingDepth = 0; @Override - public AbstractPart visitWithExpression(ConditionParser.WithExpressionContext ctx) { - withNestingDepth++; + public AbstractPart visitLetExpression(ConditionParser.LetExpressionContext ctx) { + letNestingDepth++; try { - List expressions = new ArrayList<>(); + List expressions = new ArrayList<>(); // iterate through each definition for (ConditionParser.VariableDefinitionContext vdc : ctx.variableDefinition()) { AbstractPart part = visit(vdc.expression()); - WithOperand withOperand = new WithOperand(part, vdc.stringOperand().getText()); - expressions.add(withOperand); + LetOperand letOperand = new LetOperand(part, vdc.stringOperand().getText()); + expressions.add(letOperand); } - // last expression is the action (described after "do") - expressions.add(new WithOperand(visit(ctx.expression()), true)); - if (withNestingDepth == 1) { + // last expression is the action (described after "then") + expressions.add(new LetOperand(visit(ctx.expression()), true)); + if (letNestingDepth == 1) { validateInVariableBindings(expressions); } - return new ExpressionContainer(new WithStructure(expressions), - ExpressionContainer.ExprPartsOperation.WITH_STRUCTURE); + return new ExpressionContainer(new LetStructure(expressions), + ExpressionContainer.ExprPartsOperation.LET_STRUCTURE); } finally { - withNestingDepth--; + letNestingDepth--; } } @@ -435,29 +435,29 @@ private static boolean isPathExplicitlyNonList(Path path) { /** * Validates that variables used as the right operand of an IN expression - * within a WITH block are bound to list-compatible values. + * within a LET block are bound to list-compatible values. *

* Variable definitions are known at parse time, so scalar/map bindings * can be rejected early instead of deferring to server-side failure. */ - private static void validateInVariableBindings(List expressions) { - // WITH block always has at least one variable definition (grammar-enforced) + private static void validateInVariableBindings(List expressions) { + // LET block always has at least one variable definition (grammar-enforced) Map varBindings = new HashMap<>(); - for (WithOperand operand : expressions) { + for (LetOperand operand : expressions) { if (!operand.isLastPart()) { validateInVariablesInTree(operand.getPart(), varBindings); varBindings.put(operand.getString(), operand.getPart()); } } - validateInVariablesInTree(getWithBody(expressions), varBindings); + validateInVariablesInTree(getLetBody(expressions), varBindings); } - private static AbstractPart getWithBody(List operands) { + private static AbstractPart getLetBody(List operands) { return operands.get(operands.size() - 1).getPart(); } // Handles every AbstractPart subclass that can contain expression children: - // ExpressionContainer, WithStructure, And/Or/ExclusiveStructure, WhenStructure, FunctionArgs. + // ExpressionContainer, LetStructure, And/Or/ExclusiveStructure, WhenStructure, FunctionArgs. // Leaf types (operands, BinPart, Path, etc.) are terminal — no recursion needed. // When adding a new composite AbstractPart subclass, add a branch here. private static void validateInVariablesInTree(AbstractPart part, @@ -470,15 +470,15 @@ private static void validateInVariablesInTree(AbstractPart part, if (!expr.isUnary() && expr.getRight() != null) { validateInVariablesInTree(expr.getRight(), varBindings); } - } else if (part instanceof WithStructure ws) { + } else if (part instanceof LetStructure ws) { Map merged = new HashMap<>(varBindings); - for (WithOperand op : ws.getOperands()) { + for (LetOperand op : ws.getOperands()) { if (!op.isLastPart()) { validateInVariablesInTree(op.getPart(), merged); merged.put(op.getString(), op.getPart()); } } - validateInVariablesInTree(getWithBody(ws.getOperands()), merged); + validateInVariablesInTree(getLetBody(ws.getOperands()), merged); } else if (part instanceof AndStructure s) { s.getOperands().forEach(op -> validateInVariablesInTree(op, varBindings)); } else if (part instanceof OrStructure s) { diff --git a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java index 37d9cb0..07ab640 100644 --- a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java +++ b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java @@ -17,14 +17,14 @@ 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.controlstructure.LetStructure; import com.aerospike.dsl.parts.operand.FunctionArgs; import com.aerospike.dsl.parts.operand.IntOperand; import com.aerospike.dsl.parts.operand.ListOperand; import com.aerospike.dsl.parts.operand.MetadataOperand; import com.aerospike.dsl.parts.operand.PlaceholderOperand; import com.aerospike.dsl.parts.operand.StringOperand; -import com.aerospike.dsl.parts.operand.WithOperand; +import com.aerospike.dsl.parts.operand.LetOperand; import com.aerospike.dsl.parts.path.BinPart; import com.aerospike.dsl.parts.path.Path; import com.aerospike.dsl.util.TypeUtils; @@ -1034,7 +1034,7 @@ public static AbstractPart buildExpr(ExpressionContainer expr, PlaceholderValues *

* This method traverses the expression tree starting from the root {@link ExpressionContainer}. * For each node, it checks for structures like {@link ExpressionContainer}, - * {@link WhenStructure}, and {@link WithStructure} and replaces any found placeholders with their + * {@link WhenStructure}, and {@link LetStructure} and replaces any found placeholders with their * corresponding values. *

* @@ -1046,7 +1046,7 @@ private static void resolvePlaceholders(ExpressionContainer expression, Placehol switch (part.getPartType()) { case EXPRESSION_CONTAINER -> replacePlaceholdersInExprContainer(part, placeholderValues); case WHEN_STRUCTURE -> replacePlaceholdersInWhenStructure(part, placeholderValues); - case WITH_STRUCTURE -> replacePlaceholdersInWithStructure(part, placeholderValues); + case LET_STRUCTURE -> replacePlaceholdersInLetStructure(part, placeholderValues); case EXCLUSIVE_STRUCTURE -> replacePlaceholdersInExclusiveStructure(part, placeholderValues); } }; @@ -1054,19 +1054,19 @@ private static void resolvePlaceholders(ExpressionContainer expression, Placehol } /** - * Replaces placeholders within a {@link WithStructure} object. + * Replaces placeholders within a {@link LetStructure} object. *

- * This method iterates through the operands of a given {@link WithStructure}. If an operand + * This method iterates through the operands of a given {@link LetStructure}. If an operand * is a {@link PlaceholderOperand}, it's resolved using the provided {@link PlaceholderValues} * and replaced with the resolved {@link AbstractPart}. *

* - * @param part The {@link AbstractPart} representing the {@link WithStructure} + * @param part The {@link AbstractPart} representing the {@link LetStructure} * @param placeholderValues An object storing placeholder indexes and their resolved values */ - private static void replacePlaceholdersInWithStructure(AbstractPart part, PlaceholderValues placeholderValues) { - WithStructure withStructure = (WithStructure) part; - for (WithOperand subOperand : withStructure.getOperands()) { + private static void replacePlaceholdersInLetStructure(AbstractPart part, PlaceholderValues placeholderValues) { + LetStructure letStructure = (LetStructure) part; + for (LetOperand subOperand : letStructure.getOperands()) { if (subOperand.getPart().getPartType() == PLACEHOLDER_OPERAND) { // Replace placeholder part with the resolved operand subOperand.setPart(((PlaceholderOperand) subOperand.getPart()).resolve(placeholderValues)); @@ -1334,7 +1334,7 @@ private static Exp getFilterExp(ExpressionContainer expr) { return switch (expr.getOperationType()) { case OR_STRUCTURE -> orStructureToExp(expr); case AND_STRUCTURE -> andStructureToExp(expr); - case WITH_STRUCTURE -> withStructureToExp(expr); + case LET_STRUCTURE -> letStructureToExp(expr); case WHEN_STRUCTURE -> whenStructureToExp(expr); case EXCLUSIVE_STRUCTURE -> exclStructureToExp(expr); case MIN_FUNC -> variadicToExp(expr, Exp::min); @@ -1371,21 +1371,21 @@ private static Exp binaryFunctionToExp(ExpressionContainer expr) { } /** - * Generates filter {@link Exp} for a WITH structure {@link ExpressionContainer}. + * Generates filter {@link Exp} for a LET structure {@link ExpressionContainer}. * - * @param expr The {@link ExpressionContainer} representing WITH structure + * @param expr The {@link ExpressionContainer} representing LET structure * @return The resulting {@link Exp} expression */ - private static Exp withStructureToExp(ExpressionContainer expr) { + private static Exp letStructureToExp(ExpressionContainer expr) { List expressions = new ArrayList<>(); - WithStructure withOperandsList = (WithStructure) expr.getLeft(); // extract unary Expr operand - List operands = withOperandsList.getOperands(); - for (WithOperand withOperand : operands) { - if (!withOperand.isLastPart()) { - expressions.add(Exp.def(withOperand.getString(), getExp(withOperand.getPart()))); + LetStructure letOperandsList = (LetStructure) expr.getLeft(); // extract unary Expr operand + List operands = letOperandsList.getOperands(); + for (LetOperand letOperand : operands) { + if (!letOperand.isLastPart()) { + expressions.add(Exp.def(letOperand.getString(), getExp(letOperand.getPart()))); } else { - // the last expression is the action (described after "do") - expressions.add(getExp(withOperand.getPart())); + // the last expression is the action (described after "then") + expressions.add(getExp(letOperand.getPart())); } } return Exp.let(expressions.toArray(new Exp[0])); diff --git a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java index f447d5e..cb2fbbb 100644 --- a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java @@ -79,10 +79,10 @@ void withMultipleVariablesDefinitionAndUsage() { Exp.add(Exp.var("x"), Exp.var("y")) ); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("with (x = 1, y = ${x} + 1) do (${x} + ${y})"), + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("let (x = 1, y = ${x} + 1) then (${x} + ${y})"), expected); // different spacing style - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("with(x = 1, y = ${x} + 1) do(${x} + ${y})"), + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("let(x = 1, y = ${x} + 1) then(${x} + ${y})"), expected); } } diff --git a/src/test/java/com/aerospike/dsl/expression/InCompositeTests.java b/src/test/java/com/aerospike/dsl/expression/InCompositeTests.java index f26200e..37f0c74 100644 --- a/src/test/java/com/aerospike/dsl/expression/InCompositeTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InCompositeTests.java @@ -57,14 +57,14 @@ void inWithParentheses() { } @Test - void inInsideWithStructure() { + void inInsideLetStructure() { Exp expected = Exp.let( Exp.def("allowed", Exp.val(List.of("Bob", "Mary"))), ListExp.getByValue(ListReturnType.EXISTS, Exp.stringBin("name"), Exp.var("allowed"))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(allowed = [\"Bob\", \"Mary\"])" + - " do ($.name.get(type: STRING) in ${allowed})"), expected); + ExpressionContext.of("let(allowed = [\"Bob\", \"Mary\"])" + + " then ($.name.get(type: STRING) in ${allowed})"), expected); } @Test @@ -88,7 +88,7 @@ void notWrappingIn() { } @Test - void nestedWithOuterListVar() { + void nestedLetOuterListVar() { Exp expected = Exp.let( Exp.def("x", Exp.val(List.of("a", "b"))), Exp.let( @@ -96,12 +96,12 @@ void nestedWithOuterListVar() { ListExp.getByValue(ListReturnType.EXISTS, Exp.stringBin("name"), Exp.var("x")))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = [\"a\", \"b\"]) do " + - "(with(y = 3) do ($.name.get(type: STRING) in ${x}))"), expected); + ExpressionContext.of("let(x = [\"a\", \"b\"]) then " + + "(let(y = 3) then ($.name.get(type: STRING) in ${x}))"), expected); } @Test - void nestedWithShadowedVar() { + void nestedLetShadowedVar() { Exp expected = Exp.let( Exp.def("x", Exp.val(1)), Exp.let( @@ -109,12 +109,12 @@ void nestedWithShadowedVar() { ListExp.getByValue(ListReturnType.EXISTS, Exp.stringBin("name"), Exp.var("x")))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = 1) do " + - "(with(x = [\"a\"]) do ($.name.get(type: STRING) in ${x}))"), expected); + ExpressionContext.of("let(x = 1) then " + + "(let(x = [\"a\"]) then ($.name.get(type: STRING) in ${x}))"), expected); } @Test - void nestedWithVarBoundToVar() { + void nestedLetVarBoundToVar() { Exp expected = Exp.let( Exp.def("x", Exp.val(List.of(1, 2))), Exp.let( @@ -122,8 +122,8 @@ void nestedWithVarBoundToVar() { ListExp.getByValue(ListReturnType.EXISTS, Exp.val(1), Exp.var("y")))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = [1, 2]) do " + - "(with(y = ${x}) do (1 in ${y}))"), expected); + ExpressionContext.of("let(x = [1, 2]) then " + + "(let(y = ${x}) then (1 in ${y}))"), expected); } // Known limitation: transitive variable indirection is not resolved statically. @@ -138,8 +138,8 @@ void transitiveVarIndirection() { ListExp.getByValue(ListReturnType.EXISTS, Exp.val("foo"), Exp.var("y")))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = 1) do " + - "(with(y = ${x}) do (\"foo\" in ${y}))"), expected); + ExpressionContext.of("let(x = 1) then " + + "(let(y = ${x}) then (\"foo\" in ${y}))"), expected); } // Known limitation: WHEN_STRUCTURE return type is not analyzed branch-by-branch, @@ -153,8 +153,8 @@ void whenScalarBranchesAllowedConservatively() { ListExp.getByValue(ListReturnType.EXISTS, Exp.val("foo"), Exp.var("x"))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = when(true => 1, default => 2))" + - " do (\"foo\" in ${x})"), expected); + ExpressionContext.of("let(x = when(true => 1, default => 2))" + + " then (\"foo\" in ${x})"), expected); } @Test @@ -237,7 +237,7 @@ void whenResultWithInCondition() { } @Test - void inInsideWhenWithVariable() { + void inInsideWhenWithLetVariable() { Exp expected = Exp.let( Exp.def("allowed", Exp.val(List.of("Bob", "Mary"))), Exp.cond( @@ -246,7 +246,7 @@ void inInsideWhenWithVariable() { Exp.val("found"), Exp.val("missing"))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(allowed = [\"Bob\", \"Mary\"]) do " + + ExpressionContext.of("let(allowed = [\"Bob\", \"Mary\"]) then " + "(when($.name.get(type: STRING) in ${allowed} => \"found\"," + " default => \"missing\"))"), expected); } diff --git a/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java b/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java index 17196c0..49ddf34 100644 --- a/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java @@ -318,7 +318,7 @@ void posVariableInBin() { ListExp.getByValue(ListReturnType.EXISTS, Exp.var("x"), Exp.listBin("list"))); parseFilterExpressionAndCompare( - ExpressionContext.of("with(x = 1) do (${x} in $.list)"), expected); + ExpressionContext.of("let(x = 1) then (${x} in $.list)"), expected); } // --- PATH_OPERAND with type designator (not ambiguous) --- diff --git a/src/test/java/com/aerospike/dsl/expression/InGrammarConflictTests.java b/src/test/java/com/aerospike/dsl/expression/InGrammarConflictTests.java index 745845e..2fd48c5 100644 --- a/src/test/java/com/aerospike/dsl/expression/InGrammarConflictTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InGrammarConflictTests.java @@ -177,5 +177,4 @@ void invertedRelativeIndexKeyIn() { Exp.val("in"), Exp.val(0), Exp.val(1), Exp.mapBin("mapBin")); parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin.{!0:1~in}"), expected); } - } diff --git a/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java b/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java index c211f31..9aa50c6 100644 --- a/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java @@ -167,7 +167,7 @@ void negMixedTypeListViaPlaceholder() { @Test void negVariableInMixedTypeList() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = 1) do (${x} in [1, \"y\"])"))) + ExpressionContext.of("let(x = 1) then (${x} in [1, \"y\"])"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN list elements must all be of the same type"); } @@ -209,7 +209,7 @@ void negBinInPlaceholderAmb() { @Test void negVarBoundToInt() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = 1) do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = 1) then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -218,7 +218,7 @@ void negVarBoundToInt() { @Test void negVarBoundToFloat() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = 1.5) do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = 1.5) then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -227,7 +227,7 @@ void negVarBoundToFloat() { @Test void negVarBoundToBool() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = true) do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = true) then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -236,7 +236,7 @@ void negVarBoundToBool() { @Test void negVarBoundToString() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = \"hello\") do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = \"hello\") then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -245,7 +245,7 @@ void negVarBoundToString() { @Test void negVarBoundToMap() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = {\"a\": 1}) do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = {\"a\": 1}) then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -254,7 +254,7 @@ void negVarBoundToMap() { @Test void negVarBoundToMetadata() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = $.ttl()) do (\"100\" in ${x})"))) + ExpressionContext.of("let(x = $.ttl()) then (\"100\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -265,7 +265,7 @@ void negVarBoundToMetadata() { @Test void negVarBoundToArithmeticExpr() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = 1 + 2) do (\"foo\" in ${x})"))) + ExpressionContext.of("let(x = 1 + 2) then (\"foo\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -274,7 +274,7 @@ void negVarBoundToArithmeticExpr() { @Test void negVarBoundToFunctionExpr() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = abs(1)) do (\"foo\" in ${x})"))) + ExpressionContext.of("let(x = abs(1)) then (\"foo\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -286,7 +286,7 @@ void negVarBoundToFunctionExpr() { void negVarBoundToExplicitIntBin() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = $.someBin.get(type: INT)) do (\"foo\" in ${x})"))) + "let(x = $.someBin.get(type: INT)) then (\"foo\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -296,39 +296,39 @@ void negVarBoundToExplicitIntBin() { void negVarBoundToExplicitStrPath() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = $.a.b.get(type: STRING)) do (\"foo\" in ${x})"))) + "let(x = $.a.b.get(type: STRING)) then (\"foo\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); } - // --- Nested WITH variable validation --- + // --- Nested LET variable validation --- @Test - void negNestedWithOuterNonListVar() { + void negNestedLetOuterNonListVar() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = 1) do (with(y = [1, 2]) do (\"foo\" in ${x}))"))) + "let(x = 1) then (let(y = [1, 2]) then (\"foo\" in ${x}))"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); } @Test - void negNestedShadowedVarWithScalar() { + void negNestedShadowedVarLetScalar() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = [1]) do (with(x = 1) do (\"foo\" in ${x}))"))) + "let(x = [1]) then (let(x = 1) then (\"foo\" in ${x}))"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); } @Test - void negNotWrappingInWithScalarVar() { + void negNotWrappingInLetScalarVar() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = 1) do (not(\"foo\" in ${x}))"))) + "let(x = 1) then (not(\"foo\" in ${x}))"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -338,17 +338,17 @@ void negNotWrappingInWithScalarVar() { void negVarBoundToCountPath() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = $.a.[].count()) do (\"foo\" in ${x})"))) + "let(x = $.a.[].count()) then (\"foo\" in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); } @Test - void negVarDefWithInScalarVar() { + void negVarDefLetInScalarVar() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "with(x = 5, y = ($.bin.get(type: INT) in ${x})) do (y == true)"))) + "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); @@ -357,7 +357,7 @@ void negVarDefWithInScalarVar() { @Test void negBinInVariableAmbiguous() { assertThatThrownBy(() -> parseFilterExp( - ExpressionContext.of("with(x = [\"a\"]) do ($.name in ${x})"))) + ExpressionContext.of("let(x = [\"a\"]) then ($.name in ${x})"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("cannot infer the type of the left operand for IN operation"); } diff --git a/src/test/java/com/aerospike/dsl/IndexContextTests.java b/src/test/java/com/aerospike/dsl/index/IndexContextTests.java similarity index 98% rename from src/test/java/com/aerospike/dsl/IndexContextTests.java rename to src/test/java/com/aerospike/dsl/index/IndexContextTests.java index f246015..948ed0d 100644 --- a/src/test/java/com/aerospike/dsl/IndexContextTests.java +++ b/src/test/java/com/aerospike/dsl/index/IndexContextTests.java @@ -1,5 +1,7 @@ -package com.aerospike.dsl; +package com.aerospike.dsl.index; +import com.aerospike.dsl.Index; +import com.aerospike.dsl.IndexContext; import com.aerospike.dsl.client.query.IndexType; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/aerospike/dsl/IndexTests.java b/src/test/java/com/aerospike/dsl/index/IndexTests.java similarity index 98% rename from src/test/java/com/aerospike/dsl/IndexTests.java rename to src/test/java/com/aerospike/dsl/index/IndexTests.java index 8c93d94..7f37359 100644 --- a/src/test/java/com/aerospike/dsl/IndexTests.java +++ b/src/test/java/com/aerospike/dsl/index/IndexTests.java @@ -1,5 +1,6 @@ -package com.aerospike.dsl; +package com.aerospike.dsl.index; +import com.aerospike.dsl.Index; import com.aerospike.dsl.client.query.IndexType; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/aerospike/dsl/parsedExpression/InFilterTests.java b/src/test/java/com/aerospike/dsl/parsedExpression/InExprTests.java similarity index 99% rename from src/test/java/com/aerospike/dsl/parsedExpression/InFilterTests.java rename to src/test/java/com/aerospike/dsl/parsedExpression/InExprTests.java index a2d566b..889f778 100644 --- a/src/test/java/com/aerospike/dsl/parsedExpression/InFilterTests.java +++ b/src/test/java/com/aerospike/dsl/parsedExpression/InExprTests.java @@ -15,7 +15,7 @@ import static com.aerospike.dsl.util.TestUtils.NAMESPACE; import static com.aerospike.dsl.util.TestUtils.parseDslExpressionAndCompare; -class InFilterTests { +class InExprTests { // --- Single IN + comparison with indexes — IN always excluded from Filter --- diff --git a/src/test/java/com/aerospike/dsl/parsedExpression/PlaceholdersTests.java b/src/test/java/com/aerospike/dsl/parsedExpression/PlaceholdersTests.java index f61baf7..ccb1dc7 100644 --- a/src/test/java/com/aerospike/dsl/parsedExpression/PlaceholdersTests.java +++ b/src/test/java/com/aerospike/dsl/parsedExpression/PlaceholdersTests.java @@ -321,5 +321,4 @@ void bothPlaceholdersEquality() { TestUtils.parseDslExpressionAndCompare(ExpressionContext.of("?0 == ?1", PlaceholderValues.of(42, 42)), null, exp); } - }