executor

package
v0.0.0-...-e08b47a Latest Latest
Warning

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

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

Documentation

Overview

Package executor bridges RecordQueryPlan trees (Cascades planner output) and the FDBRecordStore scanning API to produce RecordCursor[QueryResult] streams. Mirrors Java's RecordQueryPlan.executePlan dispatching to FDBRecordStoreBase.scanRecords.

The executor is a standalone visitor (not a method on RecordQueryPlan) to avoid circular dependencies between the plans package and the recordlayer package.

Index

Constants

View Source
const DefaultMaxSortBufferRows = 5_000_000

DefaultMaxSortBufferRows is the maximum number of rows the in-memory sort cursor will materialize before returning an error. Prevents OOM on queries that sort unbounded result sets without LIMIT. Override per cursor via the maxBuf field.

Variables

View Source
var DisablePositionalEmission bool

DisablePositionalEmission, when true, stops the row-birth sites — the oracle REGISTRY: FromStoredRecord, the covering-index cursor, and the RFC-173 Slice 2 ordinal-join births (nljCursor pairBinder/evaluateBound emission sites, flatMapCursor.computeResultLegs) — from emitting the RFC-173 PositionalRow, recreating the pre-Slice-1 NAME model end-to-end: no positional row is born, so the frontier gates (`qr.Positional != nil`) never fire and every producer/consumer runs name resolution + name emission, exactly the pre-flip world. Every NEW positional birth site MUST gate on this flag and be added to this list (the §4 standing obligation). It exists for ONE purpose: the §5 dual-window corpus DIFFERENTIAL (ordinal result == name result row-for-row across the corpus, with enumerated carve-outs), which needs the name model as a live oracle during the dual-representation window (retired with the map side in Slice 4). It is NOT a resolution fallback — reviewer's no-name-fallback rule governs resolution; this suppresses EMISSION, in test builds only. Default false; production pays one bool read per scanned row. Tests that flip it must own the whole test binary phase (no concurrent queries in the other mode).

View Source
var StrictReferenceCheck bool

StrictReferenceCheck, when true, makes filter/projection cursors evaluate QueryResult.Complete rows through a Strict RowEvalContext, so a reference to a name absent from the (complete) row is reported via values.ReportUnresolvedReference instead of silently yielding NULL. It is the RFC-048 W1 invariant's master switch: default false (production is untouched and pays nothing), turned on by tests to prove no code path emits an unresolved reference. Set it once at test start, before any query runs.

Functions

func EvaluateScalarSubquery

func EvaluateScalarSubquery(
	ctx context.Context,
	plan plans.RecordQueryPlan,
	store *recordlayer.FDBRecordStore,
	evalCtx *EvaluationContext,
	props recordlayer.ExecuteProperties,
) (any, error)

EvaluateScalarSubquery executes a scalar subquery plan and returns its single scalar result. SQL standard semantics:

  • Exactly one column (else error)
  • At most one row (else 21000 cardinality violation)
  • Zero rows → nil (SQL NULL)

Used by the Cascades executor to pre-evaluate uncorrelated scalar subqueries before running the outer plan.

func ExecutePlan

ExecutePlan executes a RecordQueryPlan tree against a store, returning a cursor over the results. Recursive — child plans are executed first, then the parent operator is applied.

func OptimizeCoveringIndexScans

func OptimizeCoveringIndexScans(plan plans.RecordQueryPlan) plans.RecordQueryPlan

NOT YET WIRED INTO THE QUERY PIPELINE.

OptimizeCoveringIndexScans walks a physical plan tree and marks index scans as covering when the plan above them only references columns available from the index. This is a post-extraction optimization pass that avoids the per-row LoadRecord() call.

STATUS: skeleton only. The filter cases collect referenced columns but do NOT recurse into children — the most common covering scenario (Filter → IndexScan) is not yet handled. The skeleton compiles and passes tests but produces no optimization. Wire this in after implementing recursive plan-tree walking with column reference propagation.

The optimization is conservative: it only fires when ALL referenced columns in filters and projections above the index scan are present in the index's column list. If any column is missing, the scan stays non-covering (correctness over performance).

func SetNameModelOracle

func SetNameModelOracle(v bool)

SetNameModelOracle flips BOTH §5 name-model oracle globals together: DisablePositionalEmission (suppresses the positional row at every birth site) and values.OracleBakedNameFallback (lets BAKED FieldValues — RFC-173 S2+ eager ordinal nodes, whose name reads are otherwise loud errors — resolve by display name against the name-keyed rows the suppression leaves behind). The two flags share ONE meaning — "the process is running the pre-RFC-173 name model" — so this setter is the only sanctioned write site (review hardening: no harness can flip one without the other). Test-only; callers own the whole test-binary phase. Retires with the name map in Slice 4.

Types

type AggregateTypeMismatchError

type AggregateTypeMismatchError struct {
	Message string
}

AggregateTypeMismatchError is returned when MIN or MAX is applied to a non-numeric column. Java's fdb-relational rejects this with "VerifyException: unable to encapsulate aggregate operation due to type mismatch(es)" — the function registry only installs numeric MIN/MAX overloads.

func (*AggregateTypeMismatchError) Error

type ColumnDef

type ColumnDef struct {
	Name     string // key for datum map lookup
	Label    string // display name (alias); empty means use Name
	TypeName string // JDBC type name: BIGINT, STRING, DOUBLE, etc.
	Nullable int    // api.ColumnNoNulls / ColumnNullable / ColumnNullableUnknown
}

ColumnDef describes one column in the result set.

type EvaluationContext

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

EvaluationContext holds runtime bindings for plan execution: parameter values, correlation bindings (for correlated subqueries), scalar subquery results, and any mutable state that plan nodes share. Mirrors Java's EvaluationContext.

func EmptyEvaluationContext

func EmptyEvaluationContext() *EvaluationContext

EmptyEvaluationContext returns a context with no bindings.

func (*EvaluationContext) BindParameter

func (ec *EvaluationContext) BindParameter(ordinal int, name string) (any, bool)

BindParameter implements values.ParameterBinder. Ordinal is 1-based; named parameters are not yet supported.

func (*EvaluationContext) GetBinding

func (ec *EvaluationContext) GetBinding(id values.CorrelationIdentifier) (any, bool)

GetBinding retrieves a correlation binding.

func (*EvaluationContext) GetCorrelationBinding

func (ec *EvaluationContext) GetCorrelationBinding(id values.CorrelationIdentifier) (any, bool)

GetCorrelationBinding implements values.CorrelationBinder so that QuantifiedObjectValue can resolve correlated rows during scan comparison evaluation in the FlatMap execution path.

func (*EvaluationContext) GetOrCreateTempTable

GetOrCreateTempTable returns the TempTable at the given alias, creating one if it doesn't exist. Mutates ec.bindings directly (intentional — temp tables are shared mutable state across the execution, not copy-on-write like WithBinding). Callers must ensure this is called on the root context, not on a WithBinding copy.

st is the statement's ExecuteState (RFC-130) charged when a temp table is freshly created here; an already-bound temp table keeps its original state (it was minted with the same statement's state). Callers pass props.State.

func (*EvaluationContext) RowContext

func (ec *EvaluationContext) RowContext(datum map[string]any) *values.RowEvalContext

RowContext returns a RowEvalContext combining a datum map with this context's parameter bindings and scalar subquery results. Used when evaluating expressions that mix field references, prepared-statement parameters, and scalar subquery references.

func (*EvaluationContext) RowContextPositional

func (ec *EvaluationContext) RowContextPositional(pos values.OrdinalRow) *values.RowEvalContext

RowContextPositional returns a RowEvalContext whose authoritative row is the RFC-173 Slice 1 ordinal-model positional row (resolved by ordinal, no name-map fallback), combined with this context's parameter bindings, correlation bindings, and scalar subquery results. Use it on the non-join frontier when a param / scalar subquery / outer correlation is in play; when none is, flow the bare OrdinalRow directly. An outer correlation resolves via Correlations first; only the (unbound) frontier quantifier reference falls to the positional row.

func (*EvaluationContext) RowContextStrict

func (ec *EvaluationContext) RowContextStrict(datum map[string]any) *values.RowEvalContext

RowContextStrict is RowContext with the RFC-048 W1 unresolved-reference check armed. Use it only for rows whose key set is complete (QueryResult .Complete) — see RowEvalContext.Strict. Callers gate on StrictReferenceCheck so production keeps the cheaper bare-map fast path.

