Documentation
¶
Overview ¶
Package embedded implements the embedded (in-process) SQL execution engine for the FoundationDB relational layer.
EmbeddedConnection is the Go equivalent of Java's EmbeddedRelationalConnection. It parses SQL, routes DDL statements through the MetadataOperationsFactory, and (eventually) routes DML through the query planner.
Index ¶
- Constants
- func BuildSchemaTemplateFromDDL(schemaDDL string) (*metadata.RecordLayerSchemaTemplate, error)
- func BuildSchemaTemplateFromDDLNamed(schemaDDL, name string) (*metadata.RecordLayerSchemaTemplate, error)
- func NewExplainOnlyGenerator() query.Generator
- func NewExplainOnlyGeneratorWithSchema(schemaDDL string) (query.Generator, error)
- func PlanPhysicalDMLForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
- func PlanPhysicalDMLForTestWithReachability(sql, schemaDDL string, stats properties.StatisticsProvider, ...) (plans.RecordQueryPlan, error)
- func PlanPhysicalForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
- func PlanPhysicalForTestWithReachability(sql, schemaDDL string, stats properties.StatisticsProvider, ...) (plans.RecordQueryPlan, error)
- func PlanQueryForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (string, error)
- func PlanQueryForTestWithDisabledRules(sql, schemaDDL string, stats properties.StatisticsProvider, disabled []string) (string, error)
- func PlanQueryWithMetadata(sql string, md *recordlayer.RecordMetaData, ...) (string, error)
- func PlanRecordQueryAssertingAllIndexesReadable(sql string, md *recordlayer.RecordMetaData, ...) (plans.RecordQueryPlan, error)
- func PlanRecordQueryWithMetadata(sql string, md *recordlayer.RecordMetaData, ...) (plans.RecordQueryPlan, error)
- func PlanRecordQueryWithMetadataSchema(sql string, md *recordlayer.RecordMetaData, schemaName string, ...) (plans.RecordQueryPlan, error)
- func ResultColumnDefsForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []executor.ColumnDef
- func ResultColumnLabelsForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []string
- func ResultColumnNullabilityForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []int
- func ResultColumnTypesForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []string
- type CorrelatedExistsError
- type EmbeddedConnection
- func (c *EmbeddedConnection) ActiveTransactionTags() []string
- func (c *EmbeddedConnection) Begin() (driver.Tx, error)
- func (c *EmbeddedConnection) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, error)
- func (c *EmbeddedConnection) CheckNamedValue(nv *driver.NamedValue) error
- func (c *EmbeddedConnection) Close() error
- func (c *EmbeddedConnection) ExecContext(ctx context.Context, sql string, args []driver.NamedValue) (res driver.Result, err error)
- func (c *EmbeddedConnection) GetDBPath() string
- func (c *EmbeddedConnection) GetSchema() string
- func (c *EmbeddedConnection) IsValid() bool
- func (c *EmbeddedConnection) Options() *api.Options
- func (c *EmbeddedConnection) Ping(ctx context.Context) error
- func (c *EmbeddedConnection) PlanExplain(ctx context.Context, sql string) (string, error)
- func (c *EmbeddedConnection) Prepare(query string) (driver.Stmt, error)
- func (c *EmbeddedConnection) PrepareContext(_ context.Context, query string) (driver.Stmt, error)
- func (c *EmbeddedConnection) QueryContext(ctx context.Context, sql string, args []driver.NamedValue) (rows driver.Rows, err error)
- func (c *EmbeddedConnection) ResetSession(_ context.Context) error
- func (c *EmbeddedConnection) SetDefaultSchema(s string)
- func (c *EmbeddedConnection) SetExecutionStatsLogger(l ExecutionStatsLogger)
- func (c *EmbeddedConnection) SetFailOnScanLimitReached(v bool)
- func (c *EmbeddedConnection) SetMaxResultBytes(n int64)
- func (c *EmbeddedConnection) SetOptions(o *api.Options)
- func (c *EmbeddedConnection) SetPlanLogger(l PlanGenerationLogger)
- func (c *EmbeddedConnection) SetSchema(s string)
- func (c *EmbeddedConnection) SetSlowQueryThresholdMicros(micros int64)
- func (c *EmbeddedConnection) SetStatementTimeout(d time.Duration)
- type ExecutionStats
- type ExecutionStatsLogger
- type PlanCache
- func (c *PlanCache) Get(scope, sql string) (plans.RecordQueryPlan, []PlannedScalarSubquery, bool)
- func (c *PlanCache) Invalidate()
- func (c *PlanCache) Len() int
- func (c *PlanCache) Put(scope, sql string, plan plans.RecordQueryPlan, subs []PlannedScalarSubquery)
- func (c *PlanCache) Stats() (hits, misses int64)
- type PlanCacheEvent
- type PlanGenerationInfo
- type PlanGenerationLogger
- type PlanVisitor
- func (v *PlanVisitor) VisitQuery(q antlrgen.IQueryContext) (logical.LogicalOperator, error)
- func (v *PlanVisitor) VisitQueryBody(body antlrgen.IQueryExpressionBodyContext) (logical.LogicalOperator, error)
- func (v *PlanVisitor) VisitQueryTerm(qt antlrgen.IQueryTermContext) (logical.LogicalOperator, error)
- func (v *PlanVisitor) VisitSimpleTable(termCtx *antlrgen.QueryTermDefaultContext) (logical.LogicalOperator, error)
- type PlannedScalarSubquery
Constants ¶
const MaxLoggedSQLLength = 1024
MaxLoggedSQLLength bounds the SQL text carried in a PlanGenerationInfo so a pathological query can't blow up a log line.
Variables ¶
This section is empty.
Functions ¶
func BuildSchemaTemplateFromDDL ¶
func BuildSchemaTemplateFromDDL(schemaDDL string) (*metadata.RecordLayerSchemaTemplate, error)
BuildSchemaTemplateFromDDL parses schemaDDL as a single CREATE SCHEMA TEMPLATE statement (auto-wrapping bare CREATE TABLE/INDEX clauses) and builds the RecordLayerSchemaTemplate without any catalog write. It is the programmatic entry to the exact metadata the DDL path produces — used by wire-level index tests that need the DDL-generated metadata against a real record store.
func BuildSchemaTemplateFromDDLNamed ¶
func BuildSchemaTemplateFromDDLNamed(schemaDDL, name string) (*metadata.RecordLayerSchemaTemplate, error)
BuildSchemaTemplateFromDDLNamed is BuildSchemaTemplateFromDDL with an explicit template name for bare clause bodies. The name matters at the wire level: it is the descriptor FILE name inside the persisted RecordMetaData, so a cross-engine byte comparison must build under the same name Java persisted. Quoted to preserve case (Java's harness sets the name programmatically, case intact).
func NewExplainOnlyGenerator ¶
NewExplainOnlyGenerator constructs a Generator suitable for capturing Plan.Explain() output without executing. The returned Generator is backed by a zero-value EmbeddedConnection — Plan.Execute on the returned plans is unsupported (no FDB, no catalog, no session state). Used by the plan-equivalence harness (RFC-022 section 4.-1) to produce plan trees for diffing against Java's planner output.
Catalog-aware predicate trees (buildLogicalPlanFor*WithCatalog paths) require non-nil RecordMetaData; this constructor always produces text-only logical plans. Use NewExplainOnlyGeneratorWithSchema to unlock the catalog-aware branch.
func NewExplainOnlyGeneratorWithSchema ¶
NewExplainOnlyGeneratorWithSchema is the catalog-aware companion to NewExplainOnlyGenerator. It parses the supplied CREATE SCHEMA TEMPLATE DDL into an in-memory RecordLayerSchemaTemplate (no FDB write), wraps it in an api.Schema bound to a synthetic database + schema, and seeds the connection's SchemaCache. Subsequent statements planned through the returned Generator route through the buildLogicalPlanFor*WithCatalog paths so WHERE clauses appear as real cascades.predicates.QueryPredicate trees in the Explain output.
schemaDDL must contain exactly one CREATE SCHEMA TEMPLATE statement. Multiple-statement DDL or any non-CREATE-SCHEMA-TEMPLATE shape returns an error — callers should isolate the schema DDL from the SELECT/DML they intend to plan.
func PlanPhysicalDMLForTest ¶
func PlanPhysicalDMLForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
PlanPhysicalDMLForTest is PlanPhysicalForTest for a DELETE or UPDATE statement: it routes the DML through the SAME logical-build → translate → Cascades-plan → extract → ValidatePlanInvariants pipeline the production DML generator (cascadesGenerator.planDML) runs, but metadata-only — no live store, planner-default statistics when stats is nil — exactly as PlanPhysicalForTest does for SELECT.
It exists so the explain-differ corpus dump can pin DML plan SHAPES. A planner change that reads differ-clean on every SELECT while corrupting the DELETE-WHERE-EXISTS path (RFC-184 W2) is invisible to a SELECT-only dump; planning DELETE/UPDATE too catches that class at the plan level.
Only DELETE and UPDATE are handled. INSERT carries no interesting winning plan: INSERT … VALUES is literal rows (no scanned tree), and INSERT … SELECT is planned as its SELECT body, already covered by PlanPhysicalForTest. The sql passed here MUST be a single DELETE or UPDATE statement; anything else is a caller error (ErrCodeUnsupportedQuery).
func PlanPhysicalDMLForTestWithReachability ¶
func PlanPhysicalDMLForTestWithReachability( sql, schemaDDL string, stats properties.StatisticsProvider, reach *cascades.ReachabilityCollector, ) (plans.RecordQueryPlan, error)
PlanPhysicalDMLForTestWithReachability is PlanPhysicalDMLForTest with RFC-183's yield-time plan-reachability accounting routed into the caller's collector. nil = collect nothing (identical to PlanPhysicalDMLForTest).
func PlanPhysicalForTest ¶
func PlanPhysicalForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
PlanPhysicalForTest is PlanQueryForTest returning the TYPED physical plan instead of its Explain rendering. The rowdiff harness (RFC-182) buckets plan families over this tree — per the RFC, plan-family classification must come from a plan-type switch, never from string-matching EXPLAIN text.
func PlanPhysicalForTestWithReachability ¶
func PlanPhysicalForTestWithReachability( sql, schemaDDL string, stats properties.StatisticsProvider, reach *cascades.ReachabilityCollector, ) (plans.RecordQueryPlan, error)
PlanPhysicalForTestWithReachability is PlanPhysicalForTest with RFC-183's yield-time plan-reachability accounting routed into the caller's collector.
Exists because that tally must belong to ONE measurement. It used to live in package variables inside cascades, and the corpus ratchet consequently summed three concurrent tests' planning into a single number (edges=53748 against a true 17916). A collector the caller owns cannot be added to, or reset by, anyone it was not handed to.
nil collector is legal and means "collect nothing" — identical to PlanPhysicalForTest.
func PlanQueryForTest ¶
func PlanQueryForTest(sql, schemaDDL string, stats properties.StatisticsProvider) (string, error)
PlanQueryForTest runs the full Cascades pipeline on a SQL query against a schema defined by DDL, with optional table statistics. Returns the physical plan's Explain string. No FDB connection needed.
schemaDDL is a CREATE SCHEMA TEMPLATE statement (or just the table/index definitions — "CREATE SCHEMA TEMPLATE auto_template" is prepended if missing).
stats may be nil to use default statistics (LeafScanCardinality for all record types).
func PlanQueryForTestWithDisabledRules ¶
func PlanQueryForTestWithDisabledRules( sql, schemaDDL string, stats properties.StatisticsProvider, disabled []string, ) (string, error)
PlanQueryForTestWithDisabledRules is PlanQueryForTest with a set of planner rules excluded from selection, by the SIMPLE type name Planner.DisabledRules is keyed by ("MergeProjectionAndFetchRule").
It exists so a causal claim about WHICH rule produces an observed plan shape can be asserted rather than asserted-in-prose: the observable ("the plan has COVERING") is one measurement, and "this rule is what put it there" is a different one that only a disabling experiment can make. The names go through the same api.Options funnel a connection's DISABLED_PLANNER_RULES uses, so a test cannot reach a disabled-rule configuration production cannot.
An unrecognized name is INERT, not an error — Java's setDisabledTransformationRuleNames never resolves its strings either. A test relying on a name must therefore also show the name has an effect; a typo otherwise reads as "disabling it changed nothing".
func PlanQueryWithMetadata ¶
func PlanQueryWithMetadata(sql string, md *recordlayer.RecordMetaData, stats properties.StatisticsProvider) (string, error)
PlanQueryWithMetadata is like PlanQueryForTest but accepts pre-built RecordMetaData instead of DDL. Used for testing features that require index types not expressible in DDL (aggregate indexes).
func PlanRecordQueryAssertingAllIndexesReadable ¶
func PlanRecordQueryAssertingAllIndexesReadable( sql string, md *recordlayer.RecordMetaData, stats properties.StatisticsProvider, ) (plans.RecordQueryPlan, error)
PlanRecordQueryAssertingAllIndexesReadable is PlanRecordQueryWithMetadata under the AFFIRMATIVE all-indexes-readable view instead of the UNKNOWN one.
The plain harness deliberately leaves index state UNKNOWN — it has no store to ask — and RFC-210 §5.1.1 makes that demotion load-bearing: a planning run that never consulted a store may still plan an index scan, but it may not prove anything from an index's declared UNIQUENESS. That is why the harness never yields R2 or R3, and it is a property worth keeping.
It also means a metadata-only test cannot obtain a NARROWED plan from the planner. Before this entry existed, the tests that needed one built it by hand — `distinct.WithNarrowedDedup("BY_EMAIL", []int{0})` — which asserts nothing about the mapping from the index's key columns onto dedup-key slot positions (rule_implement_distinct_final.go's exemptSlotsFor). That mapping is the part that can be wrong: aim it at the wrong slot and the operator tests a column the index does not key, retaining rows on a passenger column's NULLs and missing the key column's. A hand-written [0] agrees with a broken exemptSlotsFor on every fixture where the key column happens to be first.
The CALLER states the assertion, which is the whole point: this is the same affirmative claim cascadesGenerator mints after fetching a snapshot and finding every index strictly READABLE (readableIndexesFrom), so a caller using it is asserting the store condition rather than being handed it. Use it only where that condition is established independently — a freshly built store whose indexes were never transitioned. Everything the plain harness documents about executing its result applies here unchanged and with more force: this one CAN produce a plan whose correctness rests on an index's state.
func PlanRecordQueryWithMetadata ¶
func PlanRecordQueryWithMetadata(sql string, md *recordlayer.RecordMetaData, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
PlanRecordQueryWithMetadata is like PlanQueryWithMetadata but returns the physical RecordQueryPlan, not just its Explain string. This is a metadata-only TEST/HARNESS API: it has no record store from which to load mutable index state. A caller that executes the result against a real store must independently establish that EVERY secondary index in md is strictly READABLE and remains so through execution. Otherwise a metadata-only UNIQUE proof can survive even when the final plan contains no index leaf for the executor to reject. Live SQL must use cascadesGenerator, which snapshots, cache-keys, and revalidates authoritative store state. The session schema defaults to the embedded planner's "s".
The query's scalar subqueries are planned but NOT returned — fine for plan-only callers (Explain/shape assertions); a caller that EXECUTES a subquery-bearing plan without pre-binding results fails loudly with values.UnboundScalarSubqueryError. Executing callers use PlanRecordQueryWithSubqueries.
func PlanRecordQueryWithMetadataSchema ¶
func PlanRecordQueryWithMetadataSchema(sql string, md *recordlayer.RecordMetaData, schemaName string, stats properties.StatisticsProvider) (plans.RecordQueryPlan, error)
PlanRecordQueryWithMetadataSchema is PlanRecordQueryWithMetadata bound to a specific session schema (the real CONNECT schema on the session path — cascades_generator.go uses g.c.sess.Schema for the same threading). A non-default schema flows through NewPlanVisitorWithSchema AND the schema-qualified-table demotion/resolution, so a schema-qualified source — including INSIDE a subquery (`… EXISTS (SELECT 1 FROM PA AS main, main.PB AS B)` with session schema `main`) — is resolved against the ACTIVE schema, not the hardcoded default. RFC-142 (P2b). It remains a metadata-only harness and inherits the strictly-READABLE execution precondition above.
func ResultColumnDefsForPlan ¶
func ResultColumnDefsForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []executor.ColumnDef
ResultColumnDefsForPlan returns the FULL production ColumnDef set for a plan — the same deriveColumnsFromPlan output the live Execute() path hands to NewRecordLayerResultSet — so an FDB test can drive the REAL result-set read path (including the positional-aligned column read) for shapes not seeded through the SQL driver.
func ResultColumnLabelsForPlan ¶
func ResultColumnLabelsForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []string
ResultColumnLabelsForPlan returns the user-visible result-set column labels a plan would advertise — the metadata-only (no-FDB) analog of the driver's paginatingRows.Columns(): it runs the SAME production column derivation (deriveColumnsFromPlan, the function the live Execute() path calls) and maps each ColumnDef to its label exactly as Columns() does (Label, or Name when the label is empty), upper-cased. This lets the planner harness assert the result COLUMN SET — distinct from the per-row datum map (which carries extra resolution-convenience keys) — for shapes that cannot be seeded through the SQL driver (historically non-empty array columns, before SQL INSERT gained array literals). RFC-142.
func ResultColumnNullabilityForPlan ¶
func ResultColumnNullabilityForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []int
ResultColumnNullabilityForPlan returns the JDBC NULLABILITY flag advertised for each result-set column, in order — the metadata-only (no-FDB) analog of the driver's ResultSetMetaData.isNullable. It runs the SAME production column derivation (deriveColumnsFromPlan → ColumnDef.Nullable) the live Execute() path uses, so the harness can assert column nullability for shapes that cannot be seeded through the SQL driver (historically a lateral unnest over a non-empty array column, before SQL INSERT gained array literals). The WITH-ORDINALITY ordinal column must report api.ColumnNoNulls here (Java's INT NOT NULL ordinal), even though it has no backing proto descriptor field. RFC-142.
func ResultColumnTypesForPlan ¶
func ResultColumnTypesForPlan(plan plans.RecordQueryPlan, md *recordlayer.RecordMetaData) []string
ResultColumnTypesForPlan returns the SQL TYPE NAME advertised for each result-set column, in order — the metadata-only (no-FDB) analog of the driver's column-type metadata. It runs the SAME production column derivation (deriveColumnsFromPlan → ColumnDef.TypeName) the live Execute() path uses, so the harness can assert column types for shapes that cannot be seeded through the SQL driver (historically a lateral unnest over a non-empty array column, before SQL INSERT gained array literals). The element column of a non-ordinal unnest over a STRING array must report STRING here, not the UnknownType→BIGINT fallback. RFC-142.
Types ¶
type CorrelatedExistsError ¶
type CorrelatedExistsError struct {
}
CorrelatedExistsError is returned when buildCorrelatedExists fails. Detected via errors.As at the caller to propagate as ErrCodeUndefinedColumn for fallback to a richer outer scope.
Unsupported distinguishes a DELIBERATE decline of an unsupported correlated- EXISTS shape (an intentional CORRECT-or-CONSERVATIVE rejection — surfaced as 0A000 unsupported-operation) from a resolution failure that should read as an undefined column (42703). The WHERE-EXISTS and projected paths both key on this so a decline reports the same 0A000 in either position.
func (*CorrelatedExistsError) Error ¶
func (e *CorrelatedExistsError) Error() string
func (*CorrelatedExistsError) Unwrap ¶
func (e *CorrelatedExistsError) Unwrap() error
type EmbeddedConnection ¶
type EmbeddedConnection struct {
// contains filtered or unexported fields
}
EmbeddedConnection is an in-process SQL connection backed by FDB.
Implements driver.Conn and driver.ExecerContext so DDL statements can execute without a Prepare round-trip.
Transaction model:
Auto-commit: every statement runs in its own FDB transaction via fdbDB.Run(). Explicit transaction: BeginTx opens an FDB transaction; all statements in the transaction share it. Commit/Rollback close it.
func New ¶
func New( dbPath string, fdbDB *recordlayer.FDBDatabase, cat *catalog.RecordLayerStoreCatalog, factory apiddl.MetadataOperationsFactory, ks *keyspace.RelationalKeyspace, ) *EmbeddedConnection
New returns a ready-to-use embedded connection.
func (*EmbeddedConnection) ActiveTransactionTags ¶
func (c *EmbeddedConnection) ActiveTransactionTags() []string
ActiveTransactionTags reports the FDB transaction tags carried by the connection's currently-open explicit transaction, or nil when there is none.
Operational introspection: with OptTransactionTags driving per-tenant throttling, "which tag is this session actually running under" is a question an operator asks of a stuck connection, and the answer is otherwise invisible once the option has been translated into transaction state.
Returns nil on the libfdb_c backend, whose options surface is write-only and so cannot report tags back.
func (*EmbeddedConnection) Begin ¶
func (c *EmbeddedConnection) Begin() (driver.Tx, error)
Begin implements driver.Conn by delegating to BeginTx with default options.
func (*EmbeddedConnection) BeginTx ¶
BeginTx implements driver.ConnBeginTx. Opens an FDB transaction that spans all subsequent statements until Commit or Rollback is called. The accepted levels are LevelDefault and LevelSerializable; every other level returns an error. ReadCommitted is NOT among them — naming it here was a stale comment that advertised a weaker level this driver has never accepted, which is the opposite of the direction a reader needs to be wrong in. Read-only transactions are not separately enforced at the FDB level.
KNOWN GAP (database/sql ctx not honored): the caller's ctx is dropped here, and beginTransaction → NewFDBRecordContext pins context.Background() (recordlayer/ database.go), so a bounded ExecContext(ctx)/QueryContext(ctx) deadline never reaches the FDB read path. Against a wedged/unreachable cluster a read therefore retries forever (correct C++/Java behaviour — neither sets a default transaction timeout — but it means a caller deadline cannot interrupt it). Honoring ctx here needs care: database/sql ctx is per-statement, an FDB transaction timeout is whole-tx (anchored at creation, bounded by the 5s MVCC window), and txns are pooled — so it is a deliberate follow-up, not a drive-by. The client CAN be bounded today via tx.SetTimeout (see TestRead_BoundedByTimeout_NoHang); what's missing is the SQL layer wiring a caller deadline through to it.
func (*EmbeddedConnection) CheckNamedValue ¶
func (c *EmbeddedConnection) CheckNamedValue(nv *driver.NamedValue) error
CheckNamedValue implements driver.NamedValueChecker. Converts custom Go types to driver-compatible values before they reach substituteParams. Accepts: uuid.UUID → string (canonical 36-char form). All standard types (int64, float64, string, bool, []byte, time.Time) pass through unchanged.
func (*EmbeddedConnection) Close ¶
func (c *EmbeddedConnection) Close() error
Close marks the connection as closed and cancels any open FDB transaction.
func (*EmbeddedConnection) ExecContext ¶
func (c *EmbeddedConnection) ExecContext(ctx context.Context, sql string, args []driver.NamedValue) (res driver.Result, err error)
ExecContext executes SQL (DDL/DML/transaction) and returns the row- count result. Routes through cascadesGenerator in exec mode, which dispatches DML/DDL/transaction through execStatement and returns a Plan whose Execute aggregates RowsAffected across a multi-statement batch.
func (*EmbeddedConnection) GetDBPath ¶
func (c *EmbeddedConnection) GetDBPath() string
GetDBPath returns the current database path.
func (*EmbeddedConnection) GetSchema ¶
func (c *EmbeddedConnection) GetSchema() string
GetSchema returns the current schema label.
func (*EmbeddedConnection) IsValid ¶
func (c *EmbeddedConnection) IsValid() bool
IsValid implements driver.Validator. Returns true if the connection is open; the FDB client is stateless so a non-closed connection is always usable (catalog init is lazy, not a validity condition).
func (*EmbeddedConnection) Options ¶
func (c *EmbeddedConnection) Options() *api.Options
Options returns the connection's api.Options, or api.NoOptions() when none have been set. Used by the execution path to read the per-page scan-limit options and the statement-wide MAX_ROWS cap (RFC-106a).
func (*EmbeddedConnection) Ping ¶
func (c *EmbeddedConnection) Ping(ctx context.Context) error
Ping implements driver.Pinger. Bootstraps the catalog on first call.
func (*EmbeddedConnection) PlanExplain ¶
PlanExplain runs the SQL through the Cascades planner and returns the physical plan's Explain string without executing the query. Useful for testing plan structure (e.g. verifying sort elimination).
func (*EmbeddedConnection) Prepare ¶
func (c *EmbeddedConnection) Prepare(query string) (driver.Stmt, error)
Prepare returns a prepared statement. DDL statements have no bind parameters.
func (*EmbeddedConnection) PrepareContext ¶
PrepareContext implements driver.ConnPrepareContext.
func (*EmbeddedConnection) QueryContext ¶
func (c *EmbeddedConnection) QueryContext(ctx context.Context, sql string, args []driver.NamedValue) (rows driver.Rows, err error)
QueryContext handles read-only queries (SELECT / SHOW). Routes through the query.Generator seam. Rejects multi-statement batches and non-row-returning Plans — behaviour matches the pre-seam QueryContext.
func (*EmbeddedConnection) ResetSession ¶
func (c *EmbeddedConnection) ResetSession(_ context.Context) error
ResetSession implements driver.SessionResetter. Resets per-request state so pooled connections start clean:
- schema → defaultSchema (original CONNECT value)
- activeTx → rolled back (prevents a leaked transaction bleeding into the next checkout)
- schemaCache → cleared (schema evolution between checkouts would otherwise serve a stale descriptor)
func (*EmbeddedConnection) SetDefaultSchema ¶
func (c *EmbeddedConnection) SetDefaultSchema(s string)
SetDefaultSchema sets the initial schema that is restored by ResetSession. Called by the driver when the DSN contains ?schema=.
The value is an SQL identifier and normalizes like one: unquoted names fold to upper case, quoted names stay verbatim — the same rule execCreateSchema applies, so `?schema=test1` finds the schema `create schema /db/test1` created (both sides normalize to TEST1, Java's identifier model; the corpus pins both spellings: setup-with-connection-options.yamsql connects lower-case to a lower-case create, create-drop-create-template.yamsql upper-case to a lower-case create).
func (*EmbeddedConnection) SetExecutionStatsLogger ¶
func (c *EmbeddedConnection) SetExecutionStatsLogger(l ExecutionStatsLogger)
SetExecutionStatsLogger installs a post-execution statistics logger (RFC-211). Passing nil disables execution logging. Not safe to call concurrently with statement execution on the same connection (matches database/sql's per-Conn threading contract, same as SetPlanLogger).
func (*EmbeddedConnection) SetFailOnScanLimitReached ¶
func (c *EmbeddedConnection) SetFailOnScanLimitReached(v bool)
SetFailOnScanLimitReached toggles the Java setFailOnScanLimitReached(true) behavior (RFC-106a): when true a leaf cursor hitting a scan/byte limit errors (54F01) instead of paginating. Default false.
func (*EmbeddedConnection) SetMaxResultBytes ¶
func (c *EmbeddedConnection) SetMaxResultBytes(n int64)
SetMaxResultBytes sets the statement-wide returned-row byte cap (RFC-106a §5). A non-positive value disables it. The accounted size is the cheap tuple-encoded length of each returned row, not exact heap — a non-exact egress ceiling.
func (*EmbeddedConnection) SetOptions ¶
func (c *EmbeddedConnection) SetOptions(o *api.Options)
SetOptions installs the per-connection api.Options (RFC-106a scan-limit + MAX_ROWS wiring). Passing nil resets to defaults. Not safe to call concurrently with query execution on the same connection (matches database/sql's per-Conn threading contract).
func (*EmbeddedConnection) SetPlanLogger ¶
func (c *EmbeddedConnection) SetPlanLogger(l PlanGenerationLogger)
SetPlanLogger installs a planning-metrics logger (RFC-034). Passing nil disables planning logging. Not safe to call concurrently with query planning on the same connection (matches database/sql's per-Conn threading contract).
func (*EmbeddedConnection) SetSchema ¶
func (c *EmbeddedConnection) SetSchema(s string)
SetSchema sets the current schema label used when no schema is specified in SQL.
func (*EmbeddedConnection) SetSlowQueryThresholdMicros ¶
func (c *EmbeddedConnection) SetSlowQueryThresholdMicros(micros int64)
SetSlowQueryThresholdMicros sets the slow-query threshold in microseconds. A non-positive value disables the slow-query flag. The threshold governs both dimensions independently: PlanGenerationInfo.SlowQuery reports that PLANNING exceeded it, ExecutionStats.SlowQuery that EXECUTION did.
func (*EmbeddedConnection) SetStatementTimeout ¶
func (c *EmbeddedConnection) SetStatementTimeout(d time.Duration)
SetStatementTimeout sets the per-Execute wall-clock deadline (RFC-106a). A non-positive duration disables it. PER-REQUEST semantics: it bounds a single Execute (all its pages); a continuation resumed by a new request starts a fresh deadline. There is intentionally no `SET statement_timeout = …` SQL path — the parser grammar has no generic SET <var> = <val> rule (only SET TRANSACTION), so a grammar change would be required; this connection-field setter is the Go-local config instead (RFC-106a §3).
type ExecutionStats ¶
type ExecutionStats struct {
// SQL is the original whitespace-preserved query text, truncated to
// MaxLoggedSQLLength. Same source and same bound as PlanGenerationInfo.SQL,
// so the two records join on it.
SQL string
// PlanHash is the deterministic hash of the executed physical plan — the
// join key to the PlanGenerationInfo that produced it. A cached plan
// executes many times under one planning record, so the hash is what
// relates the N execution records back to the one plan.
PlanHash uint64
// ExecutionDuration is the wall-clock time from the start of Execute to
// the statement's completion — across every page, not just the first.
// Java's TOTAL_EXECUTE_QUERY (RelationalMetric.java:88), which
// AbstractEmbeddedStatement clocks around the same span.
ExecutionDuration time.Duration
// RowsReturned is the number of rows actually handed to the caller across
// all pages. DML reports RowsAffected instead and leaves this zero: a DML
// statement returns a count, not a result set, so it never passes through
// the row-returning path this counts.
RowsReturned int64
// RowsAffected is the number of records mutated by a DML statement, and is
// zero for a SELECT.
RowsAffected int64
// RecordsScanned is the total records charged against the scan limiter
// across every page AND every transaction retry. A retried attempt's scan
// is counted: the cluster served those reads, and cost attribution that
// hid them would understate exactly the tenants worth knowing about.
RecordsScanned int64
// BytesScanned is the total key+value bytes charged across every page and
// retry, on the same basis as RecordsScanned.
BytesScanned int64
// Pages is the number of page fetches this statement performed. Each page
// is its own transaction in auto-commit, so this is also the auto-commit
// transaction count before retries.
Pages int
// Retries is the number of transaction attempts beyond the first, summed
// over all pages. An explicit SQL transaction is never retried
// (runInCapturedTx calls the closure directly), so this is always 0 there.
Retries int
// SlowQuery is true when ExecutionDuration exceeded the connection's
// slow-query threshold — the same knob PlanGenerationInfo.SlowQuery reads,
// applied to the other half of the statement.
SlowQuery bool
// Err is the error that ended the statement, or nil on success. A
// statement killed by a scan limit reports its consumed counters here
// alongside the 54F01: the counters are charged per attempt on the way
// out, not read at a success-only checkpoint.
Err error
}
ExecutionStats is the diagnostic record emitted once per statement EXECUTION, the post-execution counterpart to PlanGenerationInfo (RFC-211).
It exists because a multi-tenant operator cannot attribute cluster load to a tenant from planning telemetry alone: PlanGenerationInfo reports what the planner decided, never what the statement then consumed.
The scanned counters are a port, not an invention. Java reads exactly these off the execution state after the fact — ExecuteState.getRecordsScanned() (ExecuteState.java:114) and getBytesScanned() (:122), backed by the mutable RecordScanLimiter/ByteScanLimiter pair (ExecuteState.java:44-48) that the caller owns and that therefore outlives the limit trip. Go's ScanLimiterState is that pair.
The per-statement AGGREGATION is the Go-only read-side extension: Java discards the counts at every ExecuteState.reset() (:74, the way each continuation segment gets a fresh budget) and surfaces nothing to a SQL caller at all — fdb-relational-api/jdbc/grpc carry no metric surface, and fdb-relational-core never reads getRecordsScanned() back. Nothing here touches the wire: these counters already existed to enforce limits, and this only reports them.
type ExecutionStatsLogger ¶
type ExecutionStatsLogger interface {
LogExecutionStats(ctx context.Context, stats ExecutionStats)
}
ExecutionStatsLogger receives one callback per statement execution. A nil logger is silent. Sampling and log-level policy are the handler's responsibility, exactly as for PlanGenerationLogger: the engine always emits, and the handler decides volume and sink.
This is deliberately a SECOND interface rather than a second method on PlanGenerationLogger. Java can grow MetricCollector because Java has default methods and uses them for precisely this (MetricCollector.java:78,89,99,107); Go has no equivalent, so adding a method would break every existing implementer at compile time. Java also keeps the two mechanisms apart — planning through PlanGenerator's finally block into RelationalLoggingUtil, execution through AbstractEmbeddedStatement's metricCollector.clock — and the lifetimes really do differ: a cached plan is planned once and executed many times, so planning and execution records do not pair up 1:1.
type PlanCache ¶
type PlanCache struct {
// contains filtered or unexported fields
}
PlanCache caches Cascades query plans keyed by normalized SQL text. Thread-safe for concurrent access. Uses an LRU eviction strategy with a configurable maximum size.
LRU order is tracked with a doubly-linked list (front = least recently used, back = most recently used) paired with a map from key to list element. Promotion on hit/update and eviction of the oldest entry are all O(1), matching Java's Caffeine-backed plan cache (RelationalPlanCache / MultiStageCache, which uses maximumSize LRU eviction). The previous slice-based order tracking linear-scanned on every hit — O(n) under the lock — which became a contention point at large cache sizes.
See RFC-029: keys on the full normalized SQL string to eliminate hash-collision correctness bugs (previously keyed on uint64 FNV-64a). See RFC-033: O(1) LRU via container/list.
func NewPlanCache ¶
NewPlanCache creates a plan cache with the given maximum number of entries. If maxSize <= 0, it defaults to 256.
func (*PlanCache) Get ¶
func (c *PlanCache) Get(scope, sql string) (plans.RecordQueryPlan, []PlannedScalarSubquery, bool)
Get looks up a cached plan. `scope` (schema identity + metadata version) is used VERBATIM — it must NOT be normalized, because schema names are case-sensitive and folding them would collide case-distinct schemas (`s` vs `S`) into one key, returning a plan built for the wrong schema. Only the `sql` is normalized (case-folded outside quotes, whitespace-collapsed, comments stripped). Returns the plan, scalar subquery bindings, and true on a cache hit; nil, nil, false on miss.
func (*PlanCache) Invalidate ¶
func (c *PlanCache) Invalidate()
Invalidate clears all cached entries. Must be called when schema metadata changes (DDL: CREATE/DROP TABLE, CREATE/DROP INDEX, etc.).
func (*PlanCache) Put ¶
func (c *PlanCache) Put(scope, sql string, plan plans.RecordQueryPlan, subs []PlannedScalarSubquery)
Put stores a plan keyed by (verbatim scope, normalized sql) — see Get for why the scope must not be normalized. If the cache is at capacity, the least recently used entry is evicted.
type PlanCacheEvent ¶
type PlanCacheEvent int
PlanCacheEvent classifies how the plan cache participated in a single Plan() call. Mirrors Java's RelationalLoggingUtil.PlanCacheEvent ({SKIP, HIT, MISS, INCONCLUSIVE}).
const ( // PlanCacheInconclusive is the zero value: planning errored before a // cache decision was reached (Java: INCONCLUSIVE). PlanCacheInconclusive PlanCacheEvent = iota // PlanCacheSkip means the plan was produced but deliberately not cached // (LIMIT/OFFSET query) or no cache is configured. PlanCacheSkip // PlanCacheHit means the plan was served from the cache. PlanCacheHit // PlanCacheMiss means the plan was freshly built and stored in the cache. PlanCacheMiss )
func (PlanCacheEvent) String ¶
func (e PlanCacheEvent) String() string
type PlanGenerationInfo ¶
type PlanGenerationInfo struct {
// SQL is the original whitespace-preserved query text (from the parse
// tree's token interval, not GetText()), truncated to MaxLoggedSQLLength.
SQL string
// PlanHash is the deterministic hash of the chosen physical plan tree,
// or 0 when no physical plan was produced (e.g. planning error).
PlanHash uint64
// PlanExplain is the plan's Explain() text, or "" when no plan was produced.
PlanExplain string
// PlanningDuration is the wall-clock time spent in this planning call.
PlanningDuration time.Duration
// Cache records how the plan cache participated.
Cache PlanCacheEvent
// CacheNumEntries is the plan cache's current size (Java:
// primaryCacheNumEntries), or 0 when no cache is configured.
CacheNumEntries int
// SlowQuery is true when PlanningDuration exceeded the connection's
// slow-query threshold.
SlowQuery bool
// Err is the planning error, or nil on success.
Err error
}
PlanGenerationInfo is the diagnostic record emitted once per Plan() call. The field set mirrors the keys Java adds to its KeyValueLogMessage in RelationalLoggingUtil.publishPlanGenerationLogs. There is deliberately no scalar "estimated cost": the Cascades cost model is a comparator, not a number — plan identity is PlanHash + PlanExplain, matching Java.
type PlanGenerationLogger ¶
type PlanGenerationLogger interface {
LogPlanGeneration(ctx context.Context, info PlanGenerationInfo)
}
PlanGenerationLogger receives one callback per Plan() call. A nil logger is silent. Sampling and log-level policy are the handler's responsibility: the engine always emits, and the handler decides volume and sink. (Java keeps the same split — the engine builds the record unconditionally; SLF4J level and any sampling live outside the planner.)
type PlanVisitor ¶
type PlanVisitor struct {
// contains filtered or unexported fields
}
PlanVisitor builds LogicalOperator trees from ANTLR parse nodes. It holds the metadata needed for catalog-aware resolution (predicate upgrade, column validation, sort-key resolution) and any CTE column schemas accumulated from WITH clause processing.
func NewPlanVisitor ¶
func NewPlanVisitor(md *recordlayer.RecordMetaData) *PlanVisitor
NewPlanVisitor creates a PlanVisitor with the given metadata, defaulting the session schema to the embedded planner's "s". md may be nil; all catalog-aware upgrades degrade to text fallback.
func NewPlanVisitorWithSchema ¶
func NewPlanVisitorWithSchema(md *recordlayer.RecordMetaData, schemaName string) *PlanVisitor
NewPlanVisitorWithSchema creates a PlanVisitor bound to a specific session schema (the real CONNECT schema on the session path). RFC-142.
func (*PlanVisitor) VisitQuery ¶
func (v *PlanVisitor) VisitQuery(q antlrgen.IQueryContext) (logical.LogicalOperator, error)
VisitQuery is the top-level entry point. It handles WITH (CTE) wrapping and then delegates to VisitQueryBody for the main query.
Mirrors buildLogicalPlanForQueryWithCatalog: pre-scans CTE definitions to extract column schemas, then recursively builds the main query body with CTE scopes in context.
func (*PlanVisitor) VisitQueryBody ¶
func (v *PlanVisitor) VisitQueryBody(body antlrgen.IQueryExpressionBodyContext) (logical.LogicalOperator, error)
VisitQueryBody dispatches simple SELECT vs UNION, threading metadata and CTE scopes through both arms.
func (*PlanVisitor) VisitQueryTerm ¶
func (v *PlanVisitor) VisitQueryTerm(qt antlrgen.IQueryTermContext) (logical.LogicalOperator, error)
VisitQueryTerm plans a bare queryTerm parse node — the shape a `CREATE INDEX … AS <queryTerm>` definition carries (RFC-202). It is the same planning path as VisitSimpleTable, entered without the enclosing queryExpressionBody wrapper a full query has. Mirrors Java's DdlVisitor.visitIndexAsSelectDefinition, where `indexDefinitionContext.queryTerm().accept(this)` reaches the ordinary query visitor (DdlVisitor.java:211).
func (*PlanVisitor) VisitSimpleTable ¶
func (v *PlanVisitor) VisitSimpleTable(termCtx *antlrgen.QueryTermDefaultContext) (logical.LogicalOperator, error)
VisitSimpleTable is the main SELECT visitor. It walks the ANTLR tree incrementally, building the LogicalOperator tree step by step in the same order as Java's QueryVisitor.visitSimpleTable:
- FROM clause → visitFrom → scan/derived/join operator
- WHERE clause → visitWhere → wrap with filter
- SELECT+GROUP BY+HAVING → visitSelectGroupBy → wrap with aggregate
- ORDER BY → visitOrderBy → wrap with sort (ANTLR direct)
- LIMIT/OFFSET → visitLimit → wrap with limit (ANTLR direct)
- Projection → visitFinalProjection + DISTINCT (ANTLR direct)
- Catalog-aware upgrades (inline) → predicate resolution, column validation, Value resolution for projections/aggregates/sort keys, qualified star expansion, EXISTS/scalar subquery planning.
Aggregate classification delegates to classifySelectElements which returns a selectClassification. When metadata is available, the classification is bridged to a selectQuery for the upgrade functions that consume it — the operator tree itself is built directly by the visit methods.
type PlannedScalarSubquery ¶
type PlannedScalarSubquery struct {
Alias values.CorrelationIdentifier
Plan plans.RecordQueryPlan
}
PlannedScalarSubquery pairs a scalar subquery's correlation alias with its planned physical plan. The executor pre-runs each plan (executor.EvaluateScalarSubquery) and binds the scalar result under the alias (EvaluationContext.WithScalarSubqueries) before running the outer plan — executing the outer plan without the binding fails loudly with values.UnboundScalarSubqueryError.
func PlanRecordQueryWithSubqueries ¶
func PlanRecordQueryWithSubqueries(sql string, md *recordlayer.RecordMetaData, stats properties.StatisticsProvider) (plans.RecordQueryPlan, []PlannedScalarSubquery, error)
PlanRecordQueryWithSubqueries is PlanRecordQueryWithMetadata plus the query's planned scalar subqueries — planned through planScalarSubqueryPlans, the same pipeline the production generator uses. Callers that execute the returned plan must pre-evaluate each subquery (executor.EvaluateScalarSubquery) and bind the results via EvaluationContext.WithScalarSubqueries, exactly as the sql driver's fetchPage does; running the outer plan without the bindings fails loudly with values.UnboundScalarSubqueryError (never a silent NULL that vanishes rows — the bug this API closed). The same all-secondary-indexes-strictly- READABLE execution precondition applies.
Source Files
¶
- cascades_generator.go
- colref.go
- connection.go
- ddl.go
- eval_map.go
- eval_predicate_map.go
- execution_logging.go
- index_name_guard.go
- index_onsource.go
- index_state_planning.go
- insert_cascades.go
- key_component_types.go
- logical_builder.go
- logical_predicate.go
- logical_qualify.go
- plan_cache.go
- plan_harness.go
- plan_logging.go
- plan_visitor.go
- planner_error.go
- planner_options.go
- query_hash.go
- scalar_functions.go
- scalar_subquery_planning.go
- select_helpers.go
- select_parser.go
- stmt.go
- subquery_walk.go
- system_rows.go
- system_tables.go
- tri_bool.go
- utilities.go
- value_compare.go
- where_extractors.go