testkit

package
v0.2.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const CensusGateSuiteName = "TestWholeCorpusCensusGates"

CensusGateSuiteName is the identity failing gates report under. It is a test-shaped name on purpose: it is what appears after `--- FAIL:`, so it is what a reader greps for and what a log scraper attributes the failure to.

View Source
const MhCols = "(id, a, b, c, s, f)"
View Source
const PlanCacheBindingShapesSchema = "CREATE TABLE T (id BIGINT, v BIGINT, PRIMARY KEY(id)) CREATE INDEX i_v ON T(v)"

PlanCacheBindingShapesSchema is the schema the binding-shape table runs over.

Variables

View Source
var (
	MhIntLits = []string{"-2", "-1", "0", "1", "2", "3", "10"}
	MhDblLits = []string{"-1.5", "-0.0", "0.0", "1.5", "2.25", "3.0"}
	MhStrLits = []string{"''", "'a'", "'ab'", "'b'", "'B'", "'abc'", "'z'"}
)
View Source
var Packages = []string{
	"pkg/relational/sqltest/agg",
	"pkg/relational/sqltest/agg/distinctelision",
	"pkg/relational/sqltest/catalog",
	"pkg/relational/sqltest/core",
	"pkg/relational/sqltest/cte",
	"pkg/relational/sqltest/dml",
	"pkg/relational/sqltest/join",
	"pkg/relational/sqltest/metamorphic",
	"pkg/relational/sqltest/metamorphic/aggregates",
	"pkg/relational/sqltest/metamorphic/commute",
	"pkg/relational/sqltest/metamorphic/compositepk",
	"pkg/relational/sqltest/metamorphic/direction",
	"pkg/relational/sqltest/metamorphic/exprequiv",
	"pkg/relational/sqltest/metamorphic/joins",
	"pkg/relational/sqltest/metamorphic/minmaxnulls",
	"pkg/relational/sqltest/metamorphic/norec",
	"pkg/relational/sqltest/metamorphic/paging",
	"pkg/relational/sqltest/metamorphic/parens",
	"pkg/relational/sqltest/metamorphic/predicates",
	"pkg/relational/sqltest/metamorphic/rewrites",
	"pkg/relational/sqltest/metamorphic/rowdiff",
	"pkg/relational/sqltest/metamorphic/shapeequiv",
	"pkg/relational/sqltest/plan",
	"pkg/relational/sqltest/plan/caphit",
	"pkg/relational/sqltest/plan/options",
	"pkg/relational/sqltest/planshape",
	"pkg/relational/sqltest/select",
	"pkg/relational/sqltest/subquery",
	"pkg/relational/sqltest/types",
}