func (*EvaluationContext) WithBinding

WithBinding returns a shallow copy with an additional binding.

func (*EvaluationContext) WithParams

func (ec *EvaluationContext) WithParams(params []any) *EvaluationContext

WithParams returns a copy with prepared-statement parameter bindings. Params is 0-indexed; ParameterValue ordinals are 1-based.

func (*EvaluationContext) WithScalarSubqueries

func (ec *EvaluationContext) WithScalarSubqueries(results map[values.CorrelationIdentifier]any) *EvaluationContext

WithScalarSubqueries returns a copy with pre-evaluated scalar subquery results bound by correlation alias.

type MaterializationLimitExceededError

type MaterializationLimitExceededError struct {
	Limit   int
	Context string
}

MaterializationLimitExceededError is returned when an operator tries to buffer more rows in memory than the configured materialization limit.

func (*MaterializationLimitExceededError) Error

type NumericRangeOverflowError

type NumericRangeOverflowError struct {
	Value    any
	Column   string
	TypeName string
}

func (*NumericRangeOverflowError) Error

func (e *NumericRangeOverflowError) Error() string

type PositionalRow

type PositionalRow struct {
	// Type gives each slot its name and type; Slots[i] is the value of the field
	// at ordinal i. len(Slots) == len(Type.Fields) for a well-formed row.
	Type  *values.RecordType
	Slots []any
}

PositionalRow is the RFC-173 P2 typed positional runtime row: field values indexed by ORDINAL, paired with the RecordType that names and types each slot. It is the ordinal-model counterpart to the legacy name-keyed map[string]any (QueryResult.Datum).

During the migration it is emitted ALONGSIDE the name-keyed map (dark/dual), with a field-for-field shadow assert (a later P2 increment), until the ordinal model becomes authoritative in Slice 1+ and the name map is retired. Positional access (Slots[ordinal]) mirrors Java's MessageHelpers.getFieldValueForFieldOrdinals; name access resolves the ordinal via RecordType.FieldIndex (P1's sound list-position lookup), so the two representations agree by construction on a well-formed row — that agreement is what the shadow assert pins.

func NewPositionalRow

func NewPositionalRow(typ *values.RecordType) *PositionalRow

NewPositionalRow builds a row for typ with every slot nil (SQL NULL). Slots is sized to the field count so Get/Set are position-safe. A nil typ yields an empty row (zero slots).

func (*PositionalRow) Get

func (r *PositionalRow) Get(ordinal int) (any, bool)

Get returns the value at the given ordinal plus an in-range flag. Nil-safe.

func (*PositionalRow) GetByName

func (r *PositionalRow) GetByName(name string) (any, bool)

GetByName resolves name -> ordinal via the row's RecordType (FieldIndex, P1's sound list-position lookup) then reads that slot. This is the bridge the P2 shadow assert uses to compare positional access against the legacy name-keyed map[string]any. Returns (nil, false) for an unknown name, an anonymous field (empty name never matches), or a nil row/type.

func (*PositionalRow) Set

func (r *PositionalRow) Set(ordinal int, v any) bool

Set writes v at the given ordinal, returning false (no-op) if out of range.

func (*PositionalRow) TypeNames

func (r *PositionalRow) TypeNames() []string

TypeNames returns the row type's column names in ordinal order — diagnostics for values.OrdinalResolutionError (via an optional-interface assertion), so a loud resolution miss reports what the row actually carried.

type QueryResult

type QueryResult struct {
	Datum any
	// Positional is the RFC-173 ordinal-model sibling of Datum: the same row as
	// a typed PositionalRow (field values indexed by ordinal). Non-nil marks the
	// row as being on the NON-JOIN FRONTIER (scans, covering scans, projection/
	// map over the frontier emit it; join producers mergeRows/qualifyOuterRow do
	// NOT), and since Slice 1 it is what FieldValue resolution READS there —
	// authoritative, by ordinal, loud on a miss. The name-keyed Datum is still
	// emitted alongside for coexistence (downstream name-model consumers, final
	// materialization) until Slice 4 retires it; a shadow test pins that the two
	// mirror each other field-for-field.
	Positional *PositionalRow
	Record     *recordlayer.FDBStoredRecord[proto.Message]
	PrimaryKey tuple.Tuple
	// Complete marks a computed/synthetic row whose Datum key set is
	// authoritative — every legal column is present (nil-valued for SQL NULL),
	// with no proto-style optional-field omissions. Set by aggregate output
	// (finalizeGroup/emptyScalarResult). Consumers use it to enable the RFC-048
	// W1 strict unresolved-reference check: against such a row, a referenced
	// name that is absent is a bug, not a NULL. Raw stored-record rows
	// (FromStoredRecord) leave it false, because they legitimately omit unset
	// optional fields.
	Complete bool
}

