Documentation
¶
Overview ¶
Package expr is the parse-tree → values.Value resolver. It bridges the two main Phase 3 seam packages:
- pkg/relational/core/query/semantic — identifier resolution, catalog lookup, scope chain.
- pkg/recordlayer/query/plan/cascades — Value / Predicate hierarchy.
Neither semantic nor cascades depends on the other. expr sits above both and owns the logic that turns a parsed SQL expression into a typed Value tree with every identifier resolved against a Scope.
API ¶
Two layers:
- Resolver.Resolve* primitives — programmatic construction of Value / Predicate trees from already-walked parts (caller supplies each argument). Useful for tests and for synthetic expressions the analyzer constructs from other inputs.
- Resolver.WalkExpression / WalkPredicate — parse-tree driver. Dispatches ANTLR IExpressionContext variants to the right Resolve* method, recursing into child contexts.
Call WalkExpression for Value-returning expressions (SELECT list items, arithmetic operands). Call WalkPredicate for QueryPredicate- returning expressions (WHERE / HAVING clauses).
Handled shapes ¶
- Columns: bare (`col`) and qualified (`t.col`).
- Constants: integer, string, NULL. Float pending (see ResolveConstant).
- Arithmetic: +, -, *, /.
- Comparisons: =, <>, !=, <, <=, >, >=, IS [NOT] DISTINCT FROM.
- Logical: AND / OR / NOT (with left-deep chain flattening).
- Unary predicates: IS NULL / IS NOT NULL.
- BETWEEN (desugars to AND(>=, <=)).
- IN with explicit literal list.
- LIKE (no ESCAPE).
- Aggregate function calls: COUNT / COUNT(*) / SUM / MIN / MAX / AVG.
- Parenthesised expressions (single-element RecordConstructor unwrap).
Not handled (returns UnsupportedExpressionShapeError) ¶
- Scalar function calls (UPPER, LOWER, LENGTH, …).
- LIKE with ESCAPE.
- IN with subquery / parameter / single-column.
- CAST to FLOAT / DOUBLE / BYTES / UUID / VECTOR (seed ValueType only covers INT / STRING / BOOL — the full Type hierarchy port lands in Phase 4.0).
- Multi-element or named-field record constructors.
CAST and CONVERT to INT / BIGINT / STRING / BOOLEAN are wired via DataTypeFunctionCall; XOR desugars to (a OR b) AND NOT (a AND b); IS [NOT] TRUE / FALSE desugars via the 2VL `(x IS NOT NULL) AND (x = literal)` shape.
Callers catching UnsupportedExpressionShapeError can fall back to the existing logical-builder path, which handles the full grammar surface at a less-structured level.
Index ¶
- func AnyWhereExistsInScalarPosition(tree antlr.Tree) bool
- func ContainsExistsAtom(ctx antlr.Tree) bool
- func ContainsSubqueryAtom(ctx antlr.Tree) bool
- func WhereExistsInScalarPosition(ctx antlrgen.IExpressionContext) bool
- type ExpressionResolver
- type InColumnRefError
- type InListNullError
- type InvalidBinaryLiteralError
- type NestedExistsProjectionError
- type NumericOverflowLiteralError
- type PredicateValueHolder
- type Resolver
- func (r *Resolver) ResolveAnd(preds ...predicates.QueryPredicate) predicates.QueryPredicate
- func (r *Resolver) ResolveArithmetic(op values.ArithmeticOp, left, right values.Value) (values.Value, error)
- func (r *Resolver) ResolveCast(v values.Value, target values.Type) (values.Value, error)
- func (r *Resolver) ResolveColumnShadowingQualified(qualifier, id semantic.Identifier) (values.Value, bool, error)
- func (r *Resolver) ResolveComparison(op predicates.ComparisonType, left, right values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveConstant(lit any) (values.Value, error)
- func (r *Resolver) ResolveFunctionCall(funcCatalog *semantic.FunctionCatalog, name semantic.Identifier, isStar bool, ...) (values.Value, error)
- func (r *Resolver) ResolveIdentifier(qualifier, id semantic.Identifier) (values.Value, error)
- func (r *Resolver) ResolveIn(left values.Value, rhs []values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveIsNotNull(v values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveIsNull(v values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveLike(lhs values.Value, pattern values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveLikeWithEscape(lhs values.Value, pattern values.Value, escape rune) (predicates.QueryPredicate, error)
- func (r *Resolver) ResolveNot(pred predicates.QueryPredicate) predicates.QueryPredicate
- func (r *Resolver) ResolveOr(preds ...predicates.QueryPredicate) predicates.QueryPredicate
- func (r *Resolver) ResolveStartsWith(lhs values.Value, prefix values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) SetSubqueryPlanner(p SubqueryPlanner)
- func (r *Resolver) WalkExpression(ctx antlrgen.IExpressionContext) (values.Value, error)
- func (r *Resolver) WalkExpressionForProjection(ctx antlrgen.IExpressionContext) (values.Value, error)
- func (r *Resolver) WalkPredicate(ctx antlrgen.IExpressionContext) (predicates.QueryPredicate, error)
- type SubqueryPlanner
- type UnsupportedExpressionShapeError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AnyWhereExistsInScalarPosition ¶
AnyWhereExistsInScalarPosition reports whether the parse subtree rooted at tree contains ANY WHERE clause whose expression has an EXISTS atom buried in a scalar position (per WhereExistsInScalarPosition). Used to guard statement forms whose WHERE is nested and not directly accessible — notably `INSERT … SELECT … WHERE CASE WHEN EXISTS(...) …`, whose SELECT-body WHERE is rebuilt through a path that bypasses the per-statement WHERE guard. A structural typed-node walk (WhereExprContext), no GetText. (RFC-141 R4.)
func ContainsExistsAtom ¶
ContainsExistsAtom reports whether the parse tree rooted at ctx contains an EXISTS subquery atom at the CURRENT query level — a structural (typed-node) walk, no GetText / text matching. Used by the logical builder to reject query shapes (e.g. GROUP BY on an EXISTS expression) where the projected/grouped EXISTS cannot be folded and would otherwise be silently dropped (RFC-141 §8 safety guard).
The walk STOPS at nested-subquery boundaries (introducesNestedQueryScope): an EXISTS belonging to a nested scalar / IN subquery's OWN clause is that subquery's concern (classified in its own translation context), not the outer expression's — descending into it would mis-attribute the inner EXISTS to the outer level and falsely reject a query the outer level handles fine (RFC-141 R4 RFC-141 R4). The EXISTS atom itself is matched before descent, so its own subquery is correctly never descended either.
func ContainsSubqueryAtom ¶
ContainsSubqueryAtom reports whether the parse tree rooted at ctx contains a SCALAR subquery atom `(SELECT ...)` (SubqueryExpressionAtomContext) or an `x IN (SELECT ...)` subquery body (an InListContext carrying a QueryExpressionBody) at the CURRENT query level. A structural typed-node walk, no GetText.
Used by the logical builder to reject these subquery shapes cleanly in positions that do not install a SubqueryPlanner — notably a JOIN ON clause, where Go (like Java) does not support IN-subqueries or correlated scalar subqueries. Without this, the ON resolver's WalkPredicate declines the shape with UnsupportedExpressionShapeError and the caller silently DROPS the whole ON predicate → the join degrades to a CROSS PRODUCT (silent wrong rows). EXISTS atoms are matched separately by ContainsExistsAtom (EXISTS-in-ON IS supported).
An `x IN (a, b, c)` value list (no QueryExpressionBody) is NOT a subquery and is not matched; the walk descends into its elements in case one is itself a scalar subquery. A matched subquery atom is not descended into — its inner SELECT is that subquery's own concern.
func WhereExistsInScalarPosition ¶
func WhereExistsInScalarPosition(ctx antlrgen.IExpressionContext) bool
WhereExistsInScalarPosition reports whether the WHERE expression rooted at ctx contains an EXISTS atom that is NOT in a directly-handled top-level boolean position — i.e. it is buried inside a SCALAR expression (a CASE, a comparison operand, an arithmetic, a function-call argument). Such an EXISTS lowers to a scalar Value with no existential quantifier driving it, so it evaluates to a constant false → a silent wrong result. The WHERE companion to isDirectlyFoldableProjectedExists (RFC-141 R4).
An EXISTS is directly-handled in WHERE iff the path from the WHERE root to the atom traverses ONLY boolean-combinator nodes: AND/OR (LogicalExpression), NOT (NotExpression), and a transparent paren-wrap (a PredicatedExpression with no grammar Predicate over a single-element RecordConstructor, or its ExpressionAtom). The descent below mirrors that whitelist: it recurses through those nodes, treating a reached EXISTS atom (directly, or via existsAtomOf for the NOT/paren shapes) as handled. The moment a node that is NOT a boolean combinator still CONTAINS an EXISTS atom beneath it, that EXISTS is buried in scalar position → return true.
Types ¶
type ExpressionResolver ¶
type ExpressionResolver interface {
WalkExpression(ctx antlrgen.IExpressionContext) (values.Value, error)
}
ExpressionResolver is the interface a parse-tree → values.Value walker exposes. *Resolver satisfies it; tests can swap in a fake that returns canned Values without standing up a real Analyzer + Scope + FunctionCatalog.
Per RFC-025 §"Closing the leaks": the embedded layer's projection fold pass needs a way to walk an ANTLR IExpressionContext to a values.Value at plan time. Today it instantiates a real Resolver inline (see embedded/projection_fold.go) which forces every test to set up RecordMetaData + a demo proto schema + an Analyzer + a Scope just to verify routing logic. With this interface, callers can inject a fake walker — the test boundary stays at "did the fold pass route the right Values into the folder?", not "did the entire catalog stack agree?".
Contract: WalkExpression returns (values.Value, nil) on a supported shape, (nil, error) on an unsupported shape (typically `*UnsupportedExpressionShapeError`). The caller filters on error type if it cares to distinguish "decline" from "error".
type InColumnRefError ¶
type InColumnRefError struct{}
InColumnRefError signals `x IN y` where y is a column reference, not an explicit value list. Java rejects this as unsupported syntax.
func (*InColumnRefError) Error ¶
func (*InColumnRefError) Error() string
type InListNullError ¶
type InListNullError struct{}
InListNullError signals that a NULL literal was found in an IN list. Java rejects these with "NULL values are not allowed in the IN list".
func (*InListNullError) Error ¶
func (*InListNullError) Error() string
type InvalidBinaryLiteralError ¶
InvalidBinaryLiteralError signals a malformed hex or base64 byte literal. Should be mapped to SQLSTATE 22F03 INVALID_BINARY_REPRESENTATION by the caller.
func (*InvalidBinaryLiteralError) Error ¶
func (e *InvalidBinaryLiteralError) Error() string
type NestedExistsProjectionError ¶
type NestedExistsProjectionError struct{}
NestedExistsProjectionError signals a SELECT-list item that CONTAINS an EXISTS subquery atom but is NOT a directly-foldable projected EXISTS — i.e. the EXISTS is nested inside another expression (`CASE WHEN EXISTS(...) THEN ...`, `EXISTS(...) AND x`, a comparison, an arithmetic, ...) rather than being the whole SELECT item or its single (paren/NOT) wrapper.
Unlike UnsupportedExpressionShapeError (which the projection callers SWALLOW to fall back to the logical-builder text path), this error MUST reject the query: the text-fallback path would route the nested EXISTS through the predicate walk, registering the existential subquery but evaluating the ExistsValue ABOVE the FlatMap with the binding dead — constant false / NULL, a silent wrong result. The callers (logical_predicate.go, plan_visitor.go) convert it to ErrCodeUnsupportedQuery (RFC-141 R4 convergence backstop, P1b).
func (*NestedExistsProjectionError) Error ¶
func (e *NestedExistsProjectionError) Error() string
type NumericOverflowLiteralError ¶
type NumericOverflowLiteralError struct {
Text string
}
NumericOverflowLiteralError signals that a numeric literal overflows its target type (e.g. 1e400 overflows float64). Should be mapped to SQLSTATE 22003 NUMERIC_VALUE_OUT_OF_RANGE.
func (*NumericOverflowLiteralError) Error ¶
func (e *NumericOverflowLiteralError) Error() string
type PredicateValueHolder ¶
type PredicateValueHolder interface {
values.Value
GetPredicate() predicates.QueryPredicate
SetPredicate(predicates.QueryPredicate)
}
PredicateValueHolder is implemented by values that wrap a QueryPredicate (used by CASE conditions). Exported so the aggregate-rewriter in logical_predicate.go can access the predicate for AggregateValue→FieldValue rewriting.
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver converts parsed SQL expressions into cascades Values. It needs a Scope (to resolve identifiers) and an Analyzer (to run column-reference lookup). Stateless beyond those inputs — one Resolver per analyzer is fine.
The FunctionCatalog is built lazily on first aggregate-function lookup (to keep the construction path cheap when the resolver never sees a function call) and reused thereafter. Callers that want a custom catalog (scalar extensions, overridden defaults) can pass one via NewWithFunctionCatalog. Concurrency contract: Resolver is intended for a single statement walk and is NOT goroutine-safe — `nextOrdinal` (the positional `?` counter) and the lazy `funcCat` initialization both rely on single-goroutine access during a walk. Callers that fan parsing out across goroutines should construct one Resolver per goroutine. `funcCatOnce` makes the lazy build defensive against existing concurrent test patterns that share a Resolver across `t.Parallel()` subtests; `nextOrdinal` would still be racy in that pattern but no test exercises that path.
func New ¶
New constructs a Resolver bound to a scope. Nil analyzer or nil scope panics — the resolver has nothing to do without either. Function-call resolution uses the seed defaults (COUNT/SUM/MIN/MAX/AVG).
func NewWithFunctionCatalog ¶
func NewWithFunctionCatalog(analyzer *semantic.Analyzer, scope *semantic.Scope, fc *semantic.FunctionCatalog) *Resolver
NewWithFunctionCatalog is the New variant that lets callers plug in a pre-built FunctionCatalog — used when scalar functions or user-registered aggregates need to be resolvable. Passing nil uses the seed defaults on first demand.
func (*Resolver) ResolveAnd ¶
func (r *Resolver) ResolveAnd(preds ...predicates.QueryPredicate) predicates.QueryPredicate
ResolveAnd combines N predicates via Kleene AND. A single predicate returns verbatim (no wrapping); empty list returns ConstantPredicate(TRUE) — the AND identity.
func (*Resolver) ResolveArithmetic ¶
func (r *Resolver) ResolveArithmetic(op values.ArithmeticOp, left, right values.Value) (values.Value, error)
ResolveArithmetic wraps left/right Values in a cascades ArithmeticValue with the given operator. Used when the parser produces an arithmetic expression node — the analyzer resolves each operand recursively, then pairs them here.
Operand types aren't cross-checked in the seed (both assumed int); real type inference replaces this when the Type hierarchy port lands.
func (*Resolver) ResolveCast ¶
ResolveCast wraps v in a CastValue with the target type. Rejects nil child (programmer error) and Unknown target (use the direct Value if the target is genuinely unknown).
func (*Resolver) ResolveColumnShadowingQualified ¶
func (r *Resolver) ResolveColumnShadowingQualified(qualifier, id semantic.Identifier) (values.Value, bool, error)
ResolveColumnShadowingQualified resolves a column reference and, when it binds to a SHADOWING scope source (a lateral array unnest's AS/AT binding, RFC-142), returns a Value QUALIFIED to that source's correlation (FieldValue over QuantifiedObjectValue) — even for a BARE column where ResolveIdentifier would normally emit an UNqualified FieldValue.
This is load-bearing for `FROM t, t.arr AS v, u` where a LATER FROM item `u` also has a column `v`: the unnest's element flows the merged row under BOTH the bare key `v` AND the qualified key `v.v`, but a subsequent join's mergeRows overwrites the bare `v` last-leg-wins with u.v. A bare `SELECT v` resolved to the unnest must therefore read the QUALIFIED `v.v` key (which mergeRows preserves verbatim — dotted keys are never re-prefixed), not the clobbered bare key. ok=false (and a nil Value) when the column does not bind to a shadowing source — the caller keeps its existing bare-column handling. A resolution error is returned verbatim (callers already validate separately, so they may ignore it). RFC-142.
func (*Resolver) ResolveComparison ¶
func (r *Resolver) ResolveComparison(op predicates.ComparisonType, left, right values.Value) (predicates.QueryPredicate, error)
ResolveComparison wraps left/right Values in a cascades ComparisonPredicate. Mirrors the analyzer's job of lifting `a > b` from a parse-tree comparison node to a predicate node.
Both LHS and RHS are carried as Values — non-constant RHS (`a = b`, `a < b + 1`, `a = CAST(col AS INT)`) composes uniformly with constant RHS. Plan-time folding (`5 = 5` → TRUE) happens in ComparisonConstantSimplifyRule when both sides are constant; row-context evaluation (FieldValue RHS) runs through ComparisonPredicate.Eval.
Does NOT pre-fold even when both operands are constant. `5 = 5` produces a real ComparisonPredicate; the fixpoint simplifier folds it to TRUE via ComparisonConstantSimplifyRule. Eager folding here would hide foldable shapes from rule matchers that expect to see them.
func (*Resolver) ResolveConstant ¶
ResolveConstant wraps a Go-native literal in a cascades ConstantValue with the appropriate type tag. Useful for inlining literal arguments when building a Value tree from a parsed expression.
Returns an error when the literal's runtime type doesn't map to any seed ValueType — nil, int, int32, int64, float32, float64, string, bool are supported. Float literals carry TypeFloat; arithmetic over floats still goes through ArithmeticValue's int-only Eval (mixed-type arith returns nil per the seed contract — a real arithmetic-over-float requires the Type hierarchy port to set up coercion).
func (*Resolver) ResolveFunctionCall ¶
func (r *Resolver) ResolveFunctionCall( funcCatalog *semantic.FunctionCatalog, name semantic.Identifier, isStar bool, args []values.Value, ) (values.Value, error)
ResolveFunctionCall dispatches a function call against the given catalogue. For known aggregates (COUNT/SUM/MIN/MAX/AVG) it returns the corresponding values.AggregateValue. Scalar function support comes once the scalar-function catalogue is wired in.
isStar=true signals the argument was `*` (COUNT(*)) — args must be empty in that case.
func (*Resolver) ResolveIdentifier ¶
ResolveIdentifier produces a cascades Value for a bare or qualified identifier reference. qualifier may be the zero Identifier for bare lookups.
Currently produces a values.FieldValue for scope-resolved columns. Once QuantifiedObjectValue lookup lands in the logical- builder, this will produce a FieldValue wrapping a QuantifiedObjectValue to carry the source correlation.
Row-key contract: FieldValue.Field is stored in the Identifier's case-folded form (upper-case unless the source was quoted). Callers feeding FieldValue.Evaluate a row map MUST key the map with the same case-folded names — otherwise lookup silently returns nil. The SQL executor's row-projection layer is responsible for that normalisation. Documented explicitly here because a subtle-lookup-fail is an easy trap when integrating.
Returns the underlying semantic errors verbatim so callers can match via errors.As.
func (*Resolver) ResolveIn ¶
func (r *Resolver) ResolveIn(left values.Value, rhs []values.Value) (predicates.QueryPredicate, error)
ResolveIn builds a ComparisonPredicate{ComparisonIn} from a left Value and a list of constant RHS values. Every RHS must be a plan-time constant (per values.EvaluateConstant); non-constant elements return an error so callers lift the expression-based IN-list to the value-space form explicitly.
The RHS Operand is a []any of evaluated literals.
func (*Resolver) ResolveIsNotNull ¶
func (r *Resolver) ResolveIsNotNull(v values.Value) (predicates.QueryPredicate, error)
ResolveIsNotNull builds `v IS NOT NULL`. Unary.
func (*Resolver) ResolveIsNull ¶
func (r *Resolver) ResolveIsNull(v values.Value) (predicates.QueryPredicate, error)
ResolveIsNull builds `v IS NULL`. Unary — Comparison.Operand is nil (Eval ignores it for unary types).
func (*Resolver) ResolveLike ¶
func (r *Resolver) ResolveLike(lhs values.Value, pattern values.Value) (predicates.QueryPredicate, error)
ResolveLike builds `lhs LIKE pattern`. Pattern must be a plan-time constant string (parameter-bound patterns land with the parameter-Comparison design).
func (*Resolver) ResolveLikeWithEscape ¶
func (r *Resolver) ResolveLikeWithEscape(lhs values.Value, pattern values.Value, escape rune) (predicates.QueryPredicate, error)
ResolveLikeWithEscape is the LIKE … ESCAPE form. escape == 0 is equivalent to ResolveLike. Pattern must be a plan-time constant string. The escape rune is carried verbatim on the resulting Comparison.
func (*Resolver) ResolveNot ¶
func (r *Resolver) ResolveNot(pred predicates.QueryPredicate) predicates.QueryPredicate
ResolveNot wraps a predicate in a Kleene NOT. Nil child returns ConstantPredicate(UNKNOWN) — the only sensible interpretation.
func (*Resolver) ResolveOr ¶
func (r *Resolver) ResolveOr(preds ...predicates.QueryPredicate) predicates.QueryPredicate
ResolveOr combines N predicates via Kleene OR. Empty list returns ConstantPredicate(FALSE) — the OR identity. Single predicate returns verbatim.
func (*Resolver) ResolveStartsWith ¶
func (r *Resolver) ResolveStartsWith(lhs values.Value, prefix values.Value) (predicates.QueryPredicate, error)
ResolveStartsWith builds `lhs STARTS_WITH prefix`. Prefix must be a constant string.
func (*Resolver) SetSubqueryPlanner ¶
func (r *Resolver) SetSubqueryPlanner(p SubqueryPlanner)
SetSubqueryPlanner installs a callback that builds logical plans for EXISTS subqueries. Must be called before WalkPredicate if EXISTS support is desired. Passing nil disables EXISTS handling (the walker declines with UnsupportedExpressionShapeError).
func (*Resolver) WalkExpression ¶
WalkExpression is the parse-tree → values.Value entry point. For expressions that are semantically boolean predicates (bare column comparisons, AND/OR/NOT), use WalkPredicate instead — WalkExpression returns a Value, not a QueryPredicate.
Dispatches by concrete ANTLR context type:
- PredicatedExpression wrapping an ExpressionAtom → walkAtom.
- Anything with a grammar Predicate attached (BETWEEN, IN, LIKE, IS NULL) — those are predicates, not values; rejected here.
walkAtom handles:
- FullColumnName → FieldValue (via ResolveIdentifier).
- Constant (integer / float / string / NULL / boolean) → ConstantValue / NullValue / BooleanValue.
- MathExpression (+, -, *, /, %, MOD, DIV) → ArithmeticValue.
- RecordConstructor (1-element unnamed, i.e. `(x)`) → unwrap.
- FunctionCall: · aggregate forms (COUNT/SUM/MIN/MAX/AVG) → AggregateValue; · CAST/CONVERT (SpecificFunction) → CastValue; · scalar UPPER/LOWER/LENGTH family → ScalarFunctionValue.
- PreparedStatementParameter (`?` / `?name`) → ParameterValue.
Everything else returns UnsupportedExpressionShapeError so the caller can fall back to the existing logical-builder path.
func (*Resolver) WalkExpressionForProjection ¶
func (r *Resolver) WalkExpressionForProjection(ctx antlrgen.IExpressionContext) (values.Value, error)
WalkExpressionForProjection is like WalkExpression but also handles BinaryComparisonPredicate as ExpressionAtom — comparison expressions like `a = b`, `x IS DISTINCT FROM NULL` that appear in SELECT lists. Separated from WalkExpression so that CASE WHEN branches don't gain comparison handling (Java rejects `WHERE CASE WHEN ... THEN a < b`).
func (*Resolver) WalkPredicate ¶
func (r *Resolver) WalkPredicate(ctx antlrgen.IExpressionContext) (predicates.QueryPredicate, error)
WalkPredicate is the dual of WalkExpression — returns a cascades QueryPredicate for an expression that's semantically boolean. Handles:
- LogicalExpression: `a AND b`, `a OR b` → And/Or predicate. NOT is a grammar-level unary-expression, handled separately.
- PredicatedExpression with BinaryComparisonPredicate atom → ComparisonPredicate.
- PredicatedExpression with a plain value atom → ValuePredicate (bare boolean column like `WHERE flag`).
Other shapes (BETWEEN, IN, LIKE, IS NULL via grammar's Predicate node; NOT; XOR) return UnsupportedExpressionShapeError.
type SubqueryPlanner ¶
type SubqueryPlanner interface {
BuildExists(query antlrgen.IQueryContext) (alias values.CorrelationIdentifier, err error)
BuildScalar(query antlrgen.IQueryContext) (alias values.CorrelationIdentifier, err error)
}
SubqueryPlanner is the callback interface for building subquery plans from the expr walker. The embedded package provides the implementation that calls buildLogicalPlanForQuery and stores the result. The Resolver itself is agnostic to how the plan is built — it only needs a fresh alias and the plan reference.
BuildExists receives the inner Query context from an ExistsExpressionAtomContext and returns:
- alias: a unique CorrelationIdentifier for the existential quantifier
- err: non-nil when the inner query cannot be planned
BuildScalar receives the inner Query context from a SubqueryExpressionAtomContext (scalar subquery) and returns:
- alias: a unique CorrelationIdentifier for the scalar subquery
- err: non-nil when the inner query cannot be planned
The planner stores the (alias → plan) mapping externally; the Resolver creates an ExistentialValuePredicate or ScalarSubqueryValue referencing the alias.
type UnsupportedExpressionShapeError ¶
type UnsupportedExpressionShapeError struct {
Shape string
}
UnsupportedExpressionShapeError signals a parse-tree shape the seed walker doesn't handle. Callers catching this can fall back to the existing logical-builder path (which handles the full surface) rather than failing at the SQL level.
func (*UnsupportedExpressionShapeError) Error ¶
func (e *UnsupportedExpressionShapeError) Error() string