logical

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package logical holds the Phase 3 (TODO.md §"Phase 3 — Semantic analysis") logical-operator hierarchy. A LogicalOperator describes WHAT a SQL query is doing — scan this table, filter on that predicate, project these columns, sort by those columns — without committing to HOW. Semantic analysis (porting of Java's `SemanticAnalyzer`) produces a LogicalOperator tree from the parse tree. Phase 4 Cascades consumes the tree and emits physical plans; until Cascades lands, the naive Generator can consume LogicalOperator directly through a thin translator.

Naming mirrors Java's `fdb-relational-core/recordlayer/query/LogicalOperator.java` + siblings, trimmed to the core operator set we actually need for the current yamsql corpus:

  • LogicalScan — `FROM tbl` (single-table read)
  • LogicalFilter — `WHERE pred`
  • LogicalProject — `SELECT a, b, expr AS n`
  • LogicalSort — `ORDER BY …`
  • LogicalLimit — `LIMIT n OFFSET m`
  • LogicalAggregate — `GROUP BY … + agg(…)`
  • LogicalJoin — `INNER / LEFT / RIGHT JOIN`
  • LogicalUnion — `UNION [ALL]`
  • LogicalInsert — `INSERT INTO tbl VALUES / INSERT SELECT`
  • LogicalUpdate — `UPDATE tbl SET col = expr WHERE …`
  • LogicalDelete — `DELETE FROM tbl WHERE …`
  • LogicalDDL — `CREATE / DROP` passthrough (no tree shape)
  • LogicalCTE — `WITH name AS (…) SELECT …` (non-recursive + recursive)

**Phase 3 scope:** this package is the TARGET SHAPE only. The semantic analyzer that translates parse tree → LogicalOperator and the translator that turns LogicalOperator → executable Plan are separate Phase 3 deliverables. Committing to the shape here lets them land incrementally without churning all of the pkg/ relational tree at once.

**Java alignment.** Most operators map 1:1 to a Java counterpart:

LogicalFilter    ↔ LogicalFilter / QueryPredicate-carrying child
LogicalProject   ↔ the query block's SelectExpression result value
LogicalSort      ↔ LogicalSortExpression
LogicalAggregate ↔ GroupByExpression
LogicalUnion     ↔ LogicalUnionExpression
LogicalInsert    ↔ InsertExpression
LogicalUpdate    ↔ UpdateExpression
LogicalDelete    ↔ DeleteExpression

Two deliberate divergences:

  1. `LogicalScan` does not exist in Java-Cascades; Java represents a FROM-source as a Cascades `FullUnorderedScanExpression` wrapped by a `Quantifier`. RFC-022 argues that Phase 3 should own a pure logical-plan representation distinct from Cascades; LogicalScan is the scan-stand-in at the logical level.
  2. `LogicalJoin` does not exist in Java-Cascades as a discrete type; Java encodes joins via a `SelectExpression` binding multiple `Quantifier`s. Same rationale: keeping join as an explicit logical operator separates Phase 3 tree-building from Phase 4 Cascades translation.

Both divergences are documented in RFC-023 / TODO Phase 3+4.

Explicit predicate / expression representation is deferred. For now LogicalFilter / LogicalProject etc. carry parse-tree handles (antlr IExpressionContext). RFC-021 Phase 2 replaces those with `Value` nodes from the Cascades Value hierarchy. RFC-023 committed to non-generic interfaces + `any`; the seed lives in `pkg/recordlayer/query/plan/cascades/`. As that package grows this one will migrate its text-handle predicates to real Values.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindCTESources

func BindCTESources(op LogicalOperator, registry CTERegistry)

BindCTESources seals source ownership during construction, before the graph is shared. Prepared definitions are never revisited under a consumer's registry. Read-side lookup uses ResolveScan and does not publish bindings.

func CorrelatedOnlyTo

func CorrelatedOnlyTo(v values.Value, set map[values.CorrelationIdentifier]struct{}) bool

CorrelatedOnlyTo reports whether v is correlated, and only to aliases in set.

func FindOuterScanTable

func FindOuterScanTable(op LogicalOperator, alias string) string

FindOuterScanTable resolves a lateral unnest's outer source alias to the scanned table name by matching a LogicalScan whose source alias is `alias` (case-insensitive) among the VISIBLE FROM-scope sources of the outer leg `op`. It is used to resolve the outer source of a lateral unnest (`FROM t, t.arr AS x` → the scan of `t`) so its proto descriptor can be inspected for the array field.

The walk MUST NOT descend into a CTE / derived-table BODY. A derived table `(SELECT … FROM T1) AS d` lowers to a `LogicalCTE{Body: <…scan of T1…>, Main: Scan(d)}`; only `d` is a visible source — `T1` is hidden inside the body and out of scope. Descending into `Body` would match the hidden `T1` scan and explode a correlated array against a source the query can't see (a silent-wrong / mis-classification). So at a `LogicalCTE` we resolve ONLY against its `Main` (the visible alias projection), never its `Body`. This mirrors Java's `resolveCorrelatedIdentifier` resolving against `getLogicalOperatorsIncludingOuter()` — the in-scope quantifiers, not nested query bodies.

Returns "" when no VISIBLE matching scan is found (a non-scan outer, or a name hidden behind a derived-table boundary), so the caller falls back to the table path.

func IsUnnestOrdinalAlias

func IsUnnestOrdinalAlias(op LogicalOperator, alias string) bool

IsUnnestOrdinalAlias reports whether `alias` names a prior lateral unnest's AT ORDINAL alias (`t.arr AS x AT o` → `o`) in the outer sub-plan `op`. That alias binds a scalar INTEGER ordinal, so a field access on it (`o.sub`) is a resolution error, not a chainable source — the caller surfaces the honest UNDEFINED_COLUMN Java produces instead of the generic unnest fallback. Same walk as FindOwnerUnnest (no CTE-Body descent), matching the AT alias only.

func OuterSourceIsDerivedTable

func OuterSourceIsDerivedTable(op LogicalOperator, alias string) bool

OuterSourceIsDerivedTable reports whether `alias` (a lateral unnest's segment-0 outer source name) is bound, in the outer sub-plan `op`, to a DERIVED-TABLE / CTE leg — i.e. a `LogicalCTE` whose Name equals `alias`. It reads the logical tree STRUCTURALLY (not a translator-internal cteScope map), so it fires independent of cteScope population order and regardless of whether the alias also names a real same-named base table.

A derived table `(SELECT …) AS D` lowers to a `LogicalCTE{Name:D, Main:Scan(D)}` inside the outer leg. When such a leg shadows a real same-named table, validating the unnest's array field against the base-table descriptor would explode the WRONG column (a derived-output silent-wrong). Detecting the derived/CTE leg here — by the in-scope quantifier alias, exactly as Java's generateCorrelatedFieldAccess resolves the in-scope source rather than the catalog table — lets the caller reject (or skip a base-table check for) the derived-output unnest in ALL cases.

Like FindOuterScanTable, it does NOT descend into a CTE's Body (only its Main): a derived table is its own FROM scope; a same-named CTE nested inside another derived body is out of the current scope.

func ReadsBuildingCTE

func ReadsBuildingCTE(op LogicalOperator) bool

ReadsBuildingCTE reports whether op reads a recursive declaration whose body is still being built: the self-reference of a recursive leg, which only the recursive translation can lower (to its temporary table scan).

func ReferencesCTE

func ReferencesCTE(op LogicalOperator, producer *CTEProducer) bool

ReferencesCTE follows selected identities, including retained dependencies.

func ReferencesCTEInScope

func ReferencesCTEInScope(op LogicalOperator, producer *CTEProducer, registry CTERegistry) bool

ReferencesCTEInScope also resolves unbound constructor inputs in the reader's lexical scope. It never seals scan ownership or prepares declarations.

func UnnestBindingName

func UnnestBindingName(binding, alias, atAlias string) string

UnnestBindingName reads a lateral source's runtime identity. A nonempty binding is the parser's collision-free identity; the empty-binding convention uses the captured AS/default name (AT for programmatic ordinal-only sources). Display aliases never replace a carried binding at a consumer.

func UnnestOrdinalityNames

func UnnestOrdinalityNames(alias, atAlias string) []string

UnnestOrdinalityNames names the two physical slots without changing their order. Semantic labels remain untouched. For AT-only input the visible ordinal keeps its name; only the hidden element is renamed on a collision.

Types

type AggCallProvenance

type AggCallProvenance struct {
	// AggColIdx is the index of the producing column; -1 for the synthesized
	// COUNT(*), which has no parsed column and no operand to resolve.
	AggColIdx int
	// Operand is that column's operand text as rendered BEFORE strip.
	Operand string
}

AggCallProvenance records which parsed aggregate column produced one AggregateCall, and what that column's operand rendered to BEFORE the producer's qualifier strip.

The pre-strip text is the load-bearing part. The producer names a call's operand as `strip(arg)` — the strip removes a same-source qualifier, and on the derived-table path it is non-empty — while a consumer holding only `aggCols` can recompute `arg` and has no way to reproduce `strip`. Comparing the stripped text against a recomputed unstripped one rejects valid queries: `SELECT SUM(d.a + d.b) FROM (SELECT a, b FROM sales) d` renders `D.A+D.B`, stores `A+D.B`, and matches neither spelling.

So the comparison happens on the pre-strip form, which both sides can produce from the same renderer. That is not a paired assertion that moves together: it is frozen here at production and recomputed at consumption from the live columns, so a change to the renderer moves both and passes (correctly — a re-rendering does not invalidate the correspondence), while a change to the COLUMNS moves one side only, which is the event being watched.

type AggregateCall

type AggregateCall struct {
	Func     string // upper-case aggregate function name (COUNT/SUM/MIN/MAX/AVG)
	Operand  string // canonical operand text; "*" for COUNT(*)
	Star     bool   // COUNT(*) (or COUNT(<non-null const>) collapsed to it)
	Distinct bool
	// IgnoreNulls and Limit are ARRAY_AGG's options (values.ArrayAggNoLimit
	// when uncapped).
	IgnoreNulls bool
	Limit       int
	// BareColumn: the operand is a single column reference PER THE PARSE
	// TREE — a lazy FieldValue read of Operand is well-defined without
	// catalog resolution. False for computed/expression operands, which
	// require a resolved Value.
	BareColumn bool
	// Qualified: a BareColumn operand carried a table qualifier PER THE
	// PARSE TREE (FullId segment count > 1) — never a dot scan of the
	// rendered name, which a delimited identifier containing a literal dot
	// would false-positive. Always false for computed operands; consumers
	// gating on qualification fall back to a conservative canonical-text
	// scan there (a dot inside a computed rendering only ever comes from a
	// real qualified reference or a quoted literal, and the gate is
	// optimization-only).
	Qualified bool
	// Bare/Qualifier are the SEGMENTS behind Operand for a BareColumn — the
	// last segment and the leading one(s). Qualified already said whether a
	// qualifier exists; these say what it IS, which is what a consumer needs to
	// resolve the operand WITHOUT slicing the rendered text apart at its first
	// dot. Empty for computed operands and for producers that have not been
	// taught to carry them; read them only through Ref().
	Bare      string
	Qualifier string
}

AggregateCall is the STRUCTURED form of one aggregate in the SELECT list, captured from the parse tree at build time — function, operand text (for result-map keying), and the star/distinct/bare-column classification. The translator consumes this instead of re-parsing the display text in Aggregates: a missing entry is a typed decline, never a string split (RFC-180 F-1 — the text reparse mangled nested arithmetic and silently dropped HAVING groups).

func (AggregateCall) CanonicalName

func (c AggregateCall) CanonicalName() string

CanonicalName renders the call as `FUNC(OPERAND)`, `FUNC(DISTINCT OPERAND)` or `COUNT(*)` — the alias-free output-column name for an aggregate. The FUNC half is upper because it is written that way from an enum, never folded from the SQL; the OPERAND half is whatever the producer minted and is not touched here.

Not every consumer folds case: normalizeAggOutputName and normalizeAggregateBindingName strip whitespace only (RFC-237), and several consumers compare exactly. The upper-case Func is safe because it is a literal, not because a fold on the far side rescues it. (Consumers that apply their own strings.ToUpper, like the aggTypes map, fold symmetrically on write and read, so that is a consistent key, not a naming decision.)

func (AggregateCall) Ref

func (c AggregateCall) Ref() ColumnRef

Ref is the operand's segment triple, reconciled against its canonical text. A COUNT(*) or computed operand captures nothing: its Operand is a rendering, and BareColumn is what says so.

type AggregateOutputSlot

type AggregateOutputSlot struct {
	SelectOrdinal int
	NativeOrdinal int
}

AggregateOutputSlot preserves one visible aggregate SELECT item after the parser's internal key/call harvesting and reordering. SelectOrdinal is one-based and unique. NativeOrdinal is zero-based into [group keys..., aggregate calls...], or -1 for a computed expression.

type Assignment

type Assignment struct {
	// Column is the SET column's identifier for display, its segments joined
	// by '.' as Java prints an Identifier (`Ambiguous reference X.X.F`). It is
	// never parsed back: a quoted segment holding a dot would split wrong, so
	// every reader of the path reads Segments or the resolved FieldOrdinals.
	// Its readers are the refusals' messages and Explain.
	Column string
	// Segments is the SET column's identifier, one normalized segment per
	// part (a quoted part keeps its case and any '.').
	Segments []string
	Expr     string // canonical text
	Value    values.Value
	// FieldOrdinals and FieldNames are the target field the identifier
	// resolved to, set by the catalog-aware builder and read by everything
	// after it (nil until then): the column's position in the target
	// descriptor, then each struct field's position in the struct before it,
	// and the fields' names (user identifiers) along the way.
	FieldOrdinals []int
	FieldNames    []string
}

Assignment is one SET clause entry. Expr is the canonical text (used for explain and as a fallback); Value is the resolved RHS expression Value (populated by the catalog-aware builder) that the executor evaluates against each target row. A nil Value means the text builder ran without catalog resolution.

type BoundJoinPredicate

type BoundJoinPredicate struct {
	Predicate       predicates.QueryPredicate
	Exists          []ExistsSubquery
	VisibleBindings []values.CorrelationIdentifier
}

BoundJoinPredicate retains ON provenance before existential folding. The source identities describe the left-to-current frame, never later joins.

type CTEIdentity

type CTEIdentity struct {
	// contains filtered or unexported fields
}

CTEIdentity identifies a declaration across immutable lowering snapshots.

type CTEOption

type CTEOption struct {
	// contains filtered or unexported fields
}

func CTEColumns

func CTEColumns(names ...string) CTEOption

func CTENamePath

func CTENamePath(path ...string) CTEOption

CTENamePath captures the declaration's normalized name and qualifier without flattening quoted dots. Synthetic declarations default to one literal name.

func CTETraversal

func CTETraversal(order TraversalOrder) CTEOption

type CTEProducer

type CTEProducer struct {
	// contains filtered or unexported fields
}

CTEProducer owns one declaration. After preparation its definition and defining registry are immutable; envelopes and scans share this same record. Consumer correlation identifiers belong to scans, never to this record.

func PrepareCTE

func PrepareCTE(name string, recursive bool, defining CTERegistry,
	build func(CTERegistry) (LogicalOperator, error), options ...CTEOption,
) (*CTEProducer, error)

PrepareCTE builds a declaration once, before publishing it. Recursive bodies receive a private self binding during construction, matching Java's temporary scan registration. A failed build never escapes into the caller's registry.

func ResolveScan

func ResolveScan(scan *LogicalScan, registry CTERegistry) *CTEProducer

ResolveScan is a read-only source lookup. Captured physical ownership cannot fall through to a later, same-named declaration. Unbound constructor inputs use this caller's registry without publishing it into a shared expression; only construction's BindCTESources seals that choice.

func (*CTEProducer) Body

func (p *CTEProducer) Body() LogicalOperator

func (*CTEProducer) BuildingSeed

func (p *CTEProducer) BuildingSeed() LogicalOperator

BuildingSeed is the seed leg of a recursive declaration whose body is still being built, else nil.

func (*CTEProducer) ColumnAliases

func (p *CTEProducer) ColumnAliases() []string

func (*CTEProducer) DefiningRegistry

func (p *CTEProducer) DefiningRegistry() CTERegistry

func (*CTEProducer) Identity

func (p *CTEProducer) Identity() *CTEIdentity

func (*CTEProducer) InsertBinding

func (p *CTEProducer) InsertBinding() values.CorrelationIdentifier

func (*CTEProducer) Name

func (p *CTEProducer) Name() string

func (*CTEProducer) NamePath

func (p *CTEProducer) NamePath() []string

func (*CTEProducer) Recursive

func (p *CTEProducer) Recursive() bool

func (*CTEProducer) ScanBinding

func (p *CTEProducer) ScanBinding() values.CorrelationIdentifier

func (*CTEProducer) SetBuildingSeed

func (p *CTEProducer) SetBuildingSeed(seed LogicalOperator)

SetBuildingSeed records a recursive declaration's seed leg for references met while its body is being built.

func (*CTEProducer) TraversalOrder

func (p *CTEProducer) TraversalOrder() TraversalOrder

func (*CTEProducer) WithBody

func (p *CTEProducer) WithBody(body LogicalOperator) *CTEProducer

WithBody creates an immutable lowering snapshot. Its declaration identity, aliases, recursive bindings and captured registry survive the body rewrite.

type CTERegistry

type CTERegistry struct {
	// contains filtered or unexported fields
}

CTERegistry is a persistent lexical registry. Extending it never changes the environment retained by an earlier declaration, including names absent there.

func (CTERegistry) BodyScope

func (r CTERegistry) BodyScope(producer *CTEProducer) CTERegistry

BodyScope selects a producer's defining frame without publishing it. Prepared declarations retain their frame; constructor inputs are interpreted in the lexical frame that introduced them in this reader's persistent registry.

func (CTERegistry) Lookup

func (r CTERegistry) Lookup(name string, path ...string) *CTEProducer

Lookup compares captured, normalized identifier segments when supplied. Only legacy string-only callers use a flattened, case-insensitive name.

func (CTERegistry) Names

func (r CTERegistry) Names() []string

func (CTERegistry) With

func (r CTERegistry) With(p *CTEProducer) CTERegistry

type ColumnRef

type ColumnRef struct {
	Present   bool
	Bare      string // last segment
	Qualifier string // leading segment(s); "" when unqualified
	Qualified bool   // parse-tree segment count > 1
}

ColumnRef is the parse-tree segment triple of a plain column reference. Present mirrors SortKey/GroupKey's convention of a populated Bare, made explicit because an unqualified reference and an uncaptured one are otherwise the same zero value and mean opposite things.

func ColumnRefFor

func ColumnRefFor(bare, qualifier string, qualified bool, rendered string) ColumnRef

ColumnRefFor states a segment triple against the name that was actually RENDERED for it, and is the only way a ColumnRef should be built from a producer's fields.

The reconciliation is the invariant, not a formality. The rendered name is not always the segments joined: the derived-table shell strips a `X.` qualifier prefix off the rendering, and a rebase can rewrite a key's text entirely. A triple describing a DIFFERENT string than the one downstream carries is worse than no triple, because it is trusted — told a bare `ID` is qualified by X, a consumer looks for leg X's ID instead of the flat column the shell just produced.

So Present is claimed only when the triple demonstrably spells the rendered name. Anything else reads as "not captured", which every consumer already handles by keeping the behaviour it had.

It lives here rather than beside any one producer because three of them now build these — projected columns, ORDER BY keys and aggregate operands — and three copies of an invariant is three chances for one to drift.

type CorrelatedScalarSubquery

type CorrelatedScalarSubquery struct {
	Alias      values.CorrelationIdentifier
	InnerPlan  LogicalOperator
	InnerAlias string
	ScalarCol  string // output column name from the inner aggregate
	// StrictSingle is true when the source can produce multiple post-pagination
	// rows, so SQL standard at-most-one-row semantics apply: a second inner row
	// per outer row is a cardinality violation (21000), not silent truncation.
	// It is false when exact pagination caps a multi-row-capable source to 0/1,
	// or when the source is intrinsically <=1 (for example a non-grouped real
	// aggregate, for which even LIMIT >1 remains safe). Data-dependent LIMIT >1
	// is rejected before this IR until post-pagination scalar collapse exists.
	StrictSingle bool
}

CorrelatedScalarSubquery pairs a correlation alias with a logical plan for a correlated scalar subquery like `(SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id)`. The inner plan has the correlation predicate baked in as a filter child of the aggregate — the executor re-evaluates it per outer row via FlatMap. Carried on LogicalProject or LogicalFilter, depending on whether the scalar is consumed by SELECT or WHERE.

type ExistsConstraint

type ExistsConstraint uint8

ExistsConstraint is a requirement derived from a bound child. It is not a correlation property: the same child has the same requirement in every clause.

const (
	ExistsAnyConsumer ExistsConstraint = iota
	ExistsPositivePredicateOnly
)

type ExistsConsumer

type ExistsConsumer uint8

ExistsConsumer describes a resolved use, not an assumed context at the child callback. Zero is deliberately not a positive-consumer default.

const (
	ExistsPositivePredicate ExistsConsumer = 1 << iota
	ExistsNegativePredicate
	ExistsProjectedValue
	ExistsBooleanPredicate
)

type ExistsInput

type ExistsInput struct {
	// contains filtered or unexported fields
}

ExistsInput owns the lowered child of an existential edge. Its exact row is captured from the producer, not reconstructed from SQL column labels. The enclosing translator consumes this same Reference and the child's independent scalar registrations. Copies of the attachment share the immutable input.

Mirrors Java ExpressionVisitor.visitExistsExpressionAtom: the existential quantifier ranges over the query operator's already-constructed Reference. Consumer admission is a separate obligation of the owning clause.

func NewDeferredExistsInput

func NewDeferredExistsInput(row values.Type, scalars []ScalarSubquery) (*ExistsInput, error)

NewDeferredExistsInput is the input of an EXISTS over a recursive declaration still being built: typed now, by the declaration's seed, and lowered by the translator inside the recursive translation, where the self-reference is the temporary table scan. Its Reference is nil.

func NewExistsInput

func NewExistsInput(reference *expressions.Reference, scalars []ScalarSubquery) (*ExistsInput, error)

func (*ExistsInput) Reference

func (i *ExistsInput) Reference() *expressions.Reference

Reference is the lowered producer, nil for a deferred input.

func (*ExistsInput) ResultType

func (i *ExistsInput) ResultType() values.Type

func (*ExistsInput) Scalars

func (i *ExistsInput) Scalars() []ScalarSubquery

type ExistsSubquery

type ExistsSubquery struct {
	Alias values.CorrelationIdentifier
	// FlowedType is the exact whole-row type of Plan. It is captured when the
	// subquery is built so every ExistsValue and existential Quantifier use one
	// stable type authority rather than re-deriving a placeholder type.
	FlowedType values.Type
	// Input owns the lowered producer and its scalar edges. Plan retains the
	// bound logical source/placement information; it is not rebuilt to type or
	// translate an attachment that already owns Input.
	Input         *ExistsInput
	Plan          LogicalOperator
	JoinPredicate predicates.QueryPredicate
	// KnownTruth is non-nil when the front-end can prove the EXISTS result from
	// relational cardinality alone. A non-grouped aggregate produces exactly one
	// row before pagination; applying a literal LIMIT/OFFSET therefore makes the
	// result statically TRUE (the row survives) or FALSE (the row is skipped).
	// LIMIT 0 is likewise empty for every supported inner shape. The translator
	// substitutes the constant in positive and negated WHERE consumers instead
	// of building a correlated semi-join. Nil means the result is data-dependent.
	KnownTruth predicates.TriBool
	// Constraint is an admission requirement, independent of dependency and
	// predicate placement. Only the completed owning clause can discharge it.
	Constraint ExistsConstraint
	// contains filtered or unexported fields
}

ExistsSubquery pairs an existential alias with the logical plan for an EXISTS subquery. Carried on LogicalFilter so the Cascades translator can build ExistentialQuantifiers over the subquery plans.

func (ExistsSubquery) AdmitConsumer

func (e ExistsSubquery) AdmitConsumer(use ExistsConsumer) (ExistsSubquery, error)

AdmitConsumer returns a copy carrying a proof for this use. Every use must satisfy the edge's constraint before its owner publishes the attachment.

func (ExistsSubquery) ValidateAdmission

func (e ExistsSubquery) ValidateAdmission() error

ValidateAdmission checks the construction proof without reclassifying Values in the translator. Programmatic constrained edges must also call AdmitConsumer with an explicit resolved context; omitting it cannot bypass admission.

type GroupKey

type GroupKey struct {
	Display   string
	Bare      string // last segment of a bare column ref; "" for expression keys
	Qualifier string // leading segment(s) when qualified; "" otherwise
	Qualified bool   // parse-tree segment count > 1
	// Segs is the FULL ordered segment list of a bare column reference
	// (`r.v.z` -> [R V Z]); nil for expression keys. Qualifier joins the
	// leading segments into one string and so cannot say where one segment
	// ends and the next begins — `R.V` reads as a source alias, which is why
	// a grouping key that descends INTO a struct column could only be
	// resolved once the segments travelled with it. Resolution consumes Segs;
	// Qualifier/Display remain renderings.
	Segs  []string
	Value values.Value
}

GroupKey is one GROUP BY key in structured form (RFC-180 F-3): the display text is a rendering for output naming and diagnostics, never re-parsed; qualification and segments are parse-tree truth; Value is the resolved key (expressions always resolve; a bare column's Value is filled by the upgrade passes, nil = lazy bare read).

func (GroupKey) Ref

func (g GroupKey) Ref() ColumnRef

Ref is the GroupKey's segment triple, reconciled against its display text.

type JoinKind

type JoinKind int

JoinKind mirrors the SQL join flavour.

const (
	JoinInner JoinKind = iota
	JoinLeft
	JoinRight
	JoinFull // FULL OUTER JOIN (Go-only extension; Java has no outer joins)
)

func (JoinKind) String

func (k JoinKind) String() string

type LogicalAggregate

type LogicalAggregate struct {
	Input     LogicalOperator
	GroupKeys []GroupKey
	// Calls is the SOLE aggregate-call representation. Builders populate it
	// from the parse tree; the translator requires it (RFC-180 F-3 retired
	// the parallel display-text slice).
	Calls                []AggregateCall
	Aliases              []string       // parallel to Calls
	AggregateOperands    []values.Value // resolved operand Values (parallel to Calls); nil slot = use text
	HasDistinctAggregate bool           // true when any aggregate uses DISTINCT (e.g. COUNT(DISTINCT x))
	// HasHaving: the query carried a HAVING clause. Presence SENTINEL only
	// (RFC-180 F-3 — the former canonical-text field was never re-parsed):
	// the translator declines when HasHaving is set but HavingPredicate is
	// nil, so an unstructured HAVING can never be silently dropped.
	HasHaving              bool
	HavingPredicate        predicates.QueryPredicate
	HavingExistsSubqueries []ExistsSubquery // EXISTS subquery plans inside HAVING
	HavingScalarSubqueries []ScalarSubquery // scalar subquery plans inside HAVING
	// OutputSlots is the visible SQL SELECT contract in SELECT-list order.
	// NativeOrdinal addresses the aggregate's physical output row
	// [GroupKeys..., Calls...]; -1 marks a computed post-aggregate item.
	// The aggregate keeps producing its native row until the legal projection
	// boundary above ORDER BY, where this contract is materialized. Public
	// labels live exclusively on that Project and are not producer identity.
	OutputSlots []AggregateOutputSlot
	// CallProvenance maps each Calls entry to the parsed aggregate column that
	// produced it, and CallProvenanceCols records how many such columns the
	// producer walked. HasCallProvenance says whether a producer recorded any —
	// distinct from the slice being nil, which is also what CONSUMED looks like.
	//
	// It exists because the operand-resolution pass used to RECONSTRUCT this
	// correspondence by case-folding the operand's rendered text. That text
	// carries identifiers AND string literals, and a fold cannot tell them
	// apart, so two aggregates differing only in a literal's case matched each
	// other and the second write clobbered the first — a silent wrong answer
	// (RFC-241). The producer already knew the correspondence; this carries it
	// instead of having it guessed.
	//
	// PARSER PROVENANCE, NOT PLAN STRUCTURE. Two obligations follow, and both
	// are load-bearing rather than tidiness:
	//   - the operand-resolution pass NILS both on every exit, so nothing
	//     downstream can read parse-time positions off a logical node;
	//   - neither may reach memo identity, or two structurally identical
	//     GroupByExpressions differing only in provenance would land in
	//     different groups. That holds by construction today: this type never
	//     enters the cascades package, it is translated into
	//     expressions.GroupByExpression first.
	CallProvenance     []AggCallProvenance
	CallProvenanceCols int
	HasCallProvenance  bool
	// OuterCorrelations is Java's outerCorrelations as
	// LogicalOperator.generateGroupBy receives them: the runtime correlations
	// of the enclosing query blocks. They are constant for every row the
	// aggregate consumes, so a post-aggregate value correlated only to them is
	// composable from the aggregate output (SemanticAnalyzer.isComposableFrom's
	// constantCorrelations arm) and pulls up unchanged (Expressions.pullUp's
	// constantAliases). Nil for a block with no enclosing sources.
	OuterCorrelations map[values.CorrelationIdentifier]struct{}
}

LogicalAggregate runs GROUP BY + aggregate functions on its child. GroupKeys are the grouping-column expressions; Calls holds the structured aggregate calls (see AggregateCall) with parallel Aliases.

func NewAggregate

func NewAggregate(input LogicalOperator, groupKeys []GroupKey, calls []AggregateCall, aliases []string, hasHaving bool) *LogicalAggregate

func (*LogicalAggregate) CallsFromAggCol

func (a *LogicalAggregate) CallsFromAggCol(aggColIdx int) []int

CallsFromAggCol returns the indices of the Calls entries produced by the parsed aggregate column at aggColIdx. Usually one; more than one only when the same column feeds several calls. Empty when the provenance table is absent, which is a producer that resolved its own operands.

Callers must have validated the table first — this trusts it.

func (*LogicalAggregate) Children

func (a *LogicalAggregate) Children() []LogicalOperator

func (*LogicalAggregate) CorrelatedOnlyToOuter

func (a *LogicalAggregate) CorrelatedOnlyToOuter(v values.Value) bool

CorrelatedOnlyToOuter reports whether v is correlated, and only to OuterCorrelations: a value every aggregated row sees as the same constant, e.g. the outer `w.f` in `(SELECT COUNT(*) + w.f FROM h)`.

func (*LogicalAggregate) Explain

func (a *LogicalAggregate) Explain(indent string) string

type LogicalCTE

type LogicalCTE struct {
	*CTEProducer
	// Alias retains a derived source's SQL qualifier independently of its
	// private CTE registration Name (empty = Name is also the qualifier).
	Alias string
	Main  LogicalOperator
	// PreserveMainSource marks a scope-only envelope: Name registers Body for
	// scans in Main, but the envelope does not replace Main's outward source
	// identity. Derived-table alias carriers leave it false because their
	// binding is the outward source identity.
	PreserveMainSource bool
	// Binding is the derived/CTE leg's binding correlation name when its
	// FROM alias duplicates an earlier leg's ("" = Name binds). See
	// LogicalScan.Binding.
	Binding string
}

LogicalCTE retains a Common Table Expression declaration around a Main query. Its consumers select the shared producer through LogicalScan.Source.

func NewCTE

func NewCTE(name string, body, main LogicalOperator, recursive bool, options ...CTEOption) *LogicalCTE

NewCTE constructs a LogicalCTE.

func NewCTEReference

func NewCTEReference(producer *CTEProducer, main LogicalOperator) *LogicalCTE

NewCTEReference retains a prepared declaration without copying its metadata.

func (*LogicalCTE) Children

func (c *LogicalCTE) Children() []LogicalOperator

func (*LogicalCTE) Explain

func (c *LogicalCTE) Explain(indent string) string

type LogicalDDL

type LogicalDDL struct {
	Kind string
	Text string
}

LogicalDDL wraps a DDL statement that has no meaningful tree shape (CREATE TABLE, DROP INDEX, …). Kind carries the DDL command ("CREATE TABLE" etc.) and Text the canonical source.

func NewDDL

func NewDDL(kind, text string) *LogicalDDL

func (*LogicalDDL) Children

func (*LogicalDDL) Children() []LogicalOperator

func (*LogicalDDL) Explain

func (d *LogicalDDL) Explain(indent string) string

type LogicalDelete

type LogicalDelete struct {
	Target string
	// TargetPath is the target's identifier segments (LogicalInsert.TablePath).
	TargetPath []string
	Input      LogicalOperator
}

LogicalDelete removes rows matching Input from Target.

func NewDelete

func NewDelete(target string, input LogicalOperator) *LogicalDelete

func (*LogicalDelete) Children

func (d *LogicalDelete) Children() []LogicalOperator

func (*LogicalDelete) Explain

func (d *LogicalDelete) Explain(indent string) string

type LogicalDistinct

type LogicalDistinct struct {
	Input LogicalOperator
}

LogicalDistinct removes duplicate rows from its input.

func NewDistinct

func NewDistinct(input LogicalOperator) *LogicalDistinct

func (*LogicalDistinct) Children

func (d *LogicalDistinct) Children() []LogicalOperator

func (*LogicalDistinct) Explain

func (d *LogicalDistinct) Explain(indent string) string

type LogicalFilter

type LogicalFilter struct {
	// HasQualify preserves the owning block's clause provenance when QUALIFY
	// is combined with WHERE. Correlated EXISTS admission must not infer that
	// provenance from a predicate that may already have folded to a constant.
	HasQualify                 bool
	Input                      LogicalOperator
	Predicate                  predicates.QueryPredicate  // preferred when non-nil
	PredicateText              string                     // source-text fallback
	ExistsSubqueries           []ExistsSubquery           // subquery plans for EXISTS predicates
	ScalarSubqueries           []ScalarSubquery           // uncorrelated scalar plans (pre-evaluated)
	CorrelatedScalarSubqueries []CorrelatedScalarSubquery // correlated scalar plans (per-row LEFT scalar join)
}

LogicalFilter applies a WHERE/HAVING predicate to its child.

Predicate is the preferred representation — a cascades QueryPredicate tree produced by the expr walker. When non-nil, Explain renders it via Predicate.Explain(), which yields the normalised form after simplification (tautology-folded, NOTs pushed to leaves, operands tree-walked).

PredicateText is the fallback: the canonical source text of the WHERE expression. Used when the expression shape is out of the walker's scope (UnsupportedExpressionShapeError) or when the builder is constructed without a metadata-backed catalog (the catalog-less Explain path, which has no transaction in scope).

func NewFilter

func NewFilter(input LogicalOperator, pred string) *LogicalFilter

NewFilter constructs a text-only LogicalFilter — used by the non-catalog-aware logical-builder path where only canonical source text is available. Pair with NewFilterWithPredicate when a predicates.QueryPredicate tree is in scope (catalog-aware builder); the predicate-tree form takes precedence in Explain output when both are set.

func NewFilterWithPredicate

func NewFilterWithPredicate(input LogicalOperator, pred predicates.QueryPredicate, text string) *LogicalFilter

NewFilterWithPredicate constructs a LogicalFilter whose predicate is a cascades QueryPredicate tree. The text form is retained for diagnostics so Explain output stays stable even when the Predicate render differs from the source text (e.g. after tautology-folding).

func (*LogicalFilter) Children

func (f *LogicalFilter) Children() []LogicalOperator

func (*LogicalFilter) Explain

func (f *LogicalFilter) Explain(indent string) string

type LogicalInlineValues

type LogicalInlineValues struct {
	Alias   string
	Binding string
	// contains filtered or unexported fields
}

LogicalInlineValues is a multi-row VALUES table source in a FROM clause. Its collection is the exact literal array Java lowers directly to an ExplodeExpression: every array element is one named record row.

This is deliberately distinct from both LogicalSingleton (the single-row, zero-column SELECT-without-FROM source) and LogicalUnnest (a lateral correlated array access). Conflating either shape with an inline table would give it the wrong cardinality or make it participate in lateral-unnest gather/collision rules.

func FindOwnerInlineValues

func FindOwnerInlineValues(op LogicalOperator, alias string) *LogicalInlineValues

FindOwnerInlineValues resolves one visible inline VALUES source in the current logical FROM scope. CTE bodies are separate scopes, so a CTE exposes only its Main here. Duplicate aliases are ambiguous and deliberately return nil instead of selecting whichever leaf the traversal happens to visit first.

func NewInlineValues

func NewInlineValues(alias string, collection values.Value) (*LogicalInlineValues, error)

NewInlineValues constructs an exact literal-table source. alias is the source's query-block correlation (an authored inline-table alias, or a parser-minted private alias when SQL omitted one).

func NewTableFunctionSource

func NewTableFunctionSource(alias string, stream values.Value) (*LogicalInlineValues, error)

NewTableFunctionSource constructs a table-function source over a streaming Value whose Type is the per-row record type.

func (*LogicalInlineValues) Children

func (*LogicalInlineValues) Children() []LogicalOperator

func (*LogicalInlineValues) CollectionValue

func (v *LogicalInlineValues) CollectionValue() values.Value

func (*LogicalInlineValues) Explain

func (v *LogicalInlineValues) Explain(indent string) string

func (*LogicalInlineValues) ResultType

func (v *LogicalInlineValues) ResultType() values.Type

func (*LogicalInlineValues) SetStream

func (v *LogicalInlineValues) SetStream(stream values.Value)

SetStream binds a table function's arguments once its lateral scope is known; the row type is fixed by the function, not by its arguments.

func (*LogicalInlineValues) StreamValue

func (v *LogicalInlineValues) StreamValue() values.Value

type LogicalInsert

type LogicalInsert struct {
	Table string
	// TablePath preserves the target's normalized identifier segments from
	// the SQL parse, as LogicalScan.TablePath does: a quoted dotted name is
	// one segment, not a template qualifier. Nil denotes legacy programmatic
	// input, resolved from Table by splitting.
	TablePath   []string
	Columns     []string
	Source      LogicalOperator
	ValuesArray values.Value
}

LogicalInsert describes an INSERT into Table. Source is the row- producing child (a SELECT); Columns is the projected-column list (may be empty to mean "all columns").

ValuesArray holds the literal VALUES rows as a Cascades array Value (an ArrayConstructorValue of one RecordConstructorValue per row), mutually exclusive with Source. The translator wraps it in an ExplodeExpression so INSERT … VALUES streams through the same Cascades path as INSERT … SELECT — matching Java's RecordConstructorValue → array → Explode → Insert shape. It is a typed Value rather than a child operator because the rows are built from evaluated literals (parameters are already substituted at plan time), which needs the connection's evaluation context the pure logical builder lacks.

func NewInsert

func NewInsert(table string, cols []string, source LogicalOperator) *LogicalInsert

func (*LogicalInsert) Children

func (i *LogicalInsert) Children() []LogicalOperator

func (*LogicalInsert) Explain

func (i *LogicalInsert) Explain(indent string) string

type LogicalJoin

type LogicalJoin struct {
	BoundOn     *BoundJoinPredicate
	Left        LogicalOperator
	Right       LogicalOperator
	Kind        JoinKind
	OnText      string
	OnPredicate any // predicates.QueryPredicate when set
	// OnExistsSubqueries carries EXISTS subqueries lifted from the ON clause
	// (RFC-154 §5) BETWEEN two builder steps only: upgradeJoinOnPredicates
	// parks them here and the block's last step, foldInnerOnExistsIntoWhere,
	// moves them into the block's WHERE filter (an inner join's ON is a WHERE
	// conjunct — Java's QueryVisitor folds it there). A join reaching the
	// translator never carries one; the translator asserts that. Only ever
	// populated for INNER joins (OUTER EXISTS-in-ON is rejected — RFC-154
	// §5.2b).
	OnExistsSubqueries []ExistsSubquery
}

LogicalJoin combines two children. Empty OnText means "no ON condition" (comma cross-join form — the outer WHERE provides the predicate). OnPredicate is the optional structured form (used by the catalog-aware walker); when non-nil, it takes precedence over OnText for Cascades lowering.

func NewJoin

func NewJoin(left, right LogicalOperator, kind JoinKind, on string) *LogicalJoin

func NewJoinWithPredicate

func NewJoinWithPredicate(left, right LogicalOperator, kind JoinKind, pred any) *LogicalJoin

NewJoinWithPredicate builds a LogicalJoin with a structured ON predicate.

func (*LogicalJoin) Children

func (j *LogicalJoin) Children() []LogicalOperator

func (*LogicalJoin) Explain

func (j *LogicalJoin) Explain(indent string) string

type LogicalLimit

type LogicalLimit struct {
	Input      LogicalOperator
	Limit      int64
	Offset     int64
	LimitValue values.Value
}

LogicalLimit caps the row count, optionally after skipping Offset. Negative Limit means "no limit" (pure offset).

LimitValue is an OPTIONAL runtime row cap (RFC-156 parameterized vector rank limit `... <= ?`): when non-nil the cap is evaluated at execution against the bound parameters and Limit is the no-cap sentinel (-1).

func NewLimit

func NewLimit(input LogicalOperator, limit, offset int64) *LogicalLimit

func NewRuntimeLimit

func NewRuntimeLimit(input LogicalOperator, limitValue values.Value, offset int64) *LogicalLimit

NewRuntimeLimit builds a LIMIT whose row cap is a runtime Value (evaluated at execution). The static Limit is the no-cap sentinel (-1).

func (*LogicalLimit) Children

func (l *LogicalLimit) Children() []LogicalOperator

func (*LogicalLimit) Explain

func (l *LogicalLimit) Explain(indent string) string

type LogicalOperator

type LogicalOperator interface {
	// Children returns the immediate child operators. Returning an
	// empty slice (not nil) for leaf nodes keeps caller code free
	// of nil checks.
	Children() []LogicalOperator

	// Explain returns an indented textual rendering of this node
	// and its subtree. The indent argument is prefixed to this
	// node's first line; children receive indent + "  ".
	//
	// This is the stable surface Plan.Explain() exposes to
	// frontend callers. Cascades physical plans will Explain()
	// through their own impl; logical plans use this.
	Explain(indent string) string
}

LogicalOperator is the root interface every logical operator satisfies. A LogicalOperator exposes its children (tree structure) and can render an indented text explanation of the subtree.

Operators are value-types (small, immutable once constructed). They are NOT identity-comparable — two structurally-identical Filter nodes should compare equal under a structural walker (implemented on top of this interface, not part of it).

func AttachedPlans

func AttachedPlans(op LogicalOperator) []LogicalOperator

AttachedPlans returns the SUBQUERY plans attached to op beyond Children(): EXISTS/scalar plans on filters, projections, aggregates (HAVING) and joins (ON). Tree walkers that must see EVERY built operator (e.g. the CTE alias-arity validator) traverse Children() + AttachedPlans(). Keep this switch in lockstep with the attachment fields on the structs above — a new attachment field added without an arm here silently escapes whole-tree validation.

func FoldTransparentUnaryInput

func FoldTransparentUnaryInput(op LogicalOperator) (LogicalOperator, bool)

FoldTransparentUnaryInput returns (input, true) when op is a fold-transparent unary operator — one the RFC-141 projected-EXISTS fold descends THROUGH to reach the existential filter without changing the row shape. Only Sort and Limit qualify (a Project/Join/Aggregate/Distinct/Union reshapes the rows and is NOT fold-transparent). This is the SINGLE source of truth for the transparency set: both the translator's `findExistsFilterUnderUnaryChain` (which folds the projection through the chain) and the generator's `existsFilterReachableForFold` (which rejects a projected EXISTS the fold cannot reach) consult it, so the two can never silently diverge. Returns (nil, false) for any non-transparent op.

type LogicalProject

type LogicalProject struct {
	Input           LogicalOperator
	Projections     []string
	Aliases         []string       // parallel to Projections; "" means no alias
	ProjectedValues []values.Value // parallel to Projections; nil slot = walker declined
	IsComputed      []bool         // parallel to Projections; true = expression, not plain column ref
	// SQLNames preserves semantic name presence independently of physical
	// aliases: an empty slot is unnamed, even when its emitted field is _N.
	// nil means names derive from the projection's authored references/aliases.
	SQLNames []string
	// AliasMinted is parallel to Aliases: true = the alias in that slot was
	// written by the MACHINERY, not by the user's `AS`. It is the provenance of
	// the name, carried, because the name's SHAPE cannot carry it: the
	// duplicated-bare-leaf dedup pins a projected reference's QUALIFIED
	// spelling ("A.K") as the alias so two same-named datum keys stay
	// distinguishable in the executor's row map, and a user is equally free to
	// write `AS "A.K"`. A consumer that decides between them by inspecting the
	// string gets the user's alias wrong, which is how `SELECT u.name AS
	// "U.NAME"` came to report the label NAME.
	//
	// Java needs no such marker because its internal column for a duplicate is
	// ANONYMOUS (Expressions.java:268-287) while the label is untouched; where
	// it genuinely needs two names for one column it carries a SEPARATE
	// construction-time field (Type.Record.Field.fieldStorageNameOptional,
	// Type.java:2826-2827,2897-2899) rather than recovering provenance from a
	// spelling later. Go's executor keys its row map by name, so the qualified
	// key must stay — deleting it returns ONE column for `SELECT a.k, b.k` —
	// and the provenance rides beside it instead.
	//
	// nil (or a short slice) reads as "user alias" for every slot it does not
	// cover: a machinery mint is the exceptional case and states itself.
	AliasMinted []bool
	// AliasSources is parallel to AliasMinted. A present entry is the
	// STRUCTURED authored source identity captured at the instant the machinery
	// minted that slot's alias. It is not the later physical Value owner: a
	// projection Value can be reanchored onto `_current` while the alias remains
	// the datum key minted from source A. A short/nil vector means the source was
	// not captured; consumers must never reconstruct it from alias text.
	AliasSources []values.ProjectionAliasSource
	// SQLNameAuthored is parallel to Projections: true = the SQL name the slot
	// publishes was written by the statement — an explicit `AS`, or an
	// un-aliased reference to a column whose own name was (an unnest's AS/AT
	// alias, a derived table's or CTE's authored label). Aliases cannot say
	// this: the builder fills an un-aliased reference's slot with the selected
	// attribute's inherited name. A derived table or CTE over this projection
	// publishes an authored slot as semantic.Column.SQLAuthored, which
	// resolution compares exactly. nil (or a short slice) reads as NOT
	// authored — the descriptor-name reading, which keeps the relaxed pass.
	SQLNameAuthored []bool
	// AggregateOutputOrdinals is the exact native [group keys..., aggregate
	// calls...] input slot for each post-aggregate projection item. A negative
	// entry marks a computed item whose Value tree is bound separately. nil
	// means this is not the SQL aggregate-output boundary. Keeping this
	// identity separate from Projections/Aliases prevents a group key and an
	// aggregate alias with the same spelling from being rebound by name.
	AggregateOutputOrdinals []int
	// InputOrdinals is a complete positional projection contract for machinery
	// boundaries that are created before their input quantifier exists (for
	// example a CTE column-list rename). The logical node deliberately leaves
	// the corresponding ProjectedValues nil; cascades translation resolves each
	// slot against the real input quantifier's exact flowed object value. nil
	// means no such positional boundary.
	InputOrdinals []int
	// AggregateSlots is parallel to Projections; true = the slot's value tree
	// CONTAINS an aggregate. Captured pre-rewrite, where the *AggregateValue
	// node is still present (rewriteAggregateValuesInTree destructively replaces
	// it with a typed FieldValue). Read once by the INSERT…SELECT promotion guard
	// to identify reliably-typed aggregate-result columns — plain columns are
	// concrete-typed too (ResolveIdentifier), so type-presence cannot
	// discriminate. A bridge until the Java end-state (PromoteValue projection
	// nodes), which dissolves this marker.
	AggregateSlots             []bool
	ScalarSubqueries           []ScalarSubquery           // uncorrelated scalar subquery plans (pre-evaluated)
	CorrelatedScalarSubqueries []CorrelatedScalarSubquery // correlated scalar subquery plans (re-evaluated per outer row via FlatMap)
	// ProjectionRefs is parallel to Projections and carries the parse-tree
	// SEGMENTS of a plain column item — the same triple SortKey and GroupKey
	// already carry, for the same reason (RFC-197: qualification is FullId
	// SEGMENT COUNT, never a scan of the rendered name).
	//
	// Projections is a RENDERING. `A.B` written as a qualified reference and
	// `"A.B"` written as one quoted identifier are the same bytes, so a consumer
	// that recovers the qualifier by slicing at the first dot cannot tell a
	// reference to source A from a column literally so named — it manufactures a
	// qualifier out of a name and resolves against the wrong row. The segments
	// are what the parser actually saw, so they decide it.
	//
	// A zero entry means "no segments": a computed item, a sentinel slot, or a
	// producer that has not been taught to capture them. Consumers must treat
	// that as "unknown", never as "not qualified" — the ONLY safe reading of an
	// absent segment triple is to fall back to whatever the rendered name
	// supported before.
	ProjectionRefs []ColumnRef
}

LogicalProject selects / renames columns and computes expressions. Each element of Projections is the canonical text of the projected expression or column name. Aliases (parallel slice) hold the output name; empty string means "use the underlying name."

ProjectedValues (parallel to Projections) carries resolved Value trees when the catalog-aware builder successfully walks the ANTLR expression. nil slots mean the walker declined (unsupported shape) — the Cascades translator treats nil as "cannot translate" and returns nil for the whole query. Non-nil slots are used directly as projection Values in the Cascades plan.

func NewProject

func NewProject(input LogicalOperator, projs, aliases []string) *LogicalProject

func (*LogicalProject) Children

func (p *LogicalProject) Children() []LogicalOperator

func (*LogicalProject) Explain

func (p *LogicalProject) Explain(indent string) string

type LogicalScan

type LogicalScan struct {
	Source ScanSource
	Table  string
	Alias  string
	// TablePath preserves normalized identifier segments from the SQL parse
	// boundary. A quoted dotted name is one segment, not a template qualifier.
	// Nil denotes legacy programmatic input; non-nil empty is malformed.
	TablePath []string
	// Binding is the scan's binding correlation name when its FROM alias
	// DUPLICATES an earlier leg's at the same level ("" = the alias binds —
	// every non-duplicate leg). Carried from the
	// parser's single mint authority (assignFromLegBindingIDs); consumers
	// read it via sourceBinding and never re-derive binding identity from
	// the alias. The SQL alias stays the DISPLAY qualifier (sourceAlias).
	Binding string
}

LogicalScan reads a single table. Empty Alias means "use the table name as the source alias."

func FindVisibleScan

func FindVisibleScan(op LogicalOperator, alias string) *LogicalScan

FindVisibleScan resolves a FROM qualifier without entering a definition.

func NewScan

func NewScan(table, alias string, tablePath ...string) *LogicalScan

NewScan constructs a LogicalScan. SQL builders supply captured tablePath segments; synthesized alias references supply exactly one literal segment. Omitting tablePath retains the legacy string-only programmatic interface.

func (*LogicalScan) Children

func (*LogicalScan) Children() []LogicalOperator

func (*LogicalScan) Explain

func (s *LogicalScan) Explain(indent string) string

type LogicalSingleton

type LogicalSingleton struct{}

LogicalSingleton is a source containing one row with no public columns. Java's FROM-less source uses Explode([true]) with empty visible expressions; the translator hides that private BOOLEAN behind an empty-record projection. SELECT expressions belong to the ordinary LogicalProject above this source.

func NewSingleton

func NewSingleton() *LogicalSingleton

NewSingleton constructs the one-row, zero-column FROM-less source.

func (*LogicalSingleton) Children

func (*LogicalSingleton) Children() []LogicalOperator

func (*LogicalSingleton) Explain

func (*LogicalSingleton) Explain(indent string) string

type LogicalSort

type LogicalSort struct {
	Input LogicalOperator
	Keys  []SortKey
}

LogicalSort sorts its child rows by the given keys.

func NewSort

func NewSort(input LogicalOperator, keys []SortKey) *LogicalSort

func (*LogicalSort) Children

func (s *LogicalSort) Children() []LogicalOperator

func (*LogicalSort) Explain

func (s *LogicalSort) Explain(indent string) string

type LogicalUnion

type LogicalUnion struct {
	Inputs   []LogicalOperator
	Distinct bool
}

LogicalUnion ties together two (or more) children with UNION [ALL] semantics. Distinct = true applies a DISTINCT dedup across the union.

func NewUnion

func NewUnion(inputs []LogicalOperator, distinct bool) *LogicalUnion

func (*LogicalUnion) Children

func (u *LogicalUnion) Children() []LogicalOperator

func (*LogicalUnion) Explain

func (u *LogicalUnion) Explain(indent string) string

type LogicalUnnest

type LogicalUnnest struct {
	// Segments is the un-flattened dotted name of the array source
	// (`["T1","ARR1"]` for `T1.arr1`). Segment 0 names the in-scope outer
	// source; the remaining segments name the array field on it. Kept
	// un-flattened (no re-split of a joined string) so the translator
	// resolves segment-by-segment against the scope.
	Segments []string
	// Binding carries the comma source's duplicate-alias binding id
	// (see LogicalScan.Binding) so the
	// TABLE-FIRST demotion (demoteQualifiedTableUnnest) can restore it on
	// the demoted LogicalScan. A genuine unnest consumes the same identity;
	// repeated SQL labels do not replace it or collapse element/ordinal
	// slots (RFC-256).
	Binding string
	// Alias is the AS alias (`x` in `... AS x`) bound to each unnested
	// element. Empty when the AS alias is omitted (AT-only form).
	Alias string
	// AtAlias is the AT ordinal alias (`ord` in `... AT ord`), empty when
	// absent. Its presence makes the Explode WITH ORDINALITY.
	AtAlias string
	// CorrelatedCollection is the already-resolved array Value over the outer
	// correlation. A lateral FROM leg binds it against its preceding FROM
	// prefix; a correlated primary source in EXISTS binds it against its outer
	// scope. Carrying the value preserves the owner, exact type and ordinal
	// path instead of resolving the syntax again during translation.
	CorrelatedCollection values.Value
	// EnclosingOwner records that a lateral FROM leg's collection resolved to a
	// source of an ENCLOSING query, not of this FROM (`EXISTS (SELECT 1 FROM h,
	// w.arr AS v)` over an outer w; Java's resolveCorrelatedIdentifier reads
	// the operators including the outer ones). The binder sets it from the
	// resolution; the translator explodes such a collection as it is, correlated
	// to the outer query, and declines a collection its FROM's left side does
	// not own unless this is set.
	EnclosingOwner bool
}

LogicalUnnest is a lateral array UNNEST source in the FROM list (`FROM t, t.arr AS x [AT ord]`). It is the RIGHT child of a lateral LogicalJoin whose LEFT child is the source `t` it correlates to. The translator lowers it to an Explode of the correlated array field under a FlatMap of the outer source. Mirrors Java's `LogicalOperator.generateCorrelatedFieldAccess`. RFC-142 R5.

func FindOwnerUnnest

func FindOwnerUnnest(op LogicalOperator, alias string) *LogicalUnnest

FindOwnerUnnest returns the LogicalUnnest in the outer sub-plan `op` whose element (AS) alias matches `alias` — the OWNER of a CHAINED lateral unnest (`FROM t, t.arr AS x, x.sub AS y`: the second unnest's segment-0 `x` names the first unnest's element). Mirrors FindOuterScanTable's walk (does not descend into a CTE/derived Body — a derived table is its own FROM scope, so a chain rooted inside a derived body is out of scope), but matches unnest element aliases instead of scans. nil when no such prior unnest.

The AT ordinal alias (`t.arr AS x AT o`) is deliberately NOT matched: `o` binds a scalar INTEGER ordinal, not a struct, so `o.sub` can never be a valid chain root. Matching it would misclassify `o.sub` as owned by this unnest and resolve `sub` against the ELEMENT's descriptor — emitting a plan that silently returns zero rows where Java rejects `o.sub` (a field access on a scalar). Only the AS element alias carries a chainable struct.

func (*LogicalUnnest) Children

func (*LogicalUnnest) Children() []LogicalOperator

func (*LogicalUnnest) Explain

func (u *LogicalUnnest) Explain(indent string) string

type LogicalUpdate

type LogicalUpdate struct {
	Target string
	// TargetPath is the target's identifier segments (LogicalInsert.TablePath).
	TargetPath []string
	Sets       []Assignment
	Input      LogicalOperator // the scan + filter producing target rows
}

LogicalUpdate updates Target rows matching Input with the per-col expression assignments in Sets.

func NewUpdate

func NewUpdate(target string, sets []Assignment, input LogicalOperator) *LogicalUpdate

func (*LogicalUpdate) Children

func (u *LogicalUpdate) Children() []LogicalOperator

func (*LogicalUpdate) Explain

func (u *LogicalUpdate) Explain(indent string) string

type ScalarSubquery

type ScalarSubquery struct {
	Alias values.CorrelationIdentifier
	Plan  LogicalOperator
}

ScalarSubquery pairs a correlation alias with the logical plan for a scalar subquery `(SELECT MAX(v) FROM t2)`. Carried on LogicalFilter and LogicalProject so the Cascades translator can build inner plans. The executor pre-evaluates these and binds the scalar result under Alias before evaluating the outer plan's predicates/projections.

type ScanSource

type ScanSource struct {
	// contains filtered or unexported fields
}

ScanSource records both a selected producer and a physical lookup. The zero value is reserved for constructor input not yet adapted by BindCTESources.

func CTEScanSource

func CTEScanSource(p *CTEProducer) ScanSource

func PhysicalScanSource

func PhysicalScanSource() ScanSource

func (ScanSource) Producer

func (s ScanSource) Producer() *CTEProducer

func (ScanSource) Resolved

func (s ScanSource) Resolved() bool

type SortDir

type SortDir int

SortDir distinguishes ASC (default) from DESC.

const (
	SortAsc SortDir = iota
	SortDesc
)

func (SortDir) String

func (d SortDir) String() string

type SortKey

type SortKey struct {
	Expr       string // canonical text
	Dir        SortDir
	NullsFirst bool
	Value      values.Value // resolved Value expression (nil = use text as FieldValue)
	// Pos is a selected output's 1-based position; 0 means no slot selected.
	// ORDER BY <n>, a resolved SELECT alias, and a resolved UNION output name
	// use the same slot-binding mechanism. Authored numeric syntax remains in
	// the parser's orderByClause, independently of this semantic address.
	// The translator bakes a surviving Pos directly to the output slot, without
	// reinterpreting its rendered spelling as another alias or expression.
	Pos int
	// AggregateOutputOrdinal is an exact address into the grouped input's
	// native [keys..., calls...] row. The bool distinguishes native slot zero
	// from an unset field. It is used when the visible aggregate Project is
	// deliberately above ORDER BY.
	AggregateOutputOrdinal    int
	HasAggregateOutputOrdinal bool
	// AggregateOutputValueExact marks Value as a validated structural draft
	// over the aggregate's grouping keys and calls. The translator binds that
	// draft to its real aggregate-row owner; generic sort rebasing must not
	// overwrite it by name.
	AggregateOutputValueExact bool
	// Bare/Qualifier/Qualified: parse-tree segments of a plain column
	// reference key; zero values for positional and expression keys (their
	// Expr is a rendering only). Qualification is FullId SEGMENT COUNT.
	Bare      string
	Qualifier string
	Qualified bool
	// Segs is the FULL ordered segment list of the reference (`a.n.sk` ->
	// [A N SK]). Resolution consumes Segs; Qualifier is a rendering that
	// cannot say where one segment ends and the next begins, so a deeper
	// reference resolved through it looks for a source named "A.N".
	Segs []string
	// BareRef marks a key whose source text is a plain ONE-segment column
	// reference — the only shape SQL binds to an output alias. False for
	// qualified references (`ORDER BY d.x`), aggregates and computed
	// expressions: their canonical renderings ("X" after qualifier
	// stripping, "SUM(S.SCORE)") are indistinguishable from a delimited
	// alias's spelling, so alias-binding passes REQUIRE BareRef instead of
	// inspecting Expr text.
	BareRef bool
}

SortKey is one ORDER BY entry.

func (SortKey) Ref

func (k SortKey) Ref() ColumnRef

Ref is the SortKey's segment triple, reconciled against its canonical text. A positional or computed key captures nothing: its Expr is a rendering only, and BareRef is what says so.

type TraversalOrder

type TraversalOrder int
const (
	TraversalAnyOrder TraversalOrder = iota
	TraversalLevelOrder
	TraversalPreOrder
	TraversalPostOrder
)

The zero value is ANY — a recursion with NO traversal clause leaves the order to the planner (Java TraversalStrategy.ANY: both the level union and the DFS join implement, the cost model picks). An EXPLICIT `TRAVERSAL ORDER level_order` is TraversalLevelOrder and PINS the level union (Java's LEVEL gates the DFS rule off) — the two were once conflated, which made the explicit clause unable to force the plan.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL