Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions docs/guides/07-conditional-logic-control-structures.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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:
Expand All @@ -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);
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/main/antlr4/com/aerospike/dsl/Condition.g4
Original file line number Diff line number Diff line change
Expand Up @@ -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
;
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/aerospike/dsl/ParsedExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/aerospike/dsl/parts/AbstractPart.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public enum ExprPartsOperation {
LT,
LTEQ,
IN,
WITH_STRUCTURE, // unary
LET_STRUCTURE, // unary
WHEN_STRUCTURE, // unary
EXCLUSIVE_STRUCTURE, // unary
AND_STRUCTURE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<LetOperand> operands;

public LetStructure(List<LetOperand> operands) {
super(PartType.LET_STRUCTURE);
this.operands = operands;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,3 @@ static AbstractPart createOperand(Object value) {
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,29 +43,29 @@

public class ExpressionConditionVisitor extends ConditionBaseVisitor<AbstractPart> {

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<WithOperand> expressions = new ArrayList<>();
List<LetOperand> 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--;
}
}

Expand Down Expand Up @@ -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.
* <p>
* 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<WithOperand> expressions) {
// WITH block always has at least one variable definition (grammar-enforced)
private static void validateInVariableBindings(List<LetOperand> expressions) {
// LET block always has at least one variable definition (grammar-enforced)
Map<String, AbstractPart> 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<WithOperand> operands) {
private static AbstractPart getLetBody(List<LetOperand> 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,
Expand All @@ -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<String, AbstractPart> 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) {
Expand Down
Loading
Loading