QueryResult is the row type flowing through plan execution cursors. Wraps a datum (the computed/flowed row), an optional stored record (when the row originated from a scan), and an optional primary key. Mirrors Java's QueryResult.

func CollectAll

func CollectAll(ctx context.Context, cursor recordlayer.RecordCursor[QueryResult]) ([]QueryResult, error)

CollectAll drains a cursor into a slice.

func CollectAllBounded

func CollectAllBounded(ctx context.Context, cursor recordlayer.RecordCursor[QueryResult], st *recordlayer.ExecuteState, limit int, opName string) ([]QueryResult, error)

CollectAllBounded drains a cursor into a slice through an accounted boundedBuffer (RFC-130): every row is charged against the statement-wide memory byte budget (st) AND counted against the row-count materialization limit, so a missed accumulation site is impossible — the buffer cannot exist without the accountant. st is the always-present statement ExecuteState (props.State); a nil/zero-limit st makes the byte charge a no-op while the row-count cap still applies. Returns MaterializationLimitExceededError on the row cap and MemoryLimitExceededError (→ 54F01) on the byte budget.

func FromStoredRecord

func FromStoredRecord(rec *recordlayer.FDBStoredRecord[proto.Message]) QueryResult

FromStoredRecord builds a QueryResult from a stored record. The datum is set to a map[string]any extracted from the proto message's fields, keyed by UPPER-case field name (matching the identifier folding convention).

type RecordLayerResultSet

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

RecordLayerResultSet wraps a RecordCursor[QueryResult] and implements api.ResultSet. Mirrors Java's RecordLayerResultSet: Next() advances the cursor, typed accessors read from the current row's datum map.

Column metadata is provided at construction time (derived from the plan's result type or the schema catalog). Column accessors are 1-indexed per JDBC convention.

func NewRecordLayerResultSet

func NewRecordLayerResultSet(
	ctx context.Context,
	cursor recordlayer.RecordCursor[QueryResult],
	columns []ColumnDef,
) *RecordLayerResultSet

NewRecordLayerResultSet constructs a ResultSet from an executor cursor and column definitions.

func (*RecordLayerResultSet) Boolean

func (rs *RecordLayerResultSet) Boolean(columnIndex int) (bool, error)

func (*RecordLayerResultSet) BooleanByName

func (rs *RecordLayerResultSet) BooleanByName(name string) (bool, error)

func (*RecordLayerResultSet) Bytes

func (rs *RecordLayerResultSet) Bytes(columnIndex int) ([]byte, error)

func (*RecordLayerResultSet) BytesByName

func (rs *RecordLayerResultSet) BytesByName(name string) ([]byte, error)

func (*RecordLayerResultSet) Close

func (rs *RecordLayerResultSet) Close() error

func (*RecordLayerResultSet) Continuation

func (rs *RecordLayerResultSet) Continuation() (api.Continuation, error)

func (*RecordLayerResultSet) Double

func (rs *RecordLayerResultSet) Double(columnIndex int) (float64, error)

func (*RecordLayerResultSet) Err

func (rs *RecordLayerResultSet) Err() error

func (*RecordLayerResultSet) Float

func (rs *RecordLayerResultSet) Float(columnIndex int) (float32, error)

func (*RecordLayerResultSet) GetContinuation

GetContinuation returns the raw cursor continuation from the last Next() call. Used by the paginating execution loop to resume across FDB transactions.

func (*RecordLayerResultSet) GetNoNextReason

func (rs *RecordLayerResultSet) GetNoNextReason() recordlayer.NoNextReason

GetNoNextReason returns the NoNextReason from the last Next() call. This is the AUTHORITATIVE exhaustion signal (SourceExhausted ⇔ end-of-results), and distinguishes a non-terminal out-of-band stop (scan/time/byte limit) from a clean ReturnLimitReached/exhaustion when the continuation has nil bytes — see RFC-127 (Java carries noNextReason as a first-class field for exactly this; its nil-byte START continuation is otherwise ambiguous with end).

func (*RecordLayerResultSet) Long

func (rs *RecordLayerResultSet) Long(columnIndex int) (int64, error)

func (*RecordLayerResultSet) LongByName

func (rs *RecordLayerResultSet) LongByName(name string) (int64, error)

func (*RecordLayerResultSet) MetaData

func (*RecordLayerResultSet) Next

func (rs *RecordLayerResultSet) Next() bool

func (*RecordLayerResultSet) Object

func (rs *RecordLayerResultSet) Object(columnIndex int) (any, error)

func (*RecordLayerResultSet) ObjectByName

func (rs *RecordLayerResultSet) ObjectByName(name string) (any, error)

func (*RecordLayerResultSet) String

func (rs *RecordLayerResultSet) String(columnIndex int) (string, error)

func (*RecordLayerResultSet) StringByName

func (rs *RecordLayerResultSet) StringByName(name string) (string, error)

func (*RecordLayerResultSet) WasNull

func (rs *RecordLayerResultSet) WasNull() bool

type RecursiveCTEDepthExceededError

type RecursiveCTEDepthExceededError struct {
	MaxDepth int
}

func (*RecursiveCTEDepthExceededError) Error

type SortBufferExceededError

type SortBufferExceededError struct {
	Rows  int
	Limit int
}

SortBufferExceededError is returned when an in-memory sort materializes more rows than the configured limit. Prevents OOM on unbounded ORDER BY without LIMIT.

func (*SortBufferExceededError) Error

func (e *SortBufferExceededError) Error() string

type SumOverflowError

type SumOverflowError struct{}

func (*SumOverflowError) Error

func (*SumOverflowError) Error() string

type TempTable

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

TempTable is an in-memory list of QueryResult used by TempTableInsertPlan and TempTableScanPlan. Mirrors Java's com.apple.foundationdb.record.TempTable.

RFC-130: a TempTable is a cardinality-growing buffer — the recursive-CTE per-level working set (ping-ponged scan/insert tables) and the TempTableInsertPlan target both accumulate into it, separate from the CollectAllBounded per-level materialization. It carries the statement's always-present *ExecuteState (st) and charges each appended row's byte estimate in Add. The pre-existing sync.Mutex is defensive (the zero- goroutine executor invariant makes it currently moot); charging under the lock is correct regardless — if the executor ever goes concurrent the pinned package_invariant_test fires and ChargeMemory moves to atomic.

func NewTempTable

func NewTempTable() *TempTable

NewTempTable creates an empty temp table with no memory budget. Used by internal call sites that have no statement ExecuteState in scope (and by tests); production statement paths use NewTempTableWithState so the statement-wide memory budget covers the temp-table working set.

func NewTempTableWithState

func NewTempTableWithState(st *recordlayer.ExecuteState) *TempTable

NewTempTableWithState creates an empty temp table that charges its rows against the supplied statement ExecuteState (RFC-130). st is the always- present statement state; a nil/zero-limit st makes the charge a no-op.

func (*TempTable) Add

func (tt *TempTable) Add(qr QueryResult) error

Add appends a QueryResult to the temp table, charging its byte estimate against the statement memory budget first (RFC-130). On a budget breach the row is NOT appended and the *MemoryLimitExceededError is returned.

func (*TempTable) Clear

func (tt *TempTable) Clear()

Clear removes all entries from the temp table.

func (*TempTable) GetList

func (tt *TempTable) GetList() []QueryResult

GetList returns a snapshot of the temp table contents.

func (*TempTable) ReplaceList

func (tt *TempTable) ReplaceList(rows []QueryResult)

ReplaceList replaces the temp-table contents with rows that have ALREADY been charged against the statement memory budget — it does NOT re-charge. Used by the recursive-CTE DISTINCT path, which filters the rows the recursive plan already inserted (and charged via Add) down to the non-duplicate subset; re-charging them through Add would double-count the same resident rows. memUsed is monotonic, so the rows dropped by the filter stay charged (a conservative ceiling) — that is intentional and correct.

Jump to

Keyboard shortcuts

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