Packages are the packages of the end-to-end suite. Each is a member of the whole-corpus census (//pkg/relational/sqltest:corpus); the sqltest guard holds the two lists equal, and Main refuses to run a package missing here, so a new package cannot silently drop out of the census.

View Source
var PlanCacheBindingShapes = []PlanCacheBindingShape{
	{"secondary", "SELECT id FROM T WHERE v=?", []any{int64(10)}, []any{int64(30)}, []int64{1}, []int64{3}, true},
	{"false then true", "SELECT id FROM T WHERE ? ORDER BY id", []any{false}, []any{true}, nil, []int64{1, 2, 3}, false},
	{"contradiction then range", "SELECT id FROM T WHERE v>=? AND v<=? ORDER BY id", []any{int64(30), int64(10)}, []any{int64(10), int64(30)}, nil, []int64{1, 2, 3}, true},
	{"case", "SELECT CASE WHEN ? THEN id ELSE id * 10 END FROM T ORDER BY id", []any{true}, []any{false}, []int64{1, 2, 3}, []int64{10, 20, 30}, false},

	{"coalesce exactness", "SELECT id FROM T WHERE COALESCE(?, 0) > 4 ORDER BY id", []any{int64(5)}, []any{int64(3)}, []int64{1, 2, 3}, nil, false},
	{"scalar subquery", "SELECT id FROM T WHERE v=(SELECT ? FROM T WHERE id=1)", []any{int64(10)}, []any{int64(30)}, []int64{1}, []int64{3}, true},
	{"join", "SELECT a.id FROM T a JOIN T b ON a.v=b.v WHERE b.id=?", []any{int64(1)}, []any{int64(3)}, []int64{1}, []int64{3}, true},
	{"join output", "SELECT a.id+? FROM T a JOIN T b ON a.v=b.v ORDER BY a.id", []any{int64(7)}, []any{int64(8)}, []int64{8, 9, 10}, []int64{9, 10, 11}, true},
	{"derived join output", "SELECT x.c FROM (SELECT id-? AS c FROM T) x JOIN T b ON x.c=b.id ORDER BY x.c", []any{int64(0)}, []any{int64(1)}, []int64{1, 2, 3}, []int64{1, 2}, true},
	{"in array", "SELECT id FROM T WHERE id IN ? ORDER BY id", []any{[]int64{1, 1}}, []any{[]int64{2, 3}}, []int64{1}, []int64{2, 3}, false},
	{"limit", "SELECT id FROM T ORDER BY id LIMIT ?", []any{int64(1)}, []any{int64(2)}, []int64{1}, []int64{1, 2}, false},
	{"equality partition", "SELECT id FROM T WHERE id=? OR id=? ORDER BY id", []any{int64(1), int64(1)}, []any{int64(2), int64(3)}, []int64{1}, []int64{2, 3}, false},
	{"additional equality", "SELECT ? + ? FROM T WHERE id=3", []any{int64(1), int64(2)}, []any{int64(4), int64(4)}, []int64{3}, []int64{8}, true},
	{"named", "SELECT id FROM T WHERE id=?target", []any{sql.Named("target", int64(1))}, []any{sql.Named("target", int64(3))}, []int64{1}, []int64{3}, true},
	{"null", "SELECT id FROM T WHERE ? IS NULL ORDER BY id", []any{nil}, []any{int64(1)}, []int64{1, 2, 3}, nil, false},
	{"numeric lane", "SELECT id FROM T WHERE v>? ORDER BY id", []any{int64(10)}, []any{float64(20.5)}, []int64{2, 3}, []int64{3}, false},
}

PlanCacheBindingShapes is shared with a fast-lane test that plans every query without FDB, so a shape that does not parse cannot hide in test-full.

Functions

func ArrayElementMessageDescriptor

func ArrayElementMessageDescriptor(fd protoreflect.FieldDescriptor) protoreflect.MessageDescriptor

ArrayElementMessageDescriptor returns the element MESSAGE descriptor of a struct-array column (through the NullableArrayWrapper when present).

func AsAPIError

func AsAPIError(err error) *api.Error

AsAPIError unwraps the err to *api.Error. Returns nil if the chain has no api.Error.

func AssertErrorCode

func AssertErrorCode(t *testing.T, db *sql.DB, sql string, wantCode api.ErrorCode)

AssertErrorCode runs the SQL, expects an error, and asserts the returned error's api.ErrCode* matches `wantCode`.

func AssertFloatLadderStored

func AssertFloatLadderStored(t *testing.T, db *sql.DB, ctx context.Context, tbl string)

AssertFloatLadderStored fails loudly if the ladder did not land as intended. Every assertion below is meaningless without it: if the write path silently rejected the non-finite values (or started rejecting them after a guard change), the test would be comparing two tables of ordinary finite doubles and would pass with the defect fully present.

func AssertPlannerCapHit

func AssertPlannerCapHit(t *testing.T, err error)

AssertPlannerCapHit pins the properties that distinguish an exhausted planning budget from every other planner failure: the cap sentinel survives as a cause, the SQLSTATE is the class-54 program-limit code (Go-only — Java's SQL layer never enables these caps, so there is no Java code to port), the user-visible message is an actionable "too complex" verdict rather than either "could not plan query" or the internal sentinel's wording, and the budget numbers reach the caller.

func AssertScanReadsBaseRecords

func AssertScanReadsBaseRecords(t *testing.T, plan, scanPrefix string)

AssertScanReadsBaseRecords asserts that the scan introduced by scanPrefix reads BASE RECORDS rather than answering from the index entry.

Checked on the scan's own label, not the whole plan, so a covering scan elsewhere in the tree cannot mask a regression here.

func AssertSerializationFailure

func AssertSerializationFailure(t *testing.T, err error)

func AssertUnsupported

func AssertUnsupported(t *testing.T, db *sql.DB, ctx context.Context, q string)

AssertUnsupported runs q and asserts it fails cleanly with ErrCodeUnsupportedQuery (0AF00) — NOT a silently-wrong cross product, and for EXPLAIN not a rendered plan for a query the engine cannot run.

func BuildChainedUnnestMetadata

func BuildChainedUnnestMetadata(t *testing.T) *recordlayer.RecordMetaData

BuildChainedUnnestMetadata constructs a RecordMetaData with a THREE-level nested struct/array shape for the class-4 CHAINED lateral unnest (`FROM t, t.arr AS x, x.sub AS y`). The record T4 carries:

ID       int64 (pk)
SARR     repeated ELEM     — struct-array (the first unnest's array)
SCARR    repeated int32    — SCALAR-array (a chained-owner-is-scalar decline)

ELEM     { SUB repeated int32; K int64; SUBSTRUCT repeated ELEM2 }
ELEM2    { DEEP repeated int32; LEAF int64 }

So a 2-chain (`T4.SARR AS x, x.SUB AS y`) unnests the struct-array element's own int-array SUB; a 3-chain (`… x.SUBSTRUCT AS y, y.DEEP AS z`) descends one struct level deeper — exercising chainedOwnerElementMessage's recursion. The SQL schema builder cannot express message/struct columns, so the proto is built dynamically (descriptorpb + protodesc.NewFile) exactly as the metadata builder does; records are genuine dynamicpb messages and the chained unnest runs the full Cascades path against real FDB.

func ClusterFile

func ClusterFile() string

ClusterFile returns the cluster file of the FoundationDB container Main started, or "" when Docker is unavailable.

func CollectRows

func CollectRows(t *testing.T, db *sql.DB, query string) [][]any

CollectRows runs a query and returns rows as [][]any.

func CountRows

func CountRows(t *testing.T, rows *sql.Rows) int

func DgcEq

func DgcEq(g, w []int64) bool

func DgcInts

func DgcInts(t *testing.T, db *sql.DB, ctx context.Context, q string, sortIt bool) []int64

func DgcOpen

func DgcOpen(t *testing.T, dbpath, tpl, ddl string) (*sql.DB, context.Context)

func DistinctEmailRun

func DistinctEmailRun(
	t *testing.T, ctx context.Context, conn *sql.Conn,
	logger *SyncCaptureLogger, query string,
) (string, []string)

DistinctEmailRun plans and runs one query on the logger-equipped conn and returns the plan text the planner actually chose alongside the rows, so an assertion can name both the shape and the answer. The plan is read from the planning event rather than from EXPLAIN because EXPLAIN is a second planning call: reading the event pins the plan that produced THESE rows.

func DupAliasSurfaceDB

func DupAliasSurfaceDB(t *testing.T, tag string) (*sql.DB, context.Context)

DupAliasSurfaceDB provisions the shared fixture. zn.id ∈ {1,2} and zp.pid ∈ {5,7,9} are disjoint ranges; zn.k runs WITH id while zp.w runs AGAINST pid, so neither a wrong leg nor a wrong column within a leg can tie.

func DusrvDrainTxErr

func DusrvDrainTxErr(ctx context.Context, tx *sql.Tx, q string) ([]string, error)

DusrvDrainTxErr is dusrvDrainTx that RETURNS its driver error, which is what a retried transaction body needs. A drain crosses many page boundaries by design, and every one of those pages is a place the whole-transaction budget pre-emption can arrive; fatalling there would end the test before the retry loop could classify the error.

func EqualRows

func EqualRows(a, b []string) bool

func EqualStrings

func EqualStrings(a, b []string) bool

func EvolIndexStates

func EvolIndexStates(t *testing.T, dbPath, schemaName string) map[string]recordlayer.IndexState

EvolIndexStates reads the schema's index-state subspace exactly as the planner does (recordlayer.LoadIndexStates, the same call fetchReadableIndexes makes). An empty map is "every index readable" as far as the planner knows.

func EvolReopen

func EvolReopen(t *testing.T, dbPath, schemaName string) *sql.DB

EvolReopen returns a FRESH *sql.DB, so the query below plans against the evolved metadata with nothing cached from before the rebind.

func ExistsGatherSchemaMetadata

func ExistsGatherSchemaMetadata(tb testing.TB) *recordlayer.RecordMetaData

ExistsGatherSchemaMetadata builds the A/B/EE/EEV schema shared by the row cert (FDB execution) and the plan-only sweep below. A(AID=1,K=100,ARR=[7,8]); B(BID=2,K=110) — A.K/B.K dup-named, the leg a qualified EXISTS correlation must disambiguate; EE(CK) is the leg-correlation table, EEV(VK) the element one.

func ExpectError

func ExpectError(t *testing.T, db *sql.DB, query string) error

func ExplainConn

func ExplainConn(t *testing.T, ctx context.Context, conn *sql.Conn, q string) string

ExplainConn returns the EXPLAIN plan text for q on the pinned connection, so the connection's api.Options are the ones in effect. EXPLAIN routes through the same planSelectCascades the real query path uses.

func ExplainPlan

func ExplainPlan(t *testing.T, ctx context.Context, db *sql.DB, q string) string

ExplainPlan returns the EXPLAIN PLAN text for q.

func ExplainPlanOnErr

func ExplainPlanOnErr(ctx context.Context, q DusrvQueryer, stmt string) (string, error)

ExplainPlanOnErr is explainPlanOn that RETURNS its driver error, for use inside a retried transaction body. An EXPLAIN issued on a transaction is a read page like any other and can be pre-empted for outliving the MVCC window.

func ExplainVia

func ExplainVia(t *testing.T, ctx context.Context, db *sql.DB, query string) string

ExplainVia retrieves the Cascades physical plan Explain string via the underlying EmbeddedConnection for the given query.

func Explainer

func Explainer(t *testing.T, db *sql.DB, ctx context.Context) func(string) string

func FleetIndexNames

func FleetIndexNames(idx []*recordlayer.Index) []string

func FleetOpen

func FleetOpen(t *testing.T, dbPath, schemaName string) *sql.DB

func FleetTargets

func FleetTargets(t *testing.T, h *FleetHarness, dbPath string) []fleet.Target

FleetTargets lists the fan-out targets for one database. Narrowed to the test's own database on purpose: the catalog subspace is shared by every test in this package, so an unfiltered listing would race with concurrent tests.

func FleetTemplate

func FleetTemplate(t *testing.T, name string, version int, withIndex bool) *metadata.RecordLayerSchemaTemplate

FleetTemplate builds template `name` at `version` over one table T(PK,C,V), optionally carrying a value index on C. v1 has no index; v2 adds it, which is the migration every test below performs.

func FleetVersions

func FleetVersions(t *testing.T, h *FleetHarness, dbPath string) map[string]int

FleetVersions maps schema name -> bound TEMPLATE_VERSION, read back from the catalog. This is the migration observable.

func FloatOrderingExplain

func FloatOrderingExplain(t *testing.T, db *sql.DB, ctx context.Context, q string) string

func FloatOrderingIDs

func FloatOrderingIDs(t *testing.T, db *sql.DB, ctx context.Context, q string) []int64

func FloatOrderingSameOrder

func FloatOrderingSameOrder(a, b []int64) bool

func GetInts

func GetInts(t *testing.T, ctx context.Context, db queryer, q string) []int64

GetInts runs a single-int-column query and returns the rows in order.

func GojDB

func GojDB(t *testing.T, tag string) (*sql.DB, context.Context)

gojDB sets up emp + dept for GROUP-BY-over-join tests. Groups by dept:

eng (did=1): Alice(100), Bob(90) → COUNT 2, MAX 100, SUM 190
sales (did=2): Charlie(80)       → COUNT 1, MAX 80,  SUM 80

func InstallLogger

func InstallLogger(t *testing.T, db *sql.DB, logger embedded.PlanGenerationLogger) *sql.Conn

InstallLogger pins a single *sql.Conn and installs a planning-metrics logger on its underlying EmbeddedConnection via Raw. The returned *sql.Conn must be used for all subsequent statements so the logger-equipped connection is the one that plans them.

func Main

func Main(m *testing.M)

func MhEqRows

func MhEqRows(a, b []string) bool

func MhFirstDiff

func MhFirstDiff(a, b []string) string

func MhHead

func MhHead(rows []string) []string

MhHead truncates a row list so one systemic divergence cannot bury the rest of the report under thousands of rows.

func MhRowLiteral

func MhRowLiteral(r *rand.Rand, id int) string

MhRowLiteral builds one fixture row.

func MhScanStrings

func MhScanStrings(ctx context.Context, db *sql.DB, q string) ([]string, error)

func MhcpkRowsOnConn

func MhcpkRowsOnConn(ctx context.Context, conn *sql.Conn, q string) ([]string, error)

MhcpkRowsOnConn is QueryRowStrings over a pinned *sql.Conn (the paged reader).

func MigrateFleet

func MigrateFleet(t *testing.T, h *FleetHarness, dbPath, tmplName string, opts fleet.Options) (fleet.Result, error)

MigrateFleet is the shared arrange step: save template v2 (adding the index) and rebind every schema, one transaction per schema.

func MmAggregateIndexRowsAgree

func MmAggregateIndexRowsAgree(idx, oracle []string, nAgg int) bool

MmAggregateIndexRowsAgree reports whether rows read through SUM / COUNT aggregate indexes (idx) are the answer Java's index gives for the records the unindexed twin reads (oracle). Both are QueryRowStrings output: one "|"-joined string per row, in the read's order, the last nAgg columns aggregates.

Java reads such an index alone (AggregateIndexMatchCandidate has no notion of group existence), so its answer differs from the records' in exactly three ways, and this relation admits those and nothing else:

  • a group whose rows were all deleted or moved away keeps its key at the atomic-add residue: an idx row the oracle does not have, every aggregate 0;
  • a group with no non-NULL value has no SUM or COUNT(col) entry, so the index (and an inner intersection over it) has no row where the oracle has one with a NULL SUM or a 0 COUNT(col);
  • a live group whose non-NULL values are all gone keeps its SUM at the residue 0 where the oracle's SUM is NULL.

Every other row must be equal, and in the same order.

func MmFirstDiff

func MmFirstDiff(got, want []string) string

func MmHeadRows

func MmHeadRows(rows []string) []string

MmHeadRows truncates a row list for a failure message.

func MmTrailingAggregates

func MmTrailingAggregates(q string) int

MmTrailingAggregates counts the aggregate calls in a read's select list (the text before its first FROM), which this file's callers project last.

func MustExec

func MustExec(t *testing.T, db interface {
	ExecContext(context.Context, string, ...any) (sql.Result, error)
}, ctx context.Context, q string,
)

func MustExecCtx

func MustExecCtx(t *testing.T, db execer, ctx context.Context, query string)

func MustHaveRetried

func MustHaveRetried(t *testing.T, attemptsRun int)

MustHaveRetried fails unless the injected spike actually pre-empted an attempt. Without it a converted test degrades silently into one that never exercises the retry — green, and proving nothing about the condition it was converted for.

func MwjoInsertRange

func MwjoInsertRange(t *testing.T, db execer, ctx context.Context, table string, lo, hi int, row func(i int) string)

MwjoInsertRange inserts row(i) for i in [lo, hi] as multi-row INSERTs of 100. For fixtures whose test reads only the loaded rows: one autocommit statement per row cost these probes most of their runtime. 500-row statements hit the 5s transaction limit (1007) on an overloaded box; 100 matches the other batched fixtures here.

func NewLateClock

func NewLateClock(lateBy time.Duration) *lateClock

func OpenDB

func OpenDB(t *testing.T, dbPath string) *sql.DB

openTestDB returns a *sql.DB wired to the test FDB container. Skips the test if Docker is not available.

func OpenSpiked

func OpenSpiked(t *testing.T, key, dbPath, schema string) *sql.DB

OpenSpiked opens a handle on a spiked backend and closes it with the test.

func OracleEq

func OracleEq(p *int64, v int64) bool

func OracleGe

func OracleGe(p *int64, v int64) bool

func OracleGt

func OracleGt(p *int64, v int64) bool

func OracleIn

func OracleIn(p *int64, vs ...int64) bool

func OracleInt

func OracleInt(v int64) *int64

func OracleLe

func OracleLe(p *int64, v int64) bool

func OracleLt

func OracleLt(p *int64, v int64) bool

func OracleVal

func OracleVal(p *int64) (int64, bool)

func PagedConn

func PagedConn(t *testing.T, db *sql.DB, budget int) *sql.Conn

PagedConn returns a connection in paginate mode with a small scanned-rows budget, so the engine crosses many internal page boundaries and resumes via continuations transparently.

func PinEmbeddedConn

func PinEmbeddedConn(t *testing.T, db *sql.DB, configure func(*embedded.EmbeddedConnection)) *sql.Conn

PinEmbeddedConn pins a single *sql.Conn and hands back its underlying *embedded.EmbeddedConnection so a test can install the RFC-106a connection-local config (options / fail-on-scan / statement timeout / result-byte cap). The returned *sql.Conn MUST be used for every subsequent statement so the configured connection is the one that executes them.

func PinExplain

func PinExplain(t *testing.T, db *sql.DB, ctx context.Context, q string) string

PinExplain returns the EXPLAIN output for q.

func PinRows

func PinRows(t *testing.T, db *sql.DB, ctx context.Context, q string) []string

PinRows runs q and renders every row as "v1|v2|...|vN" in scan order (callers sort when the query has no ORDER BY). A query error here is also the guard against the ordinal model's loud internal errors (OrdinalResolutionError / BakedNameContextError / OrdinalBakeError) — those surface as query failures, so err==nil proves none fired.

func PlanBindsBoundedScanOn

func PlanBindsBoundedScanOn(p plans.RecordQueryPlan, indexName string) (found, bounded bool)

PlanBindsBoundedScanOn reports whether the plan scans indexName with at least one non-empty comparison range — the property "the predicate reached the access path" as distinct from "a Fetch node is rendered".

func PlanUsesIndex

func PlanUsesIndex(p plans.RecordQueryPlan, indexName string) bool

PlanUsesIndex reports whether the plan scans indexName at all, seeing through covering wrappers.

func PositionalNamedPipeSprint

func PositionalNamedPipeSprint(r executor.QueryResult) string

PositionalNamedPipeSprint renders a row as "NAME=value" pairs joined by "|", in SLOT order.

It is the order-sensitive replacement for the OTHER blind form in this suite: the loop that collected a row map's keys, sorted them, and joined "k=v" pairs alphabetically. That form is blind twice over — a permutation of (Fields, Slots) re-sorts to the identical string, and positionalToMap has already collapsed any duplicate output name LAST-WINS before the sort ever runs, so a value is missing rather than merely misordered.

Keeping the names (rather than converting those sites to positionalPipeSprint's bare values) is deliberate: at those sites the name is what identifies which output column a value belongs to, and dropping it would force every expectation to be re-derived from the SELECT list instead of merely re-ordered. Slot order plus the names is strictly more information than either renderer alone.

func PositionalPipeSprint

func PositionalPipeSprint(r executor.QueryResult) string

PositionalPipeSprint renders a row as its slot values joined by "|", in SLOT order. It is the order-sensitive replacement for the sorted-map-key rendering: same separator, same per-value formatting, so a converted site's expectations change only where the positional order differs from the alphabetical one.

func PositionalSlots

func PositionalSlots(r executor.QueryResult) []any

PositionalSlots returns a row's slot VALUES in slot order, untyped and unrendered.

The renderers above cover assertions that compare a row as a STRING. The other consumer shape needs the value itself — a test that type-asserts a slot (`.(int64)`, `math.Signbit(...)`, a struct-field read) cannot go through a renderer. Those sites reached for the name-keyed map instead, which reintroduces the whole defect for a typed read: `row["G"]` resolves by name, so a permuted (Fields, Slots) pair still hands back G's own value, and a duplicate output name hands back whichever one survived the last-wins collapse.

It returns nil for a row with no positional row, so a caller that indexes it gets a bounds panic at the test rather than a silently wrong value.

func PositionalSprint

func PositionalSprint(r executor.QueryResult) string

PositionalSprint renders a row as its whole slot list ("[a b c]"), so the assertion pins slot ORDER as well as the values.

func PrebindScalarSubqueries

PrebindScalarSubqueries evaluates each planned scalar subquery against the store and returns an EvaluationContext with the results bound — the direct-executor analog of the sql driver fetchPage's pre-pass. Executing a subquery-bearing plan WITHOUT this fails loudly with values.UnboundScalarSubqueryError (never a silent NULL that vanishes rows), so every direct-harness test that runs SQL through embedded.PlanRecordQueryWithSubqueries + executor.ExecutePlan threads its subqueries through here.

func QueryIDs

func QueryIDs(t *testing.T, conn *sql.Conn, text string, args ...any) []int64

QueryIDs runs text on conn and returns its single BIGINT column.

func QueryIndexStateStrings

func QueryIndexStateStrings(t *testing.T, ctx context.Context, conn *sql.Conn, query string) []string

QueryIndexStateStrings runs query INSIDE AN EXPLICIT TRANSACTION, which is the only regime in which a secondary-UNIQUE proof is drawn at all.

The proof is a statement about the store at one instant, so it is licensed only where the whole result comes from a single read version; in auto-commit each page takes a fresh one and the proof is withheld. Every arm reached through this helper — the affirmative ones that require `narrowed-by:U_EMAIL` and the refusals that require its ABSENCE — therefore has to run in a transaction. The refusals are the reason it matters most: in auto-commit no index proves anything, so "a READABLE_UNIQUE_PENDING index licensed nothing" would hold for a reason that has nothing to do with the index's state.

func QueryInt64s

func QueryInt64s(ctx context.Context, db *sql.DB, query string) ([]int64, error)

func QueryLabels

func QueryLabels(t *testing.T, sql string, md *recordlayer.RecordMetaData) []string

QueryLabels are the labels the driver reports for sql's result columns, upper-cased for comparison.

func QueryRowStrings

func QueryRowStrings(t *testing.T, ctx context.Context, db *sql.DB, q string) ([]string, error)

QueryRowStrings runs q and renders each row as a |-joined string so a case can state its expectation without knowing the column count. NULL renders as "NULL", which is distinct from the empty string a NULL-free empty column produces.

func RFC128DB

func RFC128DB(t *testing.T, tag string) (*sql.DB, context.Context)

RFC128DB seeds a single-column table t(id) with ids 1..10 and returns a *sql.DB bound to it. Each call uses a unique db/template name (tag) for t.Parallel isolation.

func ReadIDPairs

func ReadIDPairs(t *testing.T, ctx context.Context, db *sql.DB, q string) [][2]int64

func ReadIDPairsConn

func ReadIDPairsConn(t *testing.T, ctx context.Context, conn *sql.Conn, q string) [][2]int64

func ReadWorkers

func ReadWorkers() int

ReadWorkers is how many reads ReadAll runs at once: GOMAXPROCS, since the cost of a sweep statement is planner CPU. Sharing the box with other tests is Bazel's job (the target's resources:cpu tag), not a cap here.

func RelationalStoreSubspace

func RelationalStoreSubspace(t *testing.T, dbPath, schema string) subspace.Subspace

RelationalStoreSubspace is the record-store subspace of a schema the driver created: Java's (domain, database, schema) longs, looked up (never interned) in the cluster under test. Names are the ones CREATE DATABASE / CREATE SCHEMA stored (an unquoted path or schema folds to upper case).

func RequireErrContains

func RequireErrContains(t *testing.T, err error, want string)

RequireErrContains asserts the statement failed with a message carrying want — used where the Java-verbatim WORDING is the contract.

func RequireSQLSTATE

func RequireSQLSTATE(t *testing.T, err error, want api.ErrorCode)

RequireSQLSTATE unwraps err to *api.Error and asserts that the SQLSTATE code matches want. Use after expectError for Java-conformance tests where the exact SQLSTATE is known.

func RetryTx

func RetryTx(t *testing.T, b txBeginner, o TxRetryOpts, body func(TxAttempt) error)

RetryTx runs body inside an explicit transaction, restarting the WHOLE transaction when it is pre-empted for outliving its MVCC window.

Restarting the whole thing is the only thing that CAN work and also the only thing that stays correct. A fresh transaction takes a fresh read version, which is the only way the window problem goes away; and because body re-runs in full, everything it established — an uncommitted INSERT, a DELETE whose clears a later read must see — is re-established INSIDE the new transaction rather than smuggled across the boundary. A retry that resumed mid-body would be asserting read-your-writes against writes made by a transaction that no longer exists.

It deliberately retries NOTHING else. A genuine write conflict is the same SQLSTATE and is somebody else's business; retrying it here would hide a real isolation failure behind a loop. That is the whole reason the predicate is api.IsTransactionTimeLimit and not a 40001 comparison.

body owns its own COMMIT. Tests that must commit call a.tx.Commit() as their last act; the Rollback below is then a no-op. Tests that must not commit simply return, and the rollback discards the attempt.

ASSERTIONS INSIDE body MUST STILL FATAL. Only DRIVER errors are returned for the retry to classify; a failed assertion is a verdict, not a transient, and returning it would hand a real bug to the retry loop to paper over.

func RunCensusGates

func RunCensusGates(w io.Writer, verbose bool, gates []CensusGate) bool

RunCensusGates runs every gate and renders the outcome in go test's own output format, returning whether any failed.

Passing gates still have their prose emitted verbatim — several report something useful when they pass (that a -test.run filter narrowed the corpus, or the population they reached), and swallowing that would trade one silent failure mode for another.

Failing gates are rendered as a parent failure with one subtest per gate, matching what `go test` prints for a table test:

--- FAIL: TestWholeCorpusCensusGates (0.00s)
    --- FAIL: TestWholeCorpusCensusGates/exampleGate (0.00s)
        <the gate's own reasoning, indented>

The parent line sits at column 0 so `grep -c '^--- FAIL'` finds it, which is the specific query that used to return 0 on a failing run.

func RunPlanCacheBindingShapes

func RunPlanCacheBindingShapes(t *testing.T, db *sql.DB)

RunPlanCacheBindingShapes executes every shape twice on one pinned connection over db (PlanCacheBindingShapesSchema, empty) and checks rows and plan-cache reuse.

func RunShape

func RunShape(t *testing.T, ctx context.Context, db *sql.DB, q string) string

RunShape renders a query's column names and rows as one comparable string, scanning into `any` so a struct-valued cell is REPORTED rather than converted into a scan error. That is deliberate: scanning into int64 turns the defect into a type complaint, and the point of this matrix is to show what a client that does not demand a type silently receives.

func ScanInt64Rows

func ScanInt64Rows(t *testing.T, ctx context.Context, conn *sql.Conn, q string) []int64

ScanInt64Rows drains q into a slice of the first column's values.

func ScanLabel

func ScanLabel(plan, scanPrefix string) (string, bool)

ScanLabel extracts the single scan label beginning at scanPrefix, up to its closing paren, so an assertion applies to that scan and not the whole plan.

func ScanRowStrings

func ScanRowStrings(t *testing.T, rows *sql.Rows) []string

func SeedFloatOrderingLadder

func SeedFloatOrderingLadder(t *testing.T, db *sql.DB, ctx context.Context, tbl string)

SeedFloatOrderingLadder writes the ladder into tbl with a constant `a` so an equality on the index's leading column binds and leaves the float as the leading SORTED coordinate.

func SetArrayField

func SetArrayField(m *dynamicpb.Message, fd protoreflect.FieldDescriptor, vals ...protoreflect.Value)

SetArrayField sets an ARRAY column on a dynamic message regardless of the stored shape (flat repeated vs NullableArrayWrapper — nullable arrays are stored wrapped, Java's wire shape). Element values for struct arrays must be built from arrayElementMessageDescriptor(fd), not fd.Message(), because fd.Message() is the wrapper for nullable struct arrays.

func SetupCascadesTestDB

func SetupCascadesTestDB(t *testing.T) (*sql.DB, *sql.DB)

func SetupErrorDB

func SetupErrorDB(t *testing.T, dbPath, schemaName, ddl string) *sql.DB

SetupErrorDB creates a fresh database + schema template + schema and returns a *sql.DB wired into that schema. Same shape as the happy-path tests' setup, factored so the error tests can share it.

func SetupPlanShapeDB

func SetupPlanShapeDB(t *testing.T, suffix, templateDDL string) *sql.DB

SetupPlanShapeDB creates a fresh database + schema for a plan-shape subtest. Returns a *sql.DB connected to the created schema.

func SiRenderRow

func SiRenderRow(t *testing.T, rows *sql.Rows) string

SiRenderRow renders the row the cursor is currently positioned on, whatever its arity and column types, as "v1|v2|...|vN".

func SortStrings

func SortStrings(s []string)

func SortedRowStrings

func SortedRowStrings(t *testing.T, db *sql.DB, ctx context.Context, q string) []string

SortedRowStrings runs q and returns each row rendered by fmt, sorted, so an expected multiset of rows compares as one string.

func SpikedClusterKey

func SpikedClusterKey(t *testing.T, lateBy time.Duration) (string, *lateClock)

SpikedClusterKey registers a REAL FoundationDB-backed database whose record layer measures elapsed time on a one-shot late clock, and returns the key to pass as `cluster_file=` in a DSN.

Returning a KEY rather than an *sql.DB is deliberate: the tests below build several handles on one backend (a setup handle, a schema-scoped handle, a second connection that must observe isolation from outside), and they must all reach the SAME registered database or the isolation assertions would be comparing two unrelated stores.

Autocommit is unaffected by the spike, which is what makes this safe to arm for a whole test: preflightTxBudget runs under `if r.tx != nil`, so DDL and seed statements issued outside an explicit transaction never meet it.

func SpikedDSN

func SpikedDSN(key, dbPath, schema string) string

SpikedDSN is the DSN form the converted tests use, so the cluster_file key is the ONLY difference from the handle they opened before conversion.

func UnnestEqualStrs

func UnnestEqualStrs(a, b []string) bool

func UnnestSprint

func UnnestSprint(v any) string

Types

type CensusGate

type CensusGate struct {
	Name string
	Run  func(io.Writer) bool
}

CensusGate is one whole-corpus assertion: a name, and a check that writes its reasoning to the writer and reports whether it FAILED.

type DusrvQueryer

type DusrvQueryer interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

DusrvQueryer is satisfied by *sql.DB, *sql.Conn and *sql.Tx alike.

type EvolHarness

type EvolHarness struct {
	Cat *catalog.RecordLayerStoreCatalog
	Ks  *keyspace.RelationalKeyspace
	// contains filtered or unexported fields
}

EvolHarness wires a catalog over the same keyspace the sqldriver uses, so a template evolved here is the template the driver plans against. The evolution path itself is catalog-level (SaveSchemaTemplate at a higher version, then RepairSchema to rebind) because the SQL surface has no statement that rebinds a live schema — CREATE SCHEMA TEMPLATE only writes the template.

func NewEvolHarness

func NewEvolHarness(t *testing.T) *EvolHarness

func (*EvolHarness) MustRun

func (h *EvolHarness) MustRun(t *testing.T, what string, fn func(txn api.Transaction) error)

type FleetHarness

func NewFleetHarness

func NewFleetHarness(t *testing.T) *FleetHarness

func (*FleetHarness) MustRun

func (h *FleetHarness) MustRun(t *testing.T, what string, fn func(txn api.Transaction) error)

type GojRow

type GojRow struct {
	Dname string
	Cnt   int64
	Mx    int64
}

func GojRead

func GojRead(t *testing.T, ctx context.Context, db *sql.DB, q string) []GojRow

type IndexStatePlanningFixture

type IndexStatePlanningFixture struct {
	DB  *sql.DB
	RDB *recordlayer.FDBDatabase

	Ss subspace.Subspace
	// contains filtered or unexported fields
}

func NewIndexStatePlanningFixture

func NewIndexStatePlanningFixture(t *testing.T) *IndexStatePlanningFixture

NewIndexStatePlanningFixture builds a schema with one secondary UNIQUE index and a second, byte-identical metadata handle reached OUTSIDE the SQL layer. The out-of-band handle is what lets a test drive an index through a state transition mid-statement, which is not expressible in SQL.

func (*IndexStatePlanningFixture) MakeUniqueIndexPending

func (f *IndexStatePlanningFixture) MakeUniqueIndexPending(ctx context.Context) error

MakeUniqueIndexPending drives U_EMAIL from READABLE to READABLE_UNIQUE_PENDING: the state that is SCANNABLE but whose declared uniqueness the data contradicts (IndexState.java:59-66). It is the sharpest transition available, because a check written against scannability rather than Java's isReadable would not notice it.

func (*IndexStatePlanningFixture) SetIndexDisabled

func (f *IndexStatePlanningFixture) SetIndexDisabled(ctx context.Context, name string) error

SetIndexDisabled drives an index to DISABLED, the state SetIndexState cannot reach. DISABLED and WRITE_ONLY are both non-READABLE but they are not the same state — DISABLED additionally clears the index data — and a check written against "not readable" must hold for both.

func (*IndexStatePlanningFixture) SetIndexState

func (f *IndexStatePlanningFixture) SetIndexState(
	ctx context.Context, name string, readable bool,
) error

SetIndexState drives any index of the fixture's schema to a state, out of band, so a test can move an index the SQL layer is not looking at.

func (*IndexStatePlanningFixture) WriteGhostIndexState

func (f *IndexStatePlanningFixture) WriteGhostIndexState(ctx context.Context, name string) error

WriteGhostIndexState plants a state key for an index name the METADATA does not have. This is not a contrived state: an index-state key outlives the index whose name it holds whenever metadata is evolved to drop an index without vacuuming its state key, and nothing in the read path removes it.

type MhGen

type MhGen struct {
	R *rand.Rand
	// IntCols/DblCols/StrCols/BoolCols default (when nil) to the hunt-1 fixture.
	IntCols, DblCols, StrCols, BoolCols []string
}

MhGen generates random predicates. The column names are parameters so the same generator can drive fixtures with different schemas; a generator that emits a column the table does not have produces a 42703 on BOTH sides, which is an EMPTY comparison, not a passing one.

func (*MhGen) Atom

func (g *MhGen) Atom(depth int) string

func (*MhGen) CmpOp

func (g *MhGen) CmpOp() string

func (*MhGen) Dbls

func (g *MhGen) Dbls() []string

func (*MhGen) Ints

func (g *MhGen) Ints() []string

func (*MhGen) Pick

func (g *MhGen) Pick(ss []string) string

func (*MhGen) Pred

func (g *MhGen) Pred(depth int) string

func (*MhGen) Strs

func (g *MhGen) Strs() []string

type OracleRow

type OracleRow struct {
	ID      int64
	A, B, C *int64
	S       *string
	V       *int64
}

func OracleGenRows

func OracleGenRows(n int) []OracleRow

func (OracleRow) InsertSQL

func (r OracleRow) InsertSQL() string

type PlanCacheBindingShape

type PlanCacheBindingShape struct {
	Name, Query           string
	First, Second         []any
	WantFirst, WantSecond []int64
	Hit                   bool
}

PlanCacheBindingShape runs Query with First then Second over rows (1,10), (2,20), (3,30); Hit says whether the second execution reuses the plan.

type Read

type Read struct {
	DB  *sql.DB
	SQL string
}

Read is one read-only statement against one handle, for ReadAll.

type ReadResult

type ReadResult struct {
	Rows []string
	Err  error
}

ReadResult is one Read's answer, as the scan function returned it.

func ReadAll

func ReadAll(ctx context.Context, reads []Read, scan func(context.Context, *sql.DB, string) ([]string, error)) []ReadResult

ReadAll runs reads concurrently on up to ReadWorkers goroutines and returns the answers in the order the reads were given. A statement repeated in the list (same handle, same SQL) runs once and every occurrence gets its answer.

It is for metamorphic sweeps whose statements are generated independently of each other's answers: the caller generates first (keeping its random draw sequence), reads everything here, then checks in its original order, so counts, samples and failure output are the serial ones. Only statements over a fixture that does not change between them may share one call.

type SyncCaptureLogger

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

SyncCaptureLogger is a concurrency-safe PlanGenerationLogger for tests.

func (*SyncCaptureLogger) LogPlanGeneration

func (l *SyncCaptureLogger) LogPlanGeneration(_ context.Context, info embedded.PlanGenerationInfo)

func (*SyncCaptureLogger) Snapshot

type Twin

type Twin struct {
	Idx   *sql.DB
	Plain *sql.DB
	T     *testing.T
	Ctx   context.Context
}

mmTwin is a pair of connections to two schemas over the same table shapes: idx has the indexes under test, plain has none.

func NewTwin

func NewTwin(t *testing.T, ctx context.Context, dbPath, templatePrefix, tableDDL, indexDDL string) *Twin

mmNewTwin creates database dbPath with two schemas built from the same table DDL: `si` additionally carries indexDDL, `sn` carries nothing. Both are returned open.

tableDDL and indexDDL are raw schema-template fragments ("CREATE TABLE ... " / "CREATE INDEX ... "), concatenated as the relational DDL expects.

func (*Twin) Exec

func (w *Twin) Exec(stmt string)

Exec runs stmt against BOTH schemas. A statement that succeeds on one side and fails on the other is itself a finding, so the asymmetry is checked before the error is reported.

func (*Twin) Explain

func (w *Twin) Explain(q string) string

Explain returns the rendered plan for q on the INDEXED side. Used to prove a case actually reaches the operator under test — without it, a green is a statement about whichever plan the cost model happened to pick.

func (*Twin) ExplainInTx

func (w *Twin) ExplainInTx(q string) string

ExplainInTx is Explain taken inside an EXPLICIT TRANSACTION, and the two are not interchangeable: the secondary-UNIQUE distinct proof is licensed only where the whole result comes from ONE read version, which an explicit transaction provides and auto-commit does not — in auto-commit each page takes a fresh read version, a value can move between pages and be emitted twice, so the proof is deliberately withheld.

A test that reads a plan in auto-commit and expects an elision therefore sees the UN-elided plan and is asserting the wrong thing about a correct engine.

func (*Twin) Sub

func (w *Twin) Sub(t *testing.T) *Twin

Sub rebinds the twin to a SUBTEST's *testing.T, sharing the same two connections.

Without it a twin built in the parent keeps reporting against the parent, so a `t.Run` subtest whose every query failed still prints `--- PASS` and only the parent turns red. That is the reporting failure this repo names as its dominant false positive, wearing its most convincing face: the failures ARE printed and the parent IS red, so nothing is lost from a full log — but the per-subtest verdict, which is what a reader scans and what a CI summary surfaces, says the opposite of what happened. Observed live: with the IN-list flatten mutated out, `in_list_items` reported PASS while all nine of its queries returned 0AF00.

t.Run("name", func(t *testing.T) { w := w.Sub(t); w.Want(…) })

func (*Twin) Want

func (w *Twin) Want(name, q string, want []string)

Want asserts the SQL-correct answer on BOTH schemas.

It checks three things and reports them separately, because they fail for different reasons: the unindexed side wrong means the scan/executor is wrong, the indexed side wrong means the index path is wrong, and the two disagreeing with each other localizes it to the index path even when `want` itself is in doubt.

func (*Twin) WantAggregateIndex

func (w *Twin) WantAggregateIndex(name, q string, wantIndexed, wantRecords []string)

WantAggregateIndex asserts a read served by SUM / COUNT aggregate indexes: the indexed schema answers wantIndexed, Java's index answer, and the unindexed one wantRecords, and the two differ only as mmAggregateIndexRowsAgree allows. A read where they are equal uses Want.

func (*Twin) WantInTx

func (w *Twin) WantInTx(name, q string, want []string)

WantInTx is Want with both sides read inside their own explicit transaction, so the single-read-version proofs are licensed. Use it for any case whose point is an optimization gated on one read version; use Want otherwise.

func (*Twin) WantKnownDivergence

func (w *Twin) WantKnownDivergence(name, q string, wantIndexed, wantOracle []string, why string)

WantKnownDivergence pins a divergence that is KNOWN and not yet repaired.

It asserts three things, and the third is the point: the unindexed answer is the correct one, the indexed answer is the specific wrong one produced today, and the two still DISAGREE. The last arm is what makes the pin self-retiring — the moment the defect is fixed this test fails and says so, rather than quietly continuing to describe a state that no longer exists.

This is not a way to accept a wrong answer. It is how a wrong answer stays visible while the fix it needs is decided, and every use carries `why`.

func (*Twin) WantPlanContains

func (w *Twin) WantPlanContains(name, q, marker string)

WantPlanContains fails unless the indexed plan contains marker. A row assertion that silently stopped exercising the operator under test is a green that proves nothing, so every case whose point is an index-backed operator pins the operator too.

func (*Twin) WantRejected

func (w *Twin) WantRejected(name, q, wantCode string)

WantRejected asserts that BOTH schemas refuse q with the same SQLSTATE.

Rejection is part of the answer, so it belongs to the twin invariant like any other: an index may change the PLAN, it may never change whether a query is ACCEPTED. A shape that errors unindexed and plans indexed (or the reverse) is a defect in index matching even though no row was ever compared, and nothing else here would catch it — Want() reports a query that failed on both sides as a single "query failed" line and moves on.

The code is compared, not the message: wording is free to differ between the two paths, a SQLSTATE is not.

type TxAttempt

type TxAttempt struct {
	Tx  *sql.Tx
	Num int
}

TxAttempt is what RetryTx hands each attempt. The attempt number is exposed because a test that injects a one-shot spike must be able to assert the spike actually pre-empted something — a retry that never fired proves nothing.

type TxRetryOpts

type TxRetryOpts struct {
	// Attempts bounds the retry. Bounded on purpose: an explicit transaction
	// whose work genuinely cannot fit in FDB's five-second window fails every
	// attempt, and looping forever converts a legible failure into a hang.
	Attempts int

	// BeforeAttempt runs immediately before each attempt begins its
	// transaction, including the first. It exists for state a re-run would
	// otherwise double-count — a metrics scrape being the concrete case: an
	// abandoned attempt still charged its writes to the exporter, so a test
	// asserting an exact counter must re-zero it per attempt or the retry turns
	// a precise assertion into an inequality nobody wrote.
	BeforeAttempt func(attempt int)

	// OnRetry runs after an attempt was pre-empted, before the next begins.
	OnRetry func(attempt int, err error)
}

TxRetryOpts configures RetryTx. The zero value is usable: three attempts, no hooks.

func SpikeOnce

func SpikeOnce(clk *lateClock, ranAttempt *int) TxRetryOpts

SpikeOnce returns retry options that end a one-shot clock spike after the first pre-emption, plus a pointer that records the attempt the body last ran.

The one-shot lifetime is the point rather than a convenience. A spike that never ends cannot distinguish "the retry works" from "the retry is missing", because both end red; a spike lasting exactly one attempt reddens only when the retry is absent. It is the same lifetime as the chaos harness's InjectOnce.

Source Files

Jump to

Keyboard shortcuts

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