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
- func AssertLegColumnProvenanceCensus(w io.Writer, floors *LegColumnProvenanceFloors) bool
- func EvaluateScalarSubquery(ctx context.Context, plan plans.RecordQueryPlan, ...) (any, error)
- func ExecutePlan(ctx context.Context, plan plans.RecordQueryPlan, ...) (recordlayer.RecordCursor[QueryResult], error)
- func FormatLegColumnProvenanceCensus() string
- func FormatMergedLegBindingCensus() string
- func LegColumnProvenanceCensus() (legColumnProvenanceCounters, []string)
- func LegColumnProvenanceDottedNames() []string
- func MergedLegBindingCensus() (map[MergedLegBinding]int, map[string]int)
- func MergedRowLegReads() map[MergedRowRead]int
- func PositionalTypeForDescriptor(desc protoreflect.MessageDescriptor) *values.RecordType
- func PositionalTypeForRecordLayout(desc protoreflect.MessageDescriptor, storeRowVersions bool) *values.RecordType
- func RedundantMergedLegReaders() map[MergedRowRead]string
- func RegisterRedundantMergedLegReader(read MergedRowRead, why string)
- func ResetMergedLegBindingCensus()
- func RowValue(qr QueryResult) any
- func UnshadowedMergedRowLegReads() map[MergedRowRead]int
- type AggregateTypeMismatchError
- type ColumnDef
- type EvaluationContext
- func (ec *EvaluationContext) BindParameter(ordinal int, name string) (any, bool)
- func (ec *EvaluationContext) GetBinding(id values.CorrelationIdentifier) (any, bool)
- func (ec *EvaluationContext) GetCorrelationBinding(id values.CorrelationIdentifier) (any, bool)
- func (ec *EvaluationContext) GetOrCreateTempTable(id values.CorrelationIdentifier, st *recordlayer.ExecuteState) *TempTable
- func (ec *EvaluationContext) ReleaseAllTempTableCharges()
- func (ec *EvaluationContext) RowContext() *values.RowEvalContext
- func (ec *EvaluationContext) RowContextPositional(pos values.OrdinalRow) *values.RowEvalContext
- func (ec *EvaluationContext) Scratch() *ExecutionScratch
- func (ec *EvaluationContext) StatementNow() time.Time
- func (ec *EvaluationContext) WithBinding(id values.CorrelationIdentifier, val any) *EvaluationContext
- func (ec *EvaluationContext) WithExecutionScratch(s *ExecutionScratch) *EvaluationContext
- func (ec *EvaluationContext) WithMergedLegReadBypass(aliases ...string) *EvaluationContext
- func (ec *EvaluationContext) WithMergedLegReadSink(sink *MergedLegReadSink) *EvaluationContext
- func (ec *EvaluationContext) WithMergedLegWrongWindows() *EvaluationContext
- func (ec *EvaluationContext) WithParams(params []any) *EvaluationContext
- func (ec *EvaluationContext) WithScalarSubqueries(results map[values.CorrelationIdentifier]any) *EvaluationContext
- func (ec *EvaluationContext) WithStatementTime(t time.Time) *EvaluationContext
- type ExecutionScratch
- type FilteredIndexPlanError
- type IncompatiblePhysicalComparandError
- type InvalidScanComparisonShapeError
- type InvalidScanRangeExecutionIdentityTypeError
- type LegColumnProvenanceFloors
- type MaterializationLimitExceededError
- type MergedLegBinding
- type MergedLegReadSink
- type MergedRowRead
- type NumericRangeOverflowError
- type PositionalRow
- type QueryResult
- func CollectAll(ctx context.Context, cursor recordlayer.RecordCursor[QueryResult]) ([]QueryResult, error)
- func CollectAllBounded(ctx context.Context, cursor recordlayer.RecordCursor[QueryResult], ...) ([]QueryResult, int64, error)
- func FromStoredRecord(rec *recordlayer.FDBStoredRecord[proto.Message]) QueryResult
- type RecordLayerResultSet
- func (rs *RecordLayerResultSet) Boolean(columnIndex int) (bool, error)
- func (rs *RecordLayerResultSet) BooleanByName(name string) (bool, error)
- func (rs *RecordLayerResultSet) Bytes(columnIndex int) ([]byte, error)
- func (rs *RecordLayerResultSet) BytesByName(name string) ([]byte, error)
- func (rs *RecordLayerResultSet) Close() error
- func (rs *RecordLayerResultSet) Continuation() (api.Continuation, error)
- func (rs *RecordLayerResultSet) Double(columnIndex int) (float64, error)
- func (rs *RecordLayerResultSet) Err() error
- func (rs *RecordLayerResultSet) Float(columnIndex int) (float32, error)
- func (rs *RecordLayerResultSet) GetContinuation() recordlayer.RecordCursorContinuation
- func (rs *RecordLayerResultSet) GetNoNextReason() recordlayer.NoNextReason
- func (rs *RecordLayerResultSet) Long(columnIndex int) (int64, error)
- func (rs *RecordLayerResultSet) LongByName(name string) (int64, error)
- func (rs *RecordLayerResultSet) MetaData() api.ResultSetMetaData
- func (rs *RecordLayerResultSet) Next() bool
- func (rs *RecordLayerResultSet) Object(columnIndex int) (any, error)
- func (rs *RecordLayerResultSet) ObjectByName(name string) (any, error)
- func (rs *RecordLayerResultSet) String(columnIndex int) (string, error)
- func (rs *RecordLayerResultSet) StringByName(name string) (string, error)
- func (rs *RecordLayerResultSet) WasNull() bool
- type RecursiveCTEDepthExceededError
- type ScanIdentityTypeErrorKind
- type SortBufferExceededError
- type SumOverflowError
- type TempTable
- type UnknownPhysicalKeyTypeError
- type UnsupportedContinuationError
- type UnsupportedPhysicalFloatEquivalenceError
- type UnsupportedPhysicalKeyTypeError
- type UnsupportedPhysicalNumericProjectionError
- type UnsupportedPhysicalStartsWithError
Constants ¶
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 ¶
func ExecutePlan( ctx context.Context, plan plans.RecordQueryPlan, store *recordlayer.FDBRecordStore, evalCtx *EvaluationContext, continuation []byte, props recordlayer.ExecuteProperties, ) (recordlayer.RecordCursor[QueryResult], error)
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 FormatMergedLegBindingCensus ¶
func FormatMergedLegBindingCensus() string
FormatMergedLegBindingCensus renders the census for a harness to log.
The headline is the RATIO: binds are what the path costs, reads are what it buys. Reporting only the bind count would read as evidence the binder is carrying the corpus, which is the misreading this census was built to correct.
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 MergedLegBindingCensus ¶
func MergedLegBindingCensus() (map[MergedLegBinding]int, map[string]int)
MergedLegBindingCensus returns copies of the bind and read tallies.
func MergedRowLegReads ¶
func MergedRowLegReads() map[MergedRowRead]int
MergedRowLegReads returns a copy of the MULTI-LEG subset of the read tally — the reads that resolved to a window on a merged row carrying sibling legs, keyed by (alias, merged-row shape).
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 RedundantMergedLegReaders ¶
func RedundantMergedLegReaders() map[MergedRowRead]string
RedundantMergedLegReaders returns a copy of the proven-redundant registry, each shape rendered as its proofs' names in sorted order (the suite is parallel, so registration order is not a stable thing to report).
func RegisterRedundantMergedLegReader ¶
func RegisterRedundantMergedLegReader(read MergedRowRead, why string)
RegisterRedundantMergedLegReader records that reads of ONE (alias, merged-row shape) were proven not load-bearing in this run — the same rows under a perturbation of the binder's window — by the named proof. Call it ONLY from the passing path of that proof.
The perturbation is the proof's own choice and the two live ones differ: declining the window entirely so the alias resolves to nothing (TestFDB_MergedLegBinding_ReaderShapeIsRedundant), and aiming every window at a sibling leg's span (TestFDB_MergedLegBinding_WrongWindowsAreUnobservable). Both establish the same thing about the same reads — the value behind the binding does not reach an answer — so both excuse them, and both are named.
It takes the read's full identity, not its alias, so the excusal covers exactly the shape the proof ran. A proof of `ST` out of an `ST[0,3)|OT[3,5)` merged row says nothing about `ST` out of some other merged row, and a registry keyed on the name would have said it anyway.
why is the test's own identity, carried so the activation gate can name what is excusing a read rather than asserting it on the census's authority.
func ResetMergedLegBindingCensus ¶
func ResetMergedLegBindingCensus()
ResetMergedLegBindingCensus clears all tallies.
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.
func UnshadowedMergedRowLegReads ¶
func UnshadowedMergedRowLegReads() map[MergedRowRead]int
UnshadowedMergedRowLegReads returns a copy of the UNSHADOWED subset — multi-leg reads of a window that displaced nothing, so the binder was the alias's only binding.
This is a DIAGNOSTIC, not the activation gate's input: over the sqldriver corpus every multi-leg read is unshadowed and none is load-bearing, so the structural property does not separate the alarm case from the quiet one. See mergedLegUnshadowedMergedRowReads for the measurement that settled it. The accessor exists so FormatMergedLegBindingCensus can report the number that makes the point, and so a future change of that number is visible rather than invisible.
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 ¶
func (e *AggregateTypeMismatchError) Error() string
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 ¶
func (ec *EvaluationContext) GetOrCreateTempTable(id values.CorrelationIdentifier, st *recordlayer.ExecuteState) *TempTable
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) 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 ¶
func (ec *EvaluationContext) WithBinding(id values.CorrelationIdentifier, val any) *EvaluationContext
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) WithMergedLegReadBypass ¶
func (ec *EvaluationContext) WithMergedLegReadBypass(aliases ...string) *EvaluationContext
WithMergedLegReadBypass returns a copy in which lookups of the named aliases DECLINE any window bindMergedOuterLegs produced, falling back to whatever that window displaced (nothing, for a window that displaced nothing).
It exists for one caller: the redundancy pin, which has to run the SAME query down BOTH resolution routes and compare. A build-tagged neuter cannot do that job — it removes the binder from the whole binary, so the two routes never coexist in one run, and their agreement is the entire content of the pin.
Honoured only while the leg-identity census gate is on (the read site's gate), so production never consults it.
func (*EvaluationContext) WithMergedLegReadSink ¶
func (ec *EvaluationContext) WithMergedLegReadSink(sink *MergedLegReadSink) *EvaluationContext
WithMergedLegReadSink returns a copy whose binder-window reads and declined lookups are ALSO recorded into sink, scoped to this execution.
Honoured only while the leg-identity census gate is on, like the rest of this instrumentation.
func (*EvaluationContext) WithMergedLegWrongWindows ¶
func (ec *EvaluationContext) WithMergedLegWrongWindows() *EvaluationContext
WithMergedLegWrongWindows returns a copy in which bindMergedOuterLegs aims each leg window of a merged row at its SIBLING's span rather than its own, so every binding a reader can resolve through is DELIBERATELY WRONG.
It exists so the mutation that licenses "the merged-leg bindings are not load-bearing" can STAND rather than be re-run by hand. That claim rested on someone editing the binder to misaim it and watching the suite stay green; nothing performed it in CI, so the day a read starts depending on which slots its window covers, no test went red and a green census read as "the bindings are correct" when it only ever meant "nobody looked".
It rides EvaluationContext for the same two reasons WithMergedLegReadBypass does: the suite is parallel and several tests share these table names, so a process-wide switch would misaim a concurrently running test's execution; and an edited-out or build-tagged misaim cannot run the correct and the wrong window in ONE process, which is the entire comparison.
The perturbation is a ROTATION onto the sibling's span, not a constant offset, because a constant is not reliably wrong: the first leg of a merged row already starts at 0, so "point everything at slot 0" leaves it aimed correctly. Rotation moves every window of a multi-leg row whose legs are not all identically shaped, and the windows it did move are the ones counted — an instrument that cannot state it perturbed anything proves nothing.
Honoured only while the leg-identity census gate is on, like the rest of this instrumentation, so production never misaims.
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:
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.
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:
- 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.
- 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 ¶
func (e *IncompatiblePhysicalComparandError) Error() string
type InvalidScanComparisonShapeError ¶
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 ¶
func (e *InvalidScanComparisonShapeError) Error() string
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 ¶
func (e *InvalidScanRangeExecutionIdentityTypeError) Error() string
type LegColumnProvenanceFloors ¶
type LegColumnProvenanceFloors struct {
// Calls floors the denominator every share is taken against.
Calls int
// DottedHitIdentityAvailable floors the population the retirement decision
// is ABOUT. Calls alone does not cover it: the flat-hit arm carries 40 of
// the 52 calls, so Calls can stay healthy while the dotted arm — the only
// arm this census exists for — goes to zero.
DottedHitIdentityAvailable int
}
LegColumnProvenanceFloors is the minimum population this census must report over a whole suite run.
It exists for the reason its two siblings' floors exist, and the reason is sharper here than for either of them. This census's entire finding is a pair of small numbers — the dotted arm answers FOUR times in the whole corpus, and all four have an identity available — and the retirement decision rests on BOTH halves: on the four being all there is, and on all four being identity-available. A zero population satisfies the second half vacuously. It also satisfies the DottedHitIdentityDiverged zero vacuously, and the partition as 0 == 0.
So a census that stopped being driven at all reports exactly the shape of a census reporting good news, and the numbers are small enough that "4" and "0" do not look different at a glance. The floors are what make them different.
Set at 1 rather than an order of magnitude below the measurement, because there is no order of magnitude here: the measured DottedHitIdentityAvailable is 4. What a floor detects at this scale is DISAPPEARANCE, which is the whole failure mode — the shapes that drive the dotted arm ceasing to be planned, or the reader ceasing to be reached.
type MaterializationLimitExceededError ¶
MaterializationLimitExceededError is returned when an operator tries to buffer more rows in memory than the configured materialization limit.
func (*MaterializationLimitExceededError) Error ¶
func (e *MaterializationLimitExceededError) Error() string
type MergedLegBinding ¶
type MergedLegBinding struct {
// OuterAlias is the FlatMap's own alias — the join whose merged row is being
// decomposed. It is recorded because the join's-own-alias skip is decided
// against it, and on the live shapes it never engages (the box's alias is not
// any leg's alias), which is exactly the case the unit fixtures did not cover.
OuterAlias string
LegAlias string
Offset int
Width int
}
The MERGED-LEG BINDING census: what bindMergedOuterLegs PRODUCED, and whether anything READ it.
It exists because those two are different questions and the answer differs. The binder was documented as having "no non-test consumer", which read as dormant. It is not dormant — it executes on the real-FDB corpus on every clustered-box gather, over ten thousand times in one sqldriver run. It also has a READER, on a multi-leg merged row: over that same run its bindings are looked up twelve times, every one of them on a merged row carrying SIBLING legs. What those reads are not is LOAD-BEARING — neither removing the bindings entirely nor aiming every window at a sibling leg's slots changes a single assertion, and both of those perturbations are performed by a standing test on every run rather than by hand.
A claim like that decays the moment someone adds a load-bearing consumer, and it decays SILENTLY — the binder keeps working, so nothing goes red, and the next person reads the stale claim and reasons from it. So the claim is measured here rather than asserted in a comment: the reads are counted, attributed to the merged row they came out of, and excused one shape at a time by a proof that re-runs. A read this census cannot match to such a proof turns the gate red and says what gets re-armed.
It is GATED on the same flag as the leg-identity census (values.LegIdentityCensusEnabled): the read side sits in EvaluationContext.GetCorrelationBinding, which is per-reference-per-row, and production must not pay a type assertion there.
type MergedLegReadSink ¶
type MergedLegReadSink struct {
// contains filtered or unexported fields
}
MergedLegReadSink collects ONE execution's binder-window reads and declined lookups, in parallel with the process-global census.
It exists because the global census cannot answer "what did THIS execution do?". Its tallies are process-wide and the suite is parallel, so a before/after delta around one execution also picks up whatever a concurrently running test read in the same window — and the redundancy pin's whole method is to compare two executions of one query by what each of them read. A delta that a sibling test can perturb makes that comparison intermittently wrong in the direction of a spurious red.
The zero value is not usable; a nil *MergedLegReadSink is, and records nothing, so the read path can call through it unconditionally.
func NewMergedLegReadSink ¶
func NewMergedLegReadSink() *MergedLegReadSink
NewMergedLegReadSink returns an empty sink.
func (*MergedLegReadSink) BypassOutcomes ¶
func (s *MergedLegReadSink) BypassOutcomes() (misses, handoffs map[MergedRowRead]int)
BypassOutcomes returns copies of this execution's declined lookups: those that resolved to NOTHING, and those handed a binding the window had displaced.
func (*MergedLegReadSink) MisaimedReads ¶
func (s *MergedLegReadSink) MisaimedReads() map[MergedRowRead]int
MisaimedReads returns a copy of the subset of this execution's reads that resolved to a window misaimMergedLegWindows had moved off its own leg.
func (*MergedLegReadSink) Reads ¶
func (s *MergedLegReadSink) Reads() map[MergedRowRead]int
Reads returns a copy of this execution's multi-leg binder-window reads.
type MergedRowRead ¶
type MergedRowRead struct {
Alias string
// Shape is mergedRowShape of the merged row the read's window looked into.
Shape string
}
MergedRowRead identifies a read that resolved to a binder-produced window on a MULTI-LEG merged row: the alias that was read, AND the LAYOUT of the merged row it was read out of.
The layout is carried because the alias alone is not an identity. Alias names COLLIDE across queries — the corpus binds twenty unrelated legs under the outer name `X` — which is why this census refuses to classify reads by alias. The same argument applies with full force to EXCUSING them: a proven-redundant entry keyed on the bare name `ST` excuses every future multi-leg read of anything called `ST`, including a load-bearing one in a query nobody has written yet. Keyed on (alias, shape) it excuses only what the proof actually covered.
type NumericRangeOverflowError ¶
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 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 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) 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) 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 ¶
func (rs *RecordLayerResultSet) GetContinuation() recordlayer.RecordCursorContinuation
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 (rs *RecordLayerResultSet) MetaData() api.ResultSetMetaData
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 ¶
func (e *RecursiveCTEDepthExceededError) Error() string
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 ¶
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 ¶
func (e *UnknownPhysicalKeyTypeError) Error() string
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 ¶
func (e *UnsupportedContinuationError) Error() string
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 ¶
func (e *UnsupportedPhysicalFloatEquivalenceError) Error() string
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 ¶
func (e *UnsupportedPhysicalKeyTypeError) Error() string
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 ¶
func (e *UnsupportedPhysicalNumericProjectionError) Error() string
type UnsupportedPhysicalStartsWithError ¶
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 ¶
func (e *UnsupportedPhysicalStartsWithError) Error() string
Source Files
¶
- continuation.go
- distinct_stream.go
- evaluation_context.go
- execution_scratch.go
- executor.go
- executor_new_plans.go
- flat_map_cursor.go
- leg_column_provenance_census.go
- memory_budget.go
- merged_leg_binding_census.go
- ordinal_join.go
- position_replay_cursor.go
- positional_row.go
- query_result.go
- recursive_cursor.go
- recursive_union_cursor.go
- resultset.go
- scalar_subquery.go
- scan_range_binding.go
- scan_range_execution_identity.go
- scan_range_set_cursor.go
- streaming_cursors.go