executor

package
v0.0.0-...-239813c Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 32 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

This section is empty.

Functions

func AssertLegColumnProvenanceCensus

func AssertLegColumnProvenanceCensus(w io.Writer, floors *LegColumnProvenanceFloors) bool

AssertLegColumnProvenanceCensus checks the census's partition, its one zero and its population floors, and reports whether it failed.

The partition is the point: every share this census prints is a share of Calls, and a share only means something if the shares add up. The zero is DottedHitIdentityDiverged — a leg whose text and whose stated identity name different things, resolved by the text. That is not a residue to shrink, it is a contradiction: two keys for one leg, disagreeing, with only the weaker one consulted.

floors is nil when the run is NARROWED, exactly as its siblings do it: the floors describe a whole-suite population, and a -test.run selecting tests that never adapt a leg row reaches this reader zero times. The partition and the zero still run — they hold over any population, which is precisely why they are not a proof on their own.

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 FormatLegColumnProvenanceCensus

func FormatLegColumnProvenanceCensus() string

FormatLegColumnProvenanceCensus renders the census for a harness to log.

func LegColumnProvenanceCensus

func LegColumnProvenanceCensus() (legColumnProvenanceCounters, []string)

LegColumnProvenanceCensus reports the counters and the retained witnesses.

func LegColumnProvenanceDottedNames

func LegColumnProvenanceDottedNames() []string

LegColumnProvenanceDottedNames returns the distinct COLUMN NAMES the dotted arm ANSWERED on — the qualified labels, not the witness prose.

It exists so a cross-population claim can be checked rather than eyeballed. The retirement condition booked for this reader is "the dotted-hit count goes to 0", booked against converting a mint in the TRANSLATOR; whether that is reachable depends on whether the names that mint produces are these names. Comparing the two sets needs both as data, and this census's witnesses are sentences.

func PositionalTypeForDescriptor

func PositionalTypeForDescriptor(desc protoreflect.MessageDescriptor) *values.RecordType

PositionalTypeForDescriptor returns the LOGICAL RecordType for a message descriptor — one field per descriptor field in declaration order (the field's ordinal), UPPER-cased name, declared column type. THE single authority for a stored record's logical row shape: every physical access path that serves a stored record's columns (base scan rows via protoToPositional, covering-index rows via coveringIndexCursor) MUST shape its rows by this type, so a plan-time LOGICAL ordinal reads the same slot on every path (mirrors Java's IndexKeyValueToPartialRecord, which reconstructs a descriptor-shaped partial record for exactly this reason). Cached per descriptor (see positionalTypeCache).

func PositionalTypeForRecordLayout

func PositionalTypeForRecordLayout(desc protoreflect.MessageDescriptor, storeRowVersions bool) *values.RecordType

PositionalTypeForRecordLayout is the metadata-aware row-shape authority: the descriptor's positional type, extended with the __ROW_VERSION pseudo-slot when the store's metadata stores row versions and the descriptor does not declare a REAL field of that name (real-column-wins, Java Type.Record.addPseudoFields, Type.java:2358-2368). Every planner- or runtime-facing derivation of a stored record's row layout must go through this so plan-time ordinals and runtime slots agree on version-storing stores.

func RowValue

func RowValue(qr QueryResult) any

RowValue returns a QueryResult's row in name-keyed form: a bare scalar for a 1-slot `_0` row (a non-record UNNEST element), else a name->value map (duplicate output names collapse last-wins). EXPORTED for external test/differential consumers only; production code reads the PositionalRow by ordinal. Nil-safe.

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 // output column name (positional slot / by-name lookup key)
	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) GetQuantifiedBinding

func (ec *EvaluationContext) GetQuantifiedBinding(view values.QuantifiedObjectValue) (any, bool, error)

GetQuantifiedBinding implements the exact QOV binder. When this context has exact declarations for a correlation, the requested flowed type must select one of them; a same-spelled foreign type cannot fall back to the legacy map.

func (*EvaluationContext) IsExplicitNullQuantifiedBinding

func (ec *EvaluationContext) IsExplicitNullQuantifiedBinding(view values.QuantifiedObjectValue) (bool, error)

