Skip to content

Add strict(...) condition wrapper for same-destination navigation evaluation #52

@jtnelson

Description

@jtnelson

Background

This is the CCL-side work required for cinchapi/concourse#533 (strict navigation in multi-valued link queries).

Concourse needs a strict(...) wrapper keyword in CCL that groups conditions requiring same-destination evaluation for navigation keys. For example:

find where strict(A.foo = "A" AND A.bar = "B")

This tells the engine that all conditions inside strict() must be satisfied by the same linked record, rather than evaluating each condition independently (which can produce false positives when multiple linked records each satisfy different conditions).

Changes Required

1. Grammar (grammar/grammar.jjt)

Add token (in the TOKEN section, near other keywords):

< STRICT : "strict" >

Add production (new grammar rule):

void StrictExpression() #Strict : {}
{
  (<STRICT>) (<OPEN_PARENTHESES>) DisjunctionExpression() (<CLOSE_PARENTHESES>)
}

Modify UnaryExpression() to include the new production as an alternative (with LOOKAHEAD):

void UnaryExpression() : {}
{
   LOOKAHEAD(2)
   StrictExpression()
   |
   (<OPEN_PARENTHESES>) DisjunctionExpression() (<CLOSE_PARENTHESES>)
   |
   RelationalExpression()
}

Same change for UnaryExpressionNoTimestamp().

Regenerate the parser using the existing javacc-parser-generator.sh script.

2. New AST node: StrictConditionTree

File: src/main/java/com/cinchapi/ccl/syntax/StrictConditionTree.java

A ConditionTree that wraps inner conditions requiring same-destination evaluation:

  • Extends BaseAbstractSyntaxTree, implements ConditionTree
  • Holds a single ConditionTree condition (the wrapped inner conditions)
  • condition() accessor returns the inner tree
  • accept() calls visitor.visit(this, data)

3. Update Visitor interface

File: src/main/java/com/cinchapi/ccl/syntax/Visitor.java

  • Add T visit(StrictConditionTree tree, Object... data) method
  • Update default visit(ConditionTree) to handle StrictConditionTree instances (dispatch to the new method)

4. Update ConditionTreeVisitor

File: src/main/java/com/cinchapi/ccl/ConditionTreeVisitor.java

Allow StrictConditionTree through (it IS a condition tree type). Add the visit(StrictConditionTree) method -- can be abstract or have a default implementation.

5. Update CompilerJavaCC

File: src/main/java/com/cinchapi/ccl/CompilerJavaCC.java

Add visitor handler for ASTStrict (the JJTree-generated node):

public Object visit(ASTStrict node, Object data) {
    ConditionTree inner = (ConditionTree) node.jjtGetChild(0)
            .jjtAccept(this, data);
    return new StrictConditionTree(inner);
}

6. Tests

Add parser tests verifying:

  • strict(A.foo = "A" AND A.bar = "B") parses into a StrictConditionTree wrapping an AndTree
  • strict(A.foo = "A") parses into a StrictConditionTree wrapping an ExpressionTree
  • strict(A.foo = "A" AND A.bar = "B") OR name = "test" -- strict inside a larger expression
  • Existing expressions without strict continue to parse identically

Edge Cases the Grammar Must Accept

The grammar itself is straightforward — strict() wraps any valid expression. However, the following inputs are syntactically valid and the parser must accept them without error, even though some are semantically degenerate (the engine handles the semantics, not the parser):

Non‑navigation keys inside strict()

strict(name = "Jeff" AND age > 30)

The parser treats this the same as any other strict(...) — it wraps the inner conditions in a StrictConditionTree. The engine is responsible for detecting that there are no navigation keys and evaluating as normal AND.

No shared navigation prefix

strict(A.foo = "X" AND B.bar = "Y")

Again, syntactically valid. The parser produces a StrictConditionTree wrapping an AndTree. The engine computes an empty common prefix and degrades to normal AND.

Pure OR inside strict()

strict(A.foo = "X" OR A.bar = "Y")

Valid and parsed as StrictConditionTree wrapping an OrTree. Semantically equivalent to the non‑strict version (strict only changes behavior when AND creates cross‑link ambiguity), but not the parser's concern.

Nested strict() inside larger expressions

name = "Jeff" OR strict(A.foo = "X" AND A.bar = "Y") AND age > 20

The strict(...) block participates in the normal operator precedence. The parser should handle this via the existing UnaryExpression integration without special precedence rules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions