Documentation
¶
Overview ¶
Package query defines the planner/plan seam between the SQL frontend (database/sql driver, future gRPC server, REPL) and the SQL execution engine. Mirrors the role of Java's fdb-relational-core/recordlayer/query/Plan.java + AbstractEmbeddedStatement.executeInternal's 40-line dispatch.
A frontend holds a Generator (typically one per connection/session). For every SQL string:
plan, err := gen.Plan(ctx, sql) // parse + analyze + plan result, err := plan.Execute(ctx) // run against the bound session
No frontend code touches the execution engine directly. All backend shapes live behind Generator + Plan and swap without changing callers.
Introduced in RFC 021 to let a Cascades Generator be swapped in behind this boundary. That migration is complete: Cascades is the sole Generator for queries and DML, and the original naive per-shape executor was removed in RFC-145 (only `execStatement`, for DDL, remains).
Index ¶
- func CheckBuriedExistentialPredicate(root *expressions.Reference) error
- func CheckProjectedExistsFolded(root *expressions.Reference) error
- func FindUnsupportedFunction(op logical.LogicalOperator) string
- func TranslateToCascades(op logical.LogicalOperator) *expressions.Reference
- type BuriedExistentialPredicateError
- type Generator
- type MultiPlan
- type Plan
- type PlanFunc
- type Result
- type ScalarSubqueryPlan
- type UnfoldedProjectedExistsError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CheckBuriedExistentialPredicate ¶
func CheckBuriedExistentialPredicate(root *expressions.Reference) error
CheckBuriedExistentialPredicate is the RFC-141 R4 convergence backstop for WHERE EXISTS (P1a). Given the root Reference of a freshly translated (pre-planning) plan tree, it returns a *BuriedExistentialPredicateError when any predicate-bearing expression carries an existential predicate that is NOT in a directly-handled position — i.e. an ExistentialValuePredicate buried inside a predicate that is neither the top-level existential nor a single-NOT-wrapped existential.
EXISTS can appear at any depth in a WHERE predicate tree. Only a top-level (or single-NOT-wrapped) existential is the semi-join shape the NLJ rule lowers to a FirstOrDefault + residual filter; everything else falls into the regular bucket where the empty FOD's NULL default is never dropped and every outer row passes. Rather than point-handle each wrapper shape (which never converges), this structurally DETECTS any buried existential and rejects cleanly.
Per predicate-bearing expression (SelectExpression / LogicalFilterExpression), each top-level predicate is classified:
- IsExistentialPredicate(p) → directly handled (bare EXISTS). OK.
- IsNotExistentialPredicate(p) → directly handled (single-NOT NOT-EXISTS). OK.
- otherwise, if p's subtree CONTAINS an ExistentialValuePredicate anywhere (predicates.ContainsExistentialPredicate) → buried → REJECT.
Returns nil when every existential predicate is in a directly-handled position (the supported WHERE-EXISTS / NOT-EXISTS shapes, including alongside ordinary non-existential conjuncts, multi-table inners, and projected EXISTS).
func CheckProjectedExistsFolded ¶
func CheckProjectedExistsFolded(root *expressions.Reference) error
CheckProjectedExistsFolded is the RFC-141 §8 safety guard. Given the root Reference of a freshly translated (pre-planning) plan tree, it returns an *UnfoldedProjectedExistsError when any ExistsValue in the tree is positioned where its existential binding will NOT be live at eval time — the long-tail silent-wrong-result the projected-EXISTS fold's structural pattern-matching could otherwise let through.
Mechanism (two passes over the expression tree, mirroring Java's structural invariant that an ExistsValue is only correct inside the resultValue of the SelectExpression whose existential quantifier it reads):
Build an ownership map: for every SelectExpression in the tree, for each existential quantifier it declares, record alias -> that SelectExpression. An existential alias is declared by exactly one SelectExpression (the one the translator attaches the NamedExistentialQuantifier to).
For every expression in the tree, inspect the Value(s) it emits in its own scope (its resultValue, or — for a LogicalProjectionExpression whose resultValue is the inner's flowed object, not the projection — its projected values) and find every ExistsValue. For each, resolve the existential alias it reads (its QuantifiedObjectValue child's correlation) and require that the emitting expression IS the SelectExpression that owns that existential quantifier. If it is not (or no SelectExpression owns the alias at all), the binding is dead at this position -> reject.
Returns nil when every ExistsValue is correctly folded (the supported shapes: projected EXISTS / NOT EXISTS, correlated / non-correlated, alongside ORDER BY / LIMIT / a scalar subquery, and projected EXISTS over a JOIN in FROM — all fold the projection into the existential SelectExpression's result value).
func FindUnsupportedFunction ¶
func FindUnsupportedFunction(op logical.LogicalOperator) string
FindUnsupportedFunction walks the logical plan tree and returns the name of the first ScalarFunctionValue that isn't in the supported set. Returns "" if all functions are supported.
func TranslateToCascades ¶
func TranslateToCascades(op logical.LogicalOperator) *expressions.Reference
TranslateToCascades converts a logical.LogicalOperator tree into a cascades RelationalExpression tree rooted in a Reference. This is the bridge between the SQL parser's logical plan and the Cascades optimizer.
Returns the root Reference suitable for passing to Planner.Plan(). Returns nil if the operator tree contains shapes that can't be translated (unsupported operators fall through to nil).
Types ¶
type BuriedExistentialPredicateError ¶
type BuriedExistentialPredicateError struct{}
BuriedExistentialPredicateError signals that a translated plan tree carries a WHERE existential predicate (an ExistentialValuePredicate) buried under a wrapper that is NOT a directly-handled semi-join shape — i.e. it is neither a top-level existential nor a single-NOT-wrapped existential. Such a predicate falls into the regular-predicate bucket of the NLJ rule's implementExistentialSelect / implementJoinWithExistential, where the empty FirstOrDefault inner emits its NULL default that no residual filter removes, so EVERY outer row silently passes (a silent wrong result). The production path rejects such a plan with ErrCodeUnsupportedQuery rather than ship wrong rows (RFC-141 R4 convergence backstop, P1a).
The cleanly-rejected shapes are the wrapped-WHERE-EXISTS long tail: any existential reachable only through a wrapper the rule's IsExistentialPredicate / IsNotExistentialPredicate routing does not recognise — `WHERE NOT (NOT EXISTS(...))`, `WHERE EXISTS(...) OR p`, deeper AND/OR/NOT nesting. A plain `WHERE EXISTS` / `WHERE NOT EXISTS` (top-level or single-NOT-wrapped) is the directly-handled shape and is NOT rejected.
func (*BuriedExistentialPredicateError) Error ¶
func (e *BuriedExistentialPredicateError) Error() string
type Generator ¶
type Generator interface {
// Plan parses, semantically analyzes, and plans a SQL string.
// Errors are typed via pkg/relational/api — callers should not
// need to wrap them.
Plan(ctx context.Context, sql string) (Plan, error)
}
Generator builds executable Plans from SQL strings. One Generator per logical session — it carries the session's catalog handle, current schema, and options.
type MultiPlan ¶
type MultiPlan struct {
Plans []Plan
}
MultiPlan wraps a sequence of Plans produced from a multi-statement SQL text (semicolon-separated). Execute runs them in order and returns a Result holding the SUM of RowsAffected (for Exec-style callers); the last Plan's Rows if any are exposed via Results(). This matches today's EmbeddedConnection.ExecContext aggregation: total modified rows across the batch, no intermediate result sets bubbled up.
func (*MultiPlan) Execute ¶
Execute runs every child Plan in order, short-circuiting on the first error. Returns the aggregate RowsAffected; Rows is nil (multi-statement Exec doesn't expose intermediate row sets).
func (*MultiPlan) IsUpdate ¶
IsUpdate returns true iff every child Plan is an update plan. A mixed batch (e.g. DDL + SELECT) is treated as non-update so the last Rows-producing plan would be reachable via a future Results() iteration. In practice today's driver doesn't send mixed batches through Exec.
type Plan ¶
type Plan interface {
Execute(ctx context.Context) (Result, error)
IsUpdate() bool
Explain() string
}
Plan is a ready-to-execute representation of a SQL statement. One Plan per SQL statement; a multi-statement SQL text produces a MultiPlan (see below).
Execute returns a Result whose concrete shape depends on the statement kind:
- SELECT / SHOW / read-only: Result.Rows is non-nil; Result. RowsAffected is zero.
- INSERT / UPDATE / DELETE: Result.RowsAffected counts the modified rows; Result.Rows is nil.
- DDL (CREATE / DROP): Result.Rows is nil; Result.RowsAffected is zero.
IsUpdate distinguishes mutation plans so the driver knows whether to return driver.Rows vs driver.Result at the boundary. Matches Java's Plan.isUpdatePlan().
Explain returns a textual description of the plan. Today the naive Generator returns the canonical SQL text; future Cascades plans will return a plan tree that is stable enough for the RFC-022 §4.-1 plan-equivalence harness to diff against Java's. Empty string is a valid value for plans that cannot produce a useful description.
type PlanFunc ¶
type PlanFunc struct {
ExecFn func(ctx context.Context) (Result, error)
UpdateFn func() bool
ExplainFn func() string
}
PlanFunc is a convenience adapter: a Plan whose Execute delegates to a closure. Used to wrap non-Cascades code paths (e.g. the executor-free INFORMATION_SCHEMA system-table handler and the explain-only renderer) as Plan implementations without duplicating logic.
Post-Phase-1c this adapter becomes obsolete — physical operator types implement Plan directly. Kept here during the transition so the frontend seam is stable before the executor is split.
type Result ¶
type Result struct {
// Rows is non-nil for SELECT-shaped plans. Nil for DML/DDL.
Rows driver.Rows
// RowsAffected is the update count for DML. Zero for SELECT/DDL.
RowsAffected int64
}
Result is the output of a Plan execution. Exactly one of Rows / RowsAffected carries the payload; which one is set is determined by Plan.IsUpdate().
type ScalarSubqueryPlan ¶
type ScalarSubqueryPlan struct {
Alias values.CorrelationIdentifier
Plan logical.LogicalOperator
}
ScalarSubqueryPlan pairs a correlation alias with a logical operator tree for a scalar subquery. Collected during translation and passed to the executor for pre-evaluation.
func TranslateToCascadesWithError ¶
func TranslateToCascadesWithError(op logical.LogicalOperator, md *recordlayer.RecordMetaData) (*expressions.Reference, []ScalarSubqueryPlan, error)
TranslateToCascadesWithError is TranslateToCascadesWithSubqueries plus an explicit translation error. A non-nil error carries a specific SQL error code (e.g. ErrCodeWrongObjectType for AT-ordinality on a non-array source, RFC-142) that a bare nil ref (untranslatable → UNSUPPORTED_QUERY) cannot. The caller surfaces it verbatim instead of the generic "could not plan".
func TranslateToCascadesWithSubqueries ¶
func TranslateToCascadesWithSubqueries(op logical.LogicalOperator, md *recordlayer.RecordMetaData) (*expressions.Reference, []ScalarSubqueryPlan)
TranslateToCascadesWithSubqueries is like TranslateToCascades but also returns any scalar subquery plans collected during translation. These must be planned independently and pre-evaluated by the executor before running the main plan.
md carries the record metadata used to source join-leg columns when building the source-anchored join result value (RFC-077 7.6). Pass nil to keep the legacy opaque-seed behavior — the no-md callers today are TranslateToCascades (used for scalar-subquery translation, which has no md in scope) and DML translation. (Tests pass real md where they exercise anchoring.) The scan leaf is NEVER typed from md (it stays Type.AnyRecord/UnknownType, matching Java — see RFC-077 v3 amendment); md is consulted only to enumerate a leg's columns for the anchored RecordConstructor.
type UnfoldedProjectedExistsError ¶
type UnfoldedProjectedExistsError struct {
// Alias is the existential correlation the offending ExistsValue reads.
Alias values.CorrelationIdentifier
}
UnfoldedProjectedExistsError signals that a translated plan tree carries a projected ExistsValue that is NOT folded into the result value of the SelectExpression owning its existential quantifier — i.e. the boolean would be evaluated ABOVE the FlatMap, without the existential binding live, and ExistsValue.Evaluate would silently return false (or, via a QOV fallback, phantom-true). That is a silent wrong result, so the production path rejects such a plan with ErrCodeUnsupportedQuery rather than shipping wrong rows (RFC-141 §8 safety guard).
The cleanly-rejected shapes are the projected-EXISTS long tail the fold does not yet recognize (e.g. multiple existential quantifiers in one query, or a projection over a shape findExistsFilterUnderUnaryChain cannot fold through). They are correctness-preserving rejections, never wrong answers.
func (*UnfoldedProjectedExistsError) Error ¶
func (e *UnfoldedProjectedExistsError) Error() string
Directories
¶
| Path | Synopsis |
|---|---|
|
Package expr is the parse-tree → values.Value resolver.
|
Package expr is the parse-tree → values.Value resolver. |
|
Package logical holds the Phase 3 (TODO.md §"Phase 3 — Semantic analysis") logical-operator hierarchy.
|
Package logical holds the Phase 3 (TODO.md §"Phase 3 — Semantic analysis") logical-operator hierarchy. |
|
Package plangen converts the embedded engine's LogicalOperator hierarchy into the Cascades-side RelationalExpression hierarchy.
|
Package plangen converts the embedded engine's LogicalOperator hierarchy into the Cascades-side RelationalExpression hierarchy. |
|
Package semantic is the Go port of Java's `com.apple.foundationdb.relational.recordlayer.query.SemanticAnalyzer` plus related Identifier / Expression / reference-resolution helpers.
|
Package semantic is the Go port of Java's `com.apple.foundationdb.relational.recordlayer.query.SemanticAnalyzer` plus related Identifier / Expression / reference-resolution helpers. |
|
rlcatalog
Package rlcatalog adapts the Record Layer's `RecordMetaData` into the `semantic.Catalog` interface.
|
Package rlcatalog adapts the Record Layer's `RecordMetaData` into the `semantic.Catalog` interface. |