logical

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 4 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   ↔ LogicalProjectionExpression
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 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.

Types

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
	// 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.

CONSUMERS NO LONGER ALL FOLD, and this sentence used to say they did ("case-insensitively, upper-cased or via normalizeAggOutputName"). Say exactly which changed, because the first attempt at this correction over-claimed in the other direction:

  • normalizeAggOutputName and normalizeAggregateBindingName stopped upper-casing under RFC-237. They now strip whitespace only.
  • Some consumers still apply their OWN strings.ToUpper to this result — logical_predicate.go's aggTypes map is one, and it folds symmetrically on write and read, which is a consistent key rather than a naming decision.

So the upper-case Func is safe because it is a LITERAL, not because a fold on the far side would have rescued it. That distinction is load-bearing the moment a consumer compares exactly, and several now do.

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 string
	Expr   string // canonical text
	Value  values.Value
}

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 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 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
	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
	// OuterOnlyJoinConjuncts marks a JoinPredicate carrying conjuncts with
	// NO inner-source reference that can FILTER (a nested-EXISTS middle
	// routes them here; the inside placement does not plan for that
	// composition; statically-TRUE tautologies are excluded - routing TRUE
	// is a no-op). The outer-routing validity matrix, enforced by
	// declineNegatedOuterOnlyEsq (predicate side) and
	// declineNegatedOuterOnlyEsqValue (value side) in the translator:
	//
	//	WHERE/ON + positive  -> VALID   (P AND EXISTS(Q) == EXISTS(P AND Q))
	//	WHERE/ON + negative  -> DECLINE (would compute P AND NOT-EXISTS(Q))
	//	projected + either   -> DECLINE (outer-routing filters the row
	//	                        stream; a projected boolean must not)
	//
	// HAVING is kept out of the surface by translateAggregate's blanket
	// rejection of HavingExistsSubqueries.
	OuterOnlyJoinConjuncts bool
}

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.

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
}

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. 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
}

func NewAggregate

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

func (*LogicalAggregate) Children

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

func (*LogicalAggregate) Explain

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

type LogicalCTE

type LogicalCTE struct {
	Name           string
	Body           LogicalOperator
	Main           LogicalOperator
	Recursive      bool
	ColumnAliases  []string // WITH c(a, b) AS (...) → renames body's output columns
	TraversalOrder TraversalOrder
	// PreserveMainSource marks a scope-only envelope: Name registers Body for
	// scans in Main, but the envelope does not replace Main's outward source
	// identity. Correlated EXISTS/Scalar plans use this when they copy enclosing
	// CTE definitions into a self-contained subplan. Derived-table alias carriers
	// leave it false because their CTE name deliberately IS the outward alias.
	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 wraps a named Common Table Expression around a Main query. The Body is the CTE's own plan; Main references Body via a LogicalScan on Name. Recursive CTEs set Recursive=true — Body may self-reference (the recursive evaluator lives at the executor layer for now).

func NewCTE

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

NewCTE constructs a LogicalCTE.

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
	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 {
	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 LogicalValues (the legacy single-row, text-only SELECT-without-FROM seed) 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 (*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

type LogicalInsert

type LogicalInsert struct {
	Table       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 {
	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). The cascades translator turns each into an existential
	// quantifier on the join's SelectExpression, so the NLJ rule's
	// the existential peel path builds the semi-join. Only populated for
	// INNER joins (OUTER EXISTS-in-ON is deferred — 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
	// 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
	// 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 {
	Table string
	Alias 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 NewScan

func NewScan(table, alias string) *LogicalScan

NewScan constructs a LogicalScan.

func (*LogicalScan) Children

func (*LogicalScan) Children() []LogicalOperator

func (*LogicalScan) Explain

func (s *LogicalScan) 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 (demoteSchemaQualifiedUnnest) can restore it on
	// the demoted LogicalScan — a mis-classified schema-qualified TABLE leg
	// is a table leg for binding purposes. A GENUINE unnest never consumes
	// it: a duplicate unnest AS/AT alias is rejected outright (RFC-142).
	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 set only when this unnest is the primary source
	// of a correlated subquery (`EXISTS (SELECT ... FROM R.TAGS AS E)`). It is
	// the already-resolved array FieldValue over the outer correlation. A
	// regular lateral FROM leg gets that value from the LogicalJoin on its left
	// and leaves this nil. Carrying the resolved Value preserves minted outer
	// correlations and avoids resolving the owner a second time.
	CorrelatedCollection values.Value
}

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
	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 LogicalValues

type LogicalValues struct {
	Rows    []string
	Aliases []string
}

LogicalValues is a leaf operator that yields a single row of constant/expression projections — the canonical target for a SELECT without a FROM clause (`SELECT 1 + 2, 'hello'`). Rows is a list of expression-texts per output column; Aliases is parallel (empty string = no AS clause). The number of rows is always 1 in this seed; a future VALUES (…), (…) literal table would extend to multi-row. Java equivalent: a ConstantExpression flowing through LogicalProjectionExpression.

func NewValues

func NewValues(rows, aliases []string) *LogicalValues

NewValues constructs a LogicalValues with per-column expression text + parallel aliases.

func (*LogicalValues) Children

func (*LogicalValues) Children() []LogicalOperator

func (*LogicalValues) Explain

func (v *LogicalValues) 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 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 the 1-based SELECT-list position for a positional key
	// (`ORDER BY <n>`); 0 = not positional. A positional key IS an output
	// ordinal by SQL definition, so the translator bakes it directly to the
	// projection's output slot — no text-rendering round-trip, which
	// diverges for computed items whose canonical source text differs from the
	// baked output spelling.
	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 already structurally bound to
	// the native aggregate row (including a computed tree whose leaves are
	// native ordinals). 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