Skip to content

Parse quantified value sets, subqueries, keyed trace and null - #82

Open
jtnelson wants to merge 11 commits into
developfrom
feature/value-sets-and-null-literal
Open

jtnelson wants to merge 11 commits into
developfrom
feature/value-sets-and-null-literal

Conversation

@jtnelson

@jtnelson jtnelson commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Concourse is adding quantified comparisons (cinchapi/concourse#779), subqueries that supply a value set (cinchapi/concourse#780), a single-key trace (cinchapi/concourse#778) and a null criteria literal (cinchapi/concourse#791). Each one needs the CCL grammar and symbol model to express it before the driver and server can evaluate it. This change gives CCL that surface: the parser recognizes every form, the parsed ExpressionSymbol exposes the quantifier and the value set, the token stream that Concourse uses to round-trip criteria carries them, and the local evaluator applies the quantifier.

in / not_in (#53) land here as sugar for the equality quantifiers rather than as new operators, so Concourse needs no new Operator member for them. Keyed trace lands here because it is the CCL form of the trace(key, record) subquery source.

A value set is delimited with square brackets, any[...], because brackets already hold every collection in CCL ([1, 2, 3], [name, age]) while parentheses group conditions, call functions and, here, form a between pair. Brackets also keep a quantifier from colliding with a value function of the same name.

Refs cinchapi/concourse#779, cinchapi/concourse#780, cinchapi/concourse#778, cinchapi/concourse#791. Closes #53.

Behavior

Quantified value sets

  • key op any[v1, v2, ...] means (key op v1) or (key op v2) or ...; either and some are aliases. key op every[v1, v2, ...] means (key op v1) and (key op v2) and ...; all is an alias. The words are case-insensitive and are not reserved, so status = all still compares against the string all.
  • The quantifier composes with every operator. between takes a set of parenthesized pairs: age bw any[(1, 5), (10, 20)].
  • key in [...] is key = any[...] and key not_in [...] is key != every[...]. not_in, nin and notin are reserved words.
  • A set must hold at least one member; any[] is a syntax error, and so is a set in parentheses such as any("a", "b"). A timestamp after the closing bracket applies to the comparison.
  • ExpressionSymbol#quantifier() returns QuantifierSymbol.ANY or EVERY, or null for a plain comparison. values() holds every member in order; for between, each consecutive pair is one member. ExpressionSymbol#create takes a quantifier, and the raw Content exposes quantifier().
  • Compiler#tokenize and Parsing#ungroupExpressions emit one ValueSetSymbol in the value position for a quantified expression. It renders as any[v1, v2] (or as pairs), so a token stream joined with spaces is valid CCL, and Parsing#groupExpressions and Compiler#parse(List<Symbol>, Multimap) rebuild the quantified expression from it, including $name placeholders inside the set.
  • Compiler#evaluate tests each member with the usual single-value semantics (any stored value may satisfy it) and combines the results with the quantifier.

Subqueries

  • Inside a quantifier, a select, get, find, trace or navigate command supplies the set, with its own condition, order, page and timestamp clauses: name = any[select name where group = "vip"], reports_to lnks2 any[trace manager from 42].
  • A subquery is the whole set. It cannot be mixed with literal members, appear without a quantifier, or supply the pairs of a between set.
  • The expression holds one SubquerySymbol whose value() is the subquery's CommandTree and whose toString() is the subquery's CCL. A $name placeholder in the subquery renders as its bound value, so that CCL parses to an equal subquery without the bindings. The subquery's condition is not a child of the enclosing condition, and Compiler#analyze of the enclosing statement does not report the subquery's keys, operators or timestamps; analyzing the subquery's CommandTree does. Compiler#evaluate throws UnsupportedOperationException for a condition that holds a subquery; the engine resolves it.

Keyed trace

  • trace <key> from <record> and trace <key> from [<records>], with in or within in place of from and an optional timestamp, parse to a TraceSymbol whose key() is the key, and Compiler#analyze reports the key among the keys the command touches. The record-only forms are unchanged and report a null key. A key with a bracket timestamp is rejected, as it is for the other single-key commands.

Null literal

  • In a condition, the unquoted word null in any letter case parses to NullValueSymbol.INSTANCE, whose value() is Java null. It is accepted with = and !=, alone or as a member of a set; any other operator rejects it with a SyntaxException that names null.
  • The quoted string "null" stays a string, and ValueSymbol renders it with quotes so a round trip through CCL keeps it a string. An unquoted null followed by another word is a multi-word string.
  • Compiler#evaluate passes the literal to the evaluator as a null operand and a missing key as a null stored value; the evaluator defines what absence means.

API

  • ExpressionSymbol gains the abstract method quantifier(). An implementation outside this library must add it; returning null keeps it a plain comparison.
  • CCL_REFERENCE.md documents each form and the formal grammar; CHANGELOG.md has a 4.2.0 (TBD) block.

…eral

A comparison may test a key against a set of values through a quantifier:
`key op any(v1, v2)` (alias `either`) holds when the comparison holds for
at least one member and `key op every(v1, v2)` (alias `all`) when it holds
for every member. The quantifier composes with every operator; `between`
takes parenthesized pairs. `in` is sugar for `= any(...)` and `not_in`
(aliases `nin`, `notin`) for `!= every(...)`. An empty set is a syntax
error and the quantifier words are not reserved.

The set may be supplied by a `select`, `get`, `find`, `trace` or `navigate`
subquery, which parses to a SubquerySymbol holding the command's
CommandTree. The subquery's condition is detached from the enclosing
condition. `trace <key> from <record>` traces links through one key.

The unquoted word `null` is a literal for an absent value, accepted only
with `=` and `!=`; the quoted string "null" stays a string.

ExpressionSymbol exposes quantifier(); the token stream carries a
quantified set as one ValueSetSymbol so criteria round-trip through CCL.

Refs cinchapi/concourse#779, cinchapi/concourse#780, cinchapi/concourse#778,
cinchapi/concourse#791, #53
A quantified set is written any[v1, v2], every[(1, 5), (10, 20)] and
in [v1, v2], so brackets hold a collection everywhere in CCL and
parentheses only group, call a function or form a pair. The change also
removes the collision between a quantifier and a value function named
with the same word. Parentheses around a set are a syntax error.

Refs cinchapi/concourse#779, #53
SQL spells the existential quantifier SOME as well as ANY, and some reads
correctly with the ordering operators, where any can be misread as every:
age > some[20, 30, 40] holds when age exceeds at least one of them. The
word is not reserved.

Refs cinchapi/concourse#779
A subquery parsed with bound data kept each $name placeholder in its CCL,
while its parsed command held the bound value. Its toString() then failed
to parse without the bindings, and two subqueries with different bindings
compared equal, because SubquerySymbol equality follows that CCL.

The CCL of a subquery now holds the CCL of each bound value in place of
its placeholder, so it parses to an equal subquery without the bindings.

Refs cinchapi/concourse#780
CommandAnalysis answers what a whole command touches, and the commands
that take an optional key, such as audit and diff, report that key.
Compiler#analyze now reports the key of a keyed trace, such as manager in
trace manager from 42.

Refs cinchapi/concourse#778
The changelog states that a key named not_in, nin or notin has no CCL form
on its own, and how to use one, and that an unquoted null in an existing
statement now means an absent value. The Javadoc of ASTExpression#toString
and of a subquery test names the bracket form of a value set, and the
grammar's helper methods carry their parameter, result and failure tags.

Refs cinchapi/concourse#779, cinchapi/concourse#791
The engine resolves a subquery that supplies a quantified value set as its
own command, so Compiler#analyze of the enclosing statement does not report
the subquery's keys, operators or timestamps. StatementAnalysis and
CommandAnalysis now state that boundary and point to the analysis of the
subquery's CommandTree.

Refs cinchapi/concourse#780
The build regenerates Grammar.java and GrammarTokenManager.java on every
compile, so an edit to either file lasts only until the next build. The
suppressions they need now come from the generator: the grammar's class
annotation also suppresses unchecked warnings, and the generator task
removes the imports JavaCC copies into the token manager and marks that
class @SuppressWarnings("unused").
The subquery test compares the order and page of a subquery with those of
the same command parsed on its own, instead of checking that they exist.
The keyed trace test parses a single record, a bracketed collection and a
bare collection after each preposition. A new test checks that not_in, nin
and notin are reserved words: a syntax error as an unquoted value or a key,
a string when quoted, and still a stop in a navigation key.
…-and-null-literal

Resolved conflicts in:
- CHANGELOG.md: kept this branch's 4.2.0 (TBD) sections and develop's Distribution section in the same block, followed by develop's 4.1.1 (TBD) block.
- src/main/java/com/cinchapi/ccl/generated/Grammar.java: regenerated from the merged grammar.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add IN and NOT_IN operators for multi-value equality matching

1 participant