IsExplicitNullQuantifiedBinding implements the positive absence-proof channel consumed by values' strict ordinal binder. Exact type validation is identical to GetQuantifiedBinding, so a same-spelled foreign declaration cannot borrow another object's FirstOrDefault absence.

func (*EvaluationContext) ReleaseAllTempTableCharges

func (ec *EvaluationContext) ReleaseAllTempTableCharges()

ReleaseAllTempTableCharges releases every temp table bound in THIS context's map back to the statement budget — the teardown half of the recursion's live-bytes accounting (the DFS accumulator is minted into the shared bindings map by GetOrCreateTempTable, so the recursion cannot name it directly). Idempotent per table (the tally zeroes). A nested recursion sharing the map would release an outer recursion's still-live tables a step early — an accepted under-account bounded by the outer working set; the outer teardown's release is then a harmless no-op.

func (*EvaluationContext) RowContext

func (ec *EvaluationContext) RowContext() *values.RowEvalContext

RowContext returns a binding-only RowEvalContext — this context's parameter bindings, correlation bindings, and scalar subquery results, with NO frontier row. Used when evaluating expressions that reference only params / correlations / scalar subqueries; a row-bearing context flows through RowContextPositional.

func (*EvaluationContext) RowContextPositional

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

RowContextPositional returns a RowEvalContext whose authoritative row is the 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) Scratch

func (ec *EvaluationContext) Scratch() *ExecutionScratch

Scratch returns the statement's execution scratch, or nil when the execution carries none.

func (*EvaluationContext) StatementNow

func (ec *EvaluationContext) StatementNow() time.Time

StatementNow implements values.StatementClock: the one instant every CURRENT_TIMESTAMP-family reference in this statement observes. An unstamped context degrades to the wall clock — that is the per-row drift the stamp exists to prevent, so statement entry points must stamp via WithStatementTime.

func (*EvaluationContext) WithBinding

WithBinding returns a shallow copy with an additional binding.

func (*EvaluationContext) WithExecutionScratch

func (ec *EvaluationContext) WithExecutionScratch(s *ExecutionScratch) *EvaluationContext

WithExecutionScratch returns a copy carrying the statement's scratch. The statement's paging loop stamps this once and reuses it for every page, which is what lets an operator hand its live resume state to the next page instead of serializing it. Like every other With* copy, it rides all derived contexts.

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. The copy CARRIES scalarSubqueries like every other With* copy — dropping them would make binding ORDER load-bearing (WithScalarSubqueries().WithParams() would silently unbind the subqueries, and unbound is a loud *values.UnboundScalarSubqueryError at row time).

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.

func (*EvaluationContext) WithStatementTime

func (ec *EvaluationContext) WithStatementTime(t time.Time) *EvaluationContext

WithStatementTime returns a copy stamped with the statement-stable CURRENT_TIMESTAMP-family instant. Like every other With* copy, the stamp rides all derived contexts (WithParams, WithBinding, …).

type ExecutionScratch

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

ExecutionScratch is the statement-scoped home for operator resume state that is too large to serialize into every page's continuation.

WHY IT EXISTS. A continuation is normally self-contained: the operator packs whatever it needs to resume into bytes, and the bytes alone rebuild it. That is right when the state is O(1) — the streaming distinct's single previous key, a scan's position. It is quadratic when the state is O(rows already emitted), which is exactly the unordered hash distinct: its seen-set is the operator's whole memory, so page P's continuation would carry every key emitted through page P and a P-page drain would serialize and re-parse O(P^2) keys in total. Measured on a 401-page drain of 400 distinct values, the continuation grew 9 -> 1749 bytes and the drain re-parsed ~350 KB of keys to emit 400 rows.

The state cannot be shrunk: exact dedup over UNORDERED input fundamentally needs the set (an ordered input is the case that needs only the last key, and the planner already routes it to the streaming executor). Java declines to pay either cost — RecordQueryUnorderedPrimaryKeyDistinctPlan.java:100-104 mints `new HashSet<>()` per execution and passes the inner's continuation through untouched, so a duplicate spanning a resume is silently re-admitted. Go does not accept wrong rows, so the set has to survive the page boundary; the only remaining choice is whether it survives THROUGH bytes or BESIDE them. Beside them is the one that is not quadratic.

WHY IT IS SOUND. The bytes are round-tripped by exactly one production caller, the SQL statement's paging loop (cascades_generator.go), which re-executes the same plan against a fresh transaction to respect FDB's 5s bound. Its continuations never escape the statement: the driver rejects statement continuations outright (Go SQL tokens are engine-private and no resume entry point exists). So a statement-scoped side channel has exactly the lifetime the bytes do, and a resume that finds no scratch entry is an impossible state rather than a supported one — it is reported as an error, never deduped against an empty set.

THE INVARIANT THAT MAKES IT SAFE: **executing a page must be idempotent with respect to the scratch.** Lifetime is not the whole story — the same continuation bytes are not merely HELD for the statement, they are EXECUTED more than once. paginatingRows.fetchPage runs its whole body inside the FDB retry loop (runInCapturedTx -> DB.Run -> TransactCtx, fdb/database.go:323), which re-invokes the closure from the UNCHANGED r.continuation on any retryable error; the closure resets r.buf, so the failed attempt's rows are discarded. A scratch that let the failed attempt mutate shared state would therefore lose rows silently — the retry would treat the discarded attempt's values as already emitted. (Reproduced: a mid-drain abort on a 12-value fixture emitted 10, no error. The by-value encoding was immune by construction, because bytes are immutable.) Two rules restore it:

  1. A published set is IMMUTABLE. A resumed cursor never touches the set it adopted; it accumulates its own page's new keys in a private delta and publishes base+delta as a NEW entry. A dying attempt then mutates nothing its retry can see. The base is never COPIED per page — that would restore the quadratic — it is extended in place exactly once, when rule 2 proves no one can reach its earlier form again.

  2. Eviction keys on ADOPT, never on PARK. Reaching token T proves the page that minted T committed and was consumed, so T's predecessor is dead. MINTING T proves nothing: the attempt that minted it can still fail, and its retry resumes from the predecessor it was minted from. (Reproduced: park-time eviction turned a routine retryable error after a completed page into a hard "seen-set not held" failure.) Evicting on adopt keeps the bound at a couple of live entries per operator all the same.

RETIREMENT FOLLOWS NAMEABILITY. An entry must live exactly as long as some continuation the statement still holds can name its token — no longer, and not one moment less. Three earlier designs keyed retirement on a CURSOR LIFECYCLE EVENT instead, and each was wrong for its own reason. They are recorded because the wrong answer is the intuitive one:

  • on PARK: a retry resumes the predecessor the failed attempt was minted from, so minting cannot retire it.
  • on a page-local MARK of the surviving continuation: enclosing continuation objects cache their own bytes, so the final ToBytes need never call down and the mark is missed. It silently dropped rows.
  • on EXHAUSTION: an enclosing operator can retain a child continuation OBJECT past the child's end and serialize it later — aggregateCursor.emitFinal emits its final row carrying a retained earlier inner position (streaming_cursors.go:393-401). A token minted before the inner ended is still named after it.

Object-graph reachability from the surviving continuation is what this WANTED to be, and the code forbids it: sixteen sites wrap a child by serializing it eagerly into a plain BytesContinuation (limitEnvelopeCursor, executor.go:2009, is the one that matters here), so the child object is dropped and no walk — however careful about byte caching — can see through it. Bytes are the only medium that survives, so ADOPTION is the only ground truth available: a page adopts exactly the entries its own continuation named. SweepAfterPage is built on that, at the cost of one page of lag, plus the exact case that needs no lag (an exhausted page names nothing at all).

"A page adopts exactly what its continuation names" has ONE hole, and it is plugged explicitly rather than argued away: a page can FORWARD a name instead of resolving it, handing bytes it received straight back out without ever building the cursor that would adopt them. holdForwardedName is how the forwarding site says so, and the sweep then judges nothing unreachable that page. A forwarded name is a name.

Residency is then bounded by that sweep plus two structural rules:

  1. An entry belongs to a CURSOR, not to a continuation (the prefix length rides the continuation instead), so an operator that serializes a child continuation on EVERY emitted row adds one entry, not one per row.
  2. Adoption evicts the predecessor and any sibling a failed attempt published from it, so a paging chain keeps two.

MEMORY. The statement is charged for exactly what the scratch holds: the COMMITTED BASE plus the LIVE DELTAS. A parked entry holds its delta's bytes for as long as it lives — the producing cursor hands its page charge over at park time instead of releasing it at close — and retirement gives them back. Folding charges the committed layer permanently, because that layer really is held for the statement.

Both halves are load-bearing and they cancel if only one is present, which is how they were both wrong at once while every test passed. Without the fold charge the committed set weighs nothing, so a high-cardinality DISTINCT can never trip the budget and the loud-failure promise is empty. Without every removal returning its bytes — and there are three doors out of s.distinct, so they all go through retire — the charge tracks how many PAGES a statement took rather than how much data it holds, and a retry-heavy or finely-paged statement fails for memory it does not have. Measured on 40 distinct values before the fix: 77 bytes at 1 row/page, 71 at 3, 59 at 10, and 0 in a single page.

Concurrency: the executor is single-threaded per statement (zero goroutine launches in this package, pinned by package_invariant_test.go), so the maps need no lock, exactly as ExecuteState's counters do not.

func NewExecutionScratch

func NewExecutionScratch() *ExecutionScratch

NewExecutionScratch mints a scratch for one statement. A nil *ExecutionScratch is valid and means "no scratch": operators then fall back to self-contained continuations, which is correct and merely quadratic.

func (*ExecutionScratch) BeginPage

func (s *ExecutionScratch) BeginPage()

BeginPage tells the scratch a page's execution is starting: it DISCARDS what a previous attempt of this same page parked, then snapshots the token high-water mark and clears the page-scoped records. Safe on a nil scratch.

THE DISCARD IS THE OTHER HALF OF RETRY IDEMPOTENCE. This runs inside the FDB retry loop's body (cascades_generator.go:2002), so a second call without an intervening SweepAfterPage means the previous attempt's transaction DIED: its rows were dropped and its continuation bytes with them. Everything it parked is therefore named by nothing — and it is CHARGED, because a cursor hands its delta charge to its entry at park time and the close hook deliberately does not release it. Leaving it behind made a conflict storm accumulate one attempt-sized delta per attempt, with nothing reclaiming any of it until the page finally committed, so a statement could fail its memory budget (or the process) before any attempt got through. Measured over five attempts of one page: 16, 22, 28, 34, 40 bytes.

Dropping it is safe for exactly the reason the published set is immutable: an attempt only ever EXTENDS a committed layer it adopted read-only, and the retry re-adopts that same layer from the same bytes. It cannot reach the dead attempt's delta, because nothing it holds names it.

This is not the eviction rule 2 forbids. Rule 2 is about the entry a retry RESUMES FROM — its predecessor, parked by a committed page and named by the unchanged continuation bytes the retry re-executes; that entry is at or below settled and is never touched here.

func (*ExecutionScratch) LiveDistinctSets

func (s *ExecutionScratch) LiveDistinctSets() int

LiveDistinctSets returns how many parked states the scratch still holds. Test/diagnostic accessor; production code never reads it. It is what pins the eviction bound — the scratch must not accumulate one entry per page.

func (*ExecutionScratch) MintedDistinctSets

func (s *ExecutionScratch) MintedDistinctSets() int64

MintedDistinctSets returns how many seen-sets have been parked in this scratch. Test/diagnostic accessor (like ExecuteState.MemUsed); production code never reads it. It exists because the statement layer's wiring of the scratch is otherwise INVISIBLE: dropping WithExecutionScratch would leave every row-level assertion green while silently restoring the quadratic continuation, so the wiring needs an observable of its own.

func (*ExecutionScratch) PeakDistinctSets

func (s *ExecutionScratch) PeakDistinctSets() int

PeakDistinctSets returns the high-water mark of simultaneously live parked states. Test/diagnostic accessor (like MintedDistinctSets); production code never reads it.

It exists because the damage a failed attempt does is INVISIBLE from outside a page: the attempt's leftovers are collected by the page's own sweep once it finally commits, so every between-pages sample — the only kind a statement's caller can take — reads the same as an unfaulted run. The high-water mark is what a retried page actually costs while it is being retried, which is the figure a conflict storm multiplies.

func (*ExecutionScratch) SweepAfterPage

func (s *ExecutionScratch) SweepAfterPage(exhausted bool)

SweepAfterPage retires the entries the completed page has made unreachable.

RETIREMENT FOLLOWS NAMEABILITY, and the only thing that can name a token is a byte string the statement still holds. The statement holds exactly one — the page's surviving continuation — so:

  • exhausted: nothing is resumable at all, so NOTHING is nameable. Every entry retires. This is what bounds a single-page statement, including one whose correlated inner ran thousands of times.
  • otherwise: an entry parked BEFORE this page began and not adopted DURING it is unreachable, because a page adopts exactly the entries its own continuation named. Entries parked by this page are kept: which of them the surviving continuation names is not knowable yet, and the next page's adoptions are what settle it.

Deliberately NOT object-graph reachability from the surviving continuation, which is the shape this wanted to be: enclosing operators serialize their child eagerly and hand back a plain BytesContinuation (limitEnvelopeCursor, executor.go:2009, plus fifteen other sites), so the child object is dropped and no walk can see through it. Adoption is the only ground truth that survives that, at the cost of one page of lag.

Retiring on a CURSOR lifecycle event instead — park, close, exhaustion — is the mistake this replaced three times over. Exhaustion in particular does not imply un-nameability: aggregateCursor.emitFinal emits its final row carrying a RETAINED earlier inner continuation (streaming_cursors.go:393-401), so a token minted before the inner exhausted is still named after it.

Safe on a nil scratch.

type FilteredIndexPlanError

type FilteredIndexPlanError struct {
	IndexName string
}

FilteredIndexPlanError reports a physical query plan that attempts to read a sparse/filtered index without carrying a predicate-implication proof. The planner currently excludes every filtered index from query candidates, so a plan that reaches this guard is hand-built or stale. Executing it would read only the predicate-selected subset and could silently omit records.

Low-level record-store APIs deliberately remain able to scan filtered indexes for maintenance and diagnostics. This is a query-plan invariant.

func (*FilteredIndexPlanError) Error

func (e *FilteredIndexPlanError) Error() string

type IncompatiblePhysicalComparandError

type IncompatiblePhysicalComparandError struct {
	Component     int
	Comparison    predicates.ComparisonType
	PhysicalType  values.Type
	ProjectedType string
}

IncompatiblePhysicalComparandError reports an evaluated scan comparand that cannot inhabit the authoritative physical key component's tuple carrier. UNKNOWN parameters deliberately pass planner type gates, so this check must happen after valid packing-boundary coercions (numeric width and UUID) but before a mismatched tuple type code can turn SQL UNKNOWN into arbitrary rows.

func (*IncompatiblePhysicalComparandError) Error

type InvalidScanComparisonShapeError

type InvalidScanComparisonShapeError struct {
	Component int
	Detail    string
}

InvalidScanComparisonShapeError reports a comparison slice that cannot be represented by one contiguous tuple-key prefix: zero or more equalities, optionally one inequality component, and then only nil/Empty components. Silently stopping at the first gap or tail would discard a later predicate while presenting the scan as fully SARGed.

func (*InvalidScanComparisonShapeError) Error

type InvalidScanRangeExecutionIdentityTypeError

type InvalidScanRangeExecutionIdentityTypeError struct {
	Kind           ScanIdentityTypeErrorKind
	Implementation string
	Path           string
	ActivePath     string
}

InvalidScanRangeExecutionIdentityTypeError reports an invalid values.Type graph while a continuation fingerprint is being built. It deliberately records only concrete implementation names and structural paths: formatting the Type itself could invoke String on the same malformed graph and recurse.

func (*InvalidScanRangeExecutionIdentityTypeError) Error

type LegColumnProvenanceFloors

type LegColumnProvenanceFloors struct{}

LegColumnProvenanceFloors is the population this census must report over a whole suite run — and it is now EMPTY, which is the reconciliation rather than an omission.

It used to floor two numbers, because the census's finding was a pair of small ones: the dotted arm answered four times in the whole corpus, all four with an identity available. A zero population satisfied that second half vacuously, satisfied the DottedHitIdentityDiverged zero vacuously, and satisfied the partition as 0 == 0 — so a census that stopped being driven reported exactly the shape of a census reporting good news, and at that scale "4" and "0" do not look different at a glance.

The reader has since been RETIRED, and the retirement is what inverts the guard. adaptLegPositional's permutation gather is its only driver, and that gather's own note said what would end it: "retiring this gather requires Go's seed to bake against the chosen physical leg layout the same way [Java does]". The exact-ordinal seed does that, so every leg row now passes positionalMatchesLegType and the gather — and this reader with it — is never entered. A floor on that population is unsatisfiable; the danger is REVIVAL, and that is asserted unconditionally in assertLegColumnProvenanceCounters.

The type stays so the gate keeps its narrowed-run shape (a nil floors pointer still means "the corpus was filtered"), and so a future population has somewhere to be floored.

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
	// Layout is the selected immutable physical address space for this row.
	// It is deliberately separate from Type: logical type equality never
	// compares layout provenance.
	Layout values.OrdinalLayout
	// LayoutPresence distinguishes an unmatched null-supplying source from a
	// matched source whose complete row happens to contain SQL NULLs. It is
	// immutable values-owned row metadata and is never encoded as a logical
	// column.
	LayoutPresence values.WindowMatchPresence
	// contains filtered or unexported fields
}

PositionalRow is the typed positional runtime row — the SOLE runtime row: field values indexed by ORDINAL, paired with the RecordType that names and types each slot. Positional access (Slots[ordinal]) mirrors Java's MessageHelpers.getFieldValueForFieldOrdinals; every column reference reads its plan-time-baked ordinal (there is no runtime name resolution — the Type's names serve plan-time binding and diagnostics).

func NewLayoutPositionalRow

func NewLayoutPositionalRow(typ *values.RecordType, layout values.OrdinalLayout) (*PositionalRow, error)

NewLayoutPositionalRow builds a positional row owned by one exact physical layout. The layout's current carrier type must equal typ; the same admitted layout pointer is retained for runtime binder admission.

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) AttachOrdinalLayout

func (r *PositionalRow) AttachOrdinalLayout(layout values.OrdinalLayout, carrierType values.Type) (*PositionalRow, error)

AttachOrdinalLayout returns a row carrying layout as its physical address authority. The input row is never mutated: pass-through plans may share a child QueryResult while exposing a different parent property, and publishing that property must not rewrite the child's carrier in place.

The selected layout must describe exactly the row's logical record type. Dynamic/erased carriers therefore fail at the plan boundary instead of falling back to the legacy ambient positional interpretation.

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) MultiLeg

func (r *PositionalRow) MultiLeg() bool

MultiLeg reports whether this row is a MULTI-LEG composed row (a merged concat / clustered box row whose Type carries leg boundaries beyond a single whole-row window). Consulted by values.FieldValue's correlated fall-through arms: a source-relative baked ordinal cannot be served by a multi-leg row without a leg binding — such a read must fail loud rather than silently address the wrong leg's slot.

func (*PositionalRow) OrdinalLayout

func (r *PositionalRow) OrdinalLayout() values.OrdinalLayout

OrdinalLayout returns the exact immutable physical layout attached to this carrier. A legacy/unadmitted row returns nil.

func (*PositionalRow) OrdinalRecordType

func (r *PositionalRow) OrdinalRecordType() *values.RecordType

OrdinalRecordType exposes the exact logical record declaration to the driver-boundary rowstruct adapter. It does not expose or alter the physical OrdinalLayout; public STRUCT materialization needs only the row's immutable field types and ordinals.

func (*PositionalRow) OrdinalRowKind

func (r *PositionalRow) OrdinalRowKind() values.OrdinalCarrierKind

OrdinalRowKind exposes whether this positional value is a genuine record or the executor's private wrapper around a scalar. It is public only through the rowstruct boundary interface; runtime binding continues to use the selected plan's OrdinalLayout authority.

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 {
	// Positional is the ordinal-model row (a typed PositionalRow, field
	// values indexed by ordinal) — the SOLE runtime row. Every producer emits it
	// (scans/covering scans, projection/map, aggregate output, and join merges
	// via concatLegPositionals), and FieldValue resolution reads it by ordinal,
	// loud on a miss (OrdinalResolutionError). There is no name-keyed row model:
	// runtime name resolution would be first-match and silently wrong on
	// duplicate column names, where the plan-time ordinal is exact.
	Positional *PositionalRow
	Record     *recordlayer.FDBStoredRecord[proto.Message]
	PrimaryKey tuple.Tuple
}

QueryResult is the row type flowing through plan execution cursors. Wraps the ordinal-model row (Positional), 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, int64, 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. The second return is the total bytes charged against st — the owner of the returned rows releases exactly that at teardown (live-bytes model: the buffer is rebuilt per page against the statement-wide state, so an unreleased charge re-accumulates once per page).

func FromStoredRecord

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

FromStoredRecord builds a QueryResult from a stored record. The row is the ordinal PositionalRow built from the proto message (protoToPositional — one slot per descriptor field in declaration order; FieldValue reads it by ordinal).

When the record's store stores row versions, the row is extended with the trailing __ROW_VERSION pseudo-slot carrying the record version's 12 bytes (nil when the record has no version) — the runtime half of Java's PseudoField.fillInIfApplicable (PseudoField.java:72-100), keyed on the same two gates: the metadata stores versions, and the descriptor does not define a REAL field of that name (real-column-wins).

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 positional slots.

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 ScanIdentityTypeErrorKind

type ScanIdentityTypeErrorKind uint8

ScanIdentityTypeErrorKind identifies why a values.Type cannot safely participate in a scan continuation identity. The encoder accepts arbitrary implementations of values.Type, including hand-built plans, so it must reject interface values that would otherwise panic or recurse without end.

const (
	ScanIdentityTypeErrorTypedNil ScanIdentityTypeErrorKind = iota + 1
	ScanIdentityTypeErrorCycle
)

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{ Int32 bool }

SumOverflowError reports a SUM/AVG accumulation leaving its operator's integer domain. Int32 selects the SUM_I/AVG_I lane's message: Java's Math.addExact(int, int) throws ArithmeticException("integer overflow") where the long overload throws "long overflow", and the messages surface verbatim through the driver (both under SQLSTATE 22003).

func (*SumOverflowError) Error

func (e *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. Allocates a fresh backing array (tt.list = nil, not tt.list[:0]) rather than truncating in place — see Snapshot's doc comment for why: reusing the old array would let a still-unread Snapshot silently see whatever level's rows get Add-ed next.

func (*TempTable) GetList

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

GetList returns a COPY of the temp table contents (safe to hold across any later mutation, at the cost of an O(len) copy on every call). Prefer Snapshot for a read that will be held across an unbounded number of future Add calls without needing that copy.

func (*TempTable) ReleaseCharges

func (tt *TempTable) ReleaseCharges()

ReleaseCharges returns every byte this table has charged to the statement budget and zeroes the tally. Called exactly once by the table's owner at teardown; idempotent because the tally is zeroed.

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. Allocates a fresh backing array for the same reason as Clear (see Snapshot): appending rows[:0] would let a still-unread Snapshot alias this replacement's writes.

func (*TempTable) Snapshot

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

Snapshot returns the table's current row slice with NO copy — O(1), just the three-word slice header. This is safe to hold indefinitely (across any number of subsequent Add calls, in particular across a lazily-serialized continuation that may never be read, or read long after further rows were added) because of an invariant the rest of this type maintains: Add only ever appends past the length a Snapshot already captured (so old data a Snapshot exposes is never overwritten by later growth), and Clear/ ReplaceList always hand tt.list a FRESH backing array rather than truncating the existing one in place — so nothing this table does after a Snapshot was taken can ever become visible through it. Same principle as mergeSortCursor's per-child continuation snapshot (executor_new_plans.go): a held reference is safe because the type never mutates what it already exposed, only replaces it wholesale.

That safety is one-directional: it covers what THIS TYPE does to the backing array, not what a careless caller could do to it. The returned slice aliases tt's own memory — never write through it (no element assignment, no append() within its existing capacity: both land exactly where Add's own next append would write, corrupting rows Add hasn't produced yet). Treat the result as read-only. Call GetList instead if you need a slice you're allowed to mutate.

type UnknownPhysicalKeyTypeError

type UnknownPhysicalKeyTypeError struct {
	Component  int
	Comparison predicates.ComparisonType
}

UnknownPhysicalKeyTypeError reports a non-null scan comparand for which the chosen physical plan did not carry an authoritative key-component type. Tuple encoding is selected by the Go runtime carrier, so guessing from an operand declaration or evaluated parameter can probe a different tuple type code than the stored key (most notably FLOAT versus DOUBLE). Type-independent NULL cases are handled before this error is considered.

func (*UnknownPhysicalKeyTypeError) Error

type UnsupportedContinuationError

type UnsupportedContinuationError struct {
	Shape string
}

UnsupportedContinuationError reports a resume attempt on a cursor shape that has no continuation support yet (RFC-180 WS-A follow-ups). The driver maps it to SQLSTATE 0A000 — a typed decline, never a silent wrong start (before RFC-180 the buffered union fed the PARENT's continuation to every child; a raw-key scan child consumed it as a scan position).

func (*UnsupportedContinuationError) Error

type UnsupportedPhysicalFloatEquivalenceError

type UnsupportedPhysicalFloatEquivalenceError struct {
	Component    int
	Comparison   predicates.ComparisonType
	PhysicalType values.Type
}

UnsupportedPhysicalFloatEquivalenceError reports a floating-point scan comparand whose logical equality class cannot yet be represented exactly by this physical range binder. FDB tuple keys preserve every NaN sign/payload, while the query value comparator treats NaNs as one logical value. Probing the evaluated payload would silently miss equal keys, so an already-chosen dynamic index access fails before opening storage.

func (*UnsupportedPhysicalFloatEquivalenceError) Error

type UnsupportedPhysicalKeyTypeError

type UnsupportedPhysicalKeyTypeError struct {
	Component    int
	Comparison   predicates.ComparisonType
	PhysicalType values.Type
}

UnsupportedPhysicalKeyTypeError reports physical metadata that names a placeholder or structured logical type rather than one concrete scalar FDB tuple carrier. Presence is not authority: ANY, NULL, NONE, RECORD, ARRAY, RELATION, and ENUM do not specify how a query comparand was encoded in the key. Production metadata maps enum fields to their LONG wire carrier; a hand-built/stale plan that carries ENUM itself must therefore fail closed.

func (*UnsupportedPhysicalKeyTypeError) Error

type UnsupportedPhysicalNumericProjectionError

type UnsupportedPhysicalNumericProjectionError struct {
	Component            int
	Comparison           predicates.ComparisonType
	PhysicalType         values.Type
	EquivalenceClassLow  int64
	EquivalenceClassHigh int64
}

UnsupportedPhysicalNumericProjectionError reports a floating-point comparand whose logical equality class contains more than one value in an authoritative physical integer key domain. The scan plan's fixed-key and one-row proofs are valid only when every successful equality binding selects at most one physical integer key. Returning this error before storage is therefore preferable to silently probing one representative of a larger cmpAny equivalence class.

The class can contain multiple integers because cmpAny compares a mixed integer/float pair after converting the integer to float64. For example, both 2^53 and 2^53+1 compare equal to float64(2^53). Ordered comparisons do not use this error: their monotone inverse is projected to an exact inclusive integer interval by projectFloatComparisonToIntegerDomain.

func (*UnsupportedPhysicalNumericProjectionError) Error

type UnsupportedPhysicalStartsWithError

type UnsupportedPhysicalStartsWithError struct {
	Component    int
	PhysicalType values.Type
}

UnsupportedPhysicalStartsWithError reports a STARTS_WITH comparison whose authoritative physical key component is not STRING. PREFIX_STRING tuple endpoints describe a byte prefix of a packed string element; applying them to another carrier (including BYTES, or the string-backed DATE/TIMESTAMP logical types) does not implement Comparison.Eval's string/string STARTS_WITH truth table. Such a plan must fail before opening storage rather than silently treating an operator/type mismatch as a compensated range.

func (*UnsupportedPhysicalStartsWithError) Error

Jump to

Keyboard shortcuts

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