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 (width-narrowed like Java's parseDecimal), float, string, NULL.
- 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 SourceRowType(src semantic.ScopeSource) *values.RecordType
- 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) DescendsIntoStruct(qualifier, id semantic.Identifier) (bool, error)
- func (r *Resolver) DescendsIntoStructPath(segs []semantic.Identifier) (bool, error)
- func (r *Resolver) QualifierIsDuplicated(qualifier semantic.Identifier) bool
- 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) ResolveIdentifierPath(segs []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) ResolveQualifiedProjection(qualifier, id semantic.Identifier) (values.Value, error)
- func (r *Resolver) ResolveQualifiedProjectionPath(segs []semantic.Identifier) (values.Value, error)
- func (r *Resolver) ResolveStartsWith(lhs values.Value, prefix values.Value) (predicates.QueryPredicate, error)
- func (r *Resolver) Scope() *semantic.Scope
- 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 UnresolvableOrdinalError
- 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 SourceRowType ¶
func SourceRowType(src semantic.ScopeSource) *values.RecordType
sourceRowType builds the ROW the resolved source flows: its declared columns, in declared order, each carrying the catalog's own type.
This is the layout a source-relative ordinal indexes, stated as a type rather than as a bare signature — which is what lets a reference's quantifier object CARRY it (Java's `QuantifiedObjectValue.of(getAlias(), getFlowedObjectType())`, Quantifier.java:801-803, where the flowed value is never untyped).
Built with a struct literal, not NewRecordType: that constructor PANICS on a duplicate field name, and a catalog is not this function's to validate — a degenerate source should decline downstream, not abort resolution.
nil when the source declares no column order, which is exactly the condition sourceColumnOrdinal declines on, so the two answers cannot disagree. SourceRowType is the exported view of sourceRowType, for callers outside this package that hold a resolved ScopeSource and need the row it flows — the enclosing-WITH bindings a derived body must be typed against, in particular.
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 that is neither an ARRAY column nor an explicit value list — a prepared-parameter list, or a shape the grammar admits and neither engine wires.
It used to say "Java rejects this as unsupported syntax", and that was a scope claim exceeding what the code does: Java's rule is a TYPE test, so it ACCEPTS an ARRAY-typed column reference as the list and rejects only the others. Go now accepts the same one — see resolveInAgainstColumnList — and the non-array rejection carries Java's wording.
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) DescendsIntoStruct ¶
func (r *Resolver) DescendsIntoStruct(qualifier, id semantic.Identifier) (bool, error)
func (*Resolver) DescendsIntoStructPath ¶
func (r *Resolver) DescendsIntoStructPath(segs []semantic.Identifier) (bool, error)
DescendsIntoStructPath is DescendsIntoStruct over the full segment list. The two-part form can only ask about the first two segments, so an alias-qualified descent (`a.n.sk`) looked like a lookup of a source "A.N", failed, and reported "does not descend" — which let a nested grouping key past the gate that exists to refuse it.
func (*Resolver) QualifierIsDuplicated ¶
func (r *Resolver) QualifierIsDuplicated(qualifier semantic.Identifier) bool
QualifierIsDuplicated reports whether the given qualifier ALIAS names MORE than one local scope source (`FROM p AS a, q AS a`) — a duplicate plain alias. A duplicated qualifier cannot be served by ordinary display-keyed emission (the duplicate-FROM-alias mint gives the sources distinct correlations), so the caller forces the per-binding bake: a source-relative ordinal against the resolved leg. A unique alias bound by its own name stays with ordinary emission.
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)
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 a known type — nil, int, int32, int64, float32, float64, string, bool, []byte are supported. Integer literals width-narrow like Java ParseHelpers.parseDecimal (in-int32-range → INT, else LONG); a float64 carrier is a DOUBLE literal and a float32 carrier a FLOAT one, so arithmetic over them rides ArithmeticValue's matching per-width lanes.
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) ResolveIdentifierPath ¶
ResolveIdentifierPath is ResolveIdentifier for a dotted reference of ARBITRARY depth — the segment list exactly as the parse tree spelled it, never a joined string. Joining is lossy in both directions: a delimited identifier may CONTAIN a dot, and a three-segment reference joined into a two-part (qualifier, name) pair produces a qualifier ("A.N") that names neither a source nor a column, which is how `SELECT a.n.sk` came to be refused as an undefined column.
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) ResolveQualifiedProjection ¶
func (r *Resolver) ResolveQualifiedProjection(qualifier, id semantic.Identifier) (values.Value, error)
ResolveQualifiedProjection resolves a QUALIFIED projection reference on the join path: it ALWAYS runs the per-attribute ambiguity check (Java's 42702) and returns a non-nil Value only when the reference binds to a LATER duplicate-alias leg (src.CorrelationName differs from the alias) — the QOV-correlated read addressing THAT leg's quantifier, which the ordinal bake resolves positionally. nil Value + nil error keeps the caller's legacy alias-keyed emission (behavior-preserving for every non-duplicate query); non-ambiguity resolution misses also return nil,nil (column validation owns 42703 on this path, as before). The AmbiguousColumnError disposition is per-caller: the projection path SURFACES it; the ORDER BY (qualifyShadowedSortKeys) and GROUP BY (upgradeAggregateOperands) callers deliberately DISCARD it, because the upstream reference validation has already terminated an ambiguous sort/group key with 42702 before those helpers run — the swallow is not a dead error path, do not "fix" it into one.
func (*Resolver) ResolveQualifiedProjectionPath ¶
ResolveQualifiedProjectionPath is ResolveQualifiedProjection over the full segment list, so a projection that DESCENDS into a struct on the join path (`a.n.sk`) binds the same leg the flat reference would and then carries the descent as an accessor suffix — Java's ofFieldsAndFuseIfPossible applied to the leg-correlated root (SemanticAnalyzer.java:598-599). Resolving only the first two segments here would bind the right leg and read the WRONG column: the struct itself instead of the member the reference names.
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) Scope ¶
DescendsIntoStruct reports whether a reference resolves by descending INTO a struct column rather than addressing a source column directly — `n.sk` where `n` is a struct column, as opposed to `t.sk` where `t` is a FROM source.
The fact comes from the semantic layer's own accessor chain, never from the shape of the produced Value: exact field resolution yields a multi-accessor FieldPath today, but the number of accessors a root reference carries is a property of the source kind, so counting them would be an inference where the analyzer already states the answer. The accessor chain is the analogue of Java's lookupNestedField result — INFERRED FROM JAVA SOURCE, SemanticAnalyzer.java:578-601, not observed against a running server; what IS measured live is the outcome this gate produces, in conformance/nested_groupby_key_java_probe_test.go.
A resolution FAILURE reports false with the error, so a caller gating on the descent never converts an unrelated lookup failure into its own verdict — existence and ambiguity stay owned by the checks that already report them. Scope returns the FROM-side scope this resolver binds against.
It exists for star expansion, which is the one caller that needs the sources' OUTPUT rather than an answer about one reference. Java hands the same thing to the same caller: expandStar takes the plan fragment's LogicalOperators and reads getExpressions().nonEphemeralVisible() off them (SemanticAnalyzer.java:323-330). Reaching for the scope instead of re-deriving the source list from the FROM parse tree is what keeps the star and the resolver naming the same columns.
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.
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, flowed values.Type, err error)
BuildScalar(query antlrgen.IQueryContext) (alias values.CorrelationIdentifier, typ values.Type, 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
- flowed: the inner plan's exact whole-row type
- err: non-nil when the inner query cannot be planned or typed exactly
BuildScalar receives the inner Query context from a SubqueryExpressionAtomContext (scalar subquery) and returns:
- alias: a unique CorrelationIdentifier for the scalar subquery
- typ: the inner plan's single output column type (UnknownType when the shape is underivable) — flowed into ScalarSubqueryValue so the plan-time gates see the real type
- 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 UnresolvableOrdinalError ¶
UnresolvableOrdinalError reports a column resolution whose source cannot bind a plan-time ordinal (no declared column order — an empty derived-table catalog or a computed alias outside it). Every production SQL resolution binds one (verified empirically across the yamsql, embedded, and full FDB driver suites: the lazy fallbacks were dead-in-effect), so this is LOUD at plan time — never a lazy FieldValue that dies later as a runtime OrdinalResolutionError or, worse, reads by name (WS-N Phase A slice 2: born-baked resolutions).
func (*UnresolvableOrdinalError) Error ¶
func (e *UnresolvableOrdinalError) Error() string
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