recordlayer

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

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

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 54 Imported by: 0

Documentation

Index

Constants

View Source
const (
	FunctionNameCount        = "count"
	FunctionNameCountNotNull = "count_not_null"
	FunctionNameCountUpdates = "count_updates"
	FunctionNameSum          = "sum"
	FunctionNameMinEver      = "min_ever"
	FunctionNameMaxEver      = "max_ever"
	FunctionNameMin          = "min"
	FunctionNameMax          = "max"

	// RANK aggregate function names.
	FunctionNameRankForScore         = "rank_for_score"
	FunctionNameScoreForRank         = "score_for_rank"
	FunctionNameScoreForRankElseSkip = "score_for_rank_else_skip"
	FunctionNameCountDistinct        = "count_distinct"

	// BITMAP_VALUE aggregate function name.
	FunctionNameBitmapValue = "bitmap_value"
)

Aggregate function name constants matching Java's FunctionNames.

View Source
const (
	CollateFuncJRE = "collate_jre"
	CollateFuncICU = "collate_icu"
)

Collation function names matching Java's CollateFunctionKeyExpressionFactory. Both JRE and ICU variants are registered since Go uses golang.org/x/text/collate (CLDR-based, similar to ICU).

NOTE: Collation key bytes are NOT wire-compatible with Java's. Java uses java.text.CollationKey.toByteArray() (JRE) or com.ibm.icu.text.CollationKey.toByteArray() (ICU), whose binary formats are Java/ICU-version-specific. Go's collation keys use golang.org/x/text's CLDR-based format. This means: - Go can read/write its own collated indexes correctly - Go preserves collated index definitions during metadata round-trip - Go CANNOT share collated indexes with Java (different sort key bytes)

View Source
const (
	CollateStrengthPrimary   = 0 // Base form only (ignores case and accents)
	CollateStrengthSecondary = 1 // Base form + accents (case-insensitive)
	CollateStrengthTertiary  = 2 // Base form + accents + case
)

Collation strength levels matching Java's Collator.PRIMARY/SECONDARY/TERTIARY.

View Source
const (
	// StoreInfoKey is the subspace key for store metadata
	StoreInfoKey = 0

	// RecordKey is the subspace key for storing records
	RecordKey = 1

	// IndexKey is the subspace key for storing indexes
	IndexKey = 2

	// IndexSecondarySpaceKey is the subspace key for secondary index data
	IndexSecondarySpaceKey = 3

	// RecordCountKey is the subspace key for record counts
	RecordCountKey = 4

	// IndexStateSpaceKey is the subspace key for index state
	IndexStateSpaceKey = 5

	// IndexRangeSpaceKey is the subspace key for index ranges
	IndexRangeSpaceKey = 6

	// IndexUniquenessViolationsKey is the subspace key for uniqueness violations
	IndexUniquenessViolationsKey = 7

	// RecordVersionKey is the subspace key for record versions
	RecordVersionKey = 8

	// IndexBuildSpaceKey is the subspace key for index building
	IndexBuildSpaceKey = 9

	// IndexSlidingWindowSpaceKey is the subspace key for a sliding-window
	// (top-N) index's bookkeeping: the per-partition entry list and the
	// count/boundary metadata that decide which records are inside the window
	// and therefore present in the wrapped vector index.
	// Matches Java's FDBRecordStoreKeyspace.INDEX_SLIDING_WINDOW_SPACE.
	//
	// Java keeps this separate from the SECONDARY space deliberately — its own
	// comment says "to avoid collisions with delegate index types that also use
	// the secondary subspace (e.g. rank, permuted min/max, text)" — and Go now
	// writes here for the same reason, from slidingWindowIndexMaintainer, which
	// decorates a VECTOR index carrying a row-number window predicate.
	//
	// Note the time-window LEADERBOARD index is a different type and does NOT use
	// this space in either engine: Java's TimeWindowLeaderboardIndexMaintainer
	// uses getSecondarySubspace() at every call site, which is what Go does too.
	IndexSlidingWindowSpaceKey = 10
)

Subspace keys used by the Record Layer to organize data within FDB. These MUST match the Java implementation for compatibility. Verified against Java: FDBRecordStoreKeyspace.java (enum values 0-10).

The enum is Java's to EXTEND — it is @API(UNSTABLE) and has grown before — so every value it defines is reserved here even when Go writes nothing to it. An unreserved prefix is one nothing is guarding: the next feature that wants a place to put bytes inside a record store would take it and collide with a Java release. TestRecordStoreKeyspaceMatchesJava pins the whole set.

View Source
const (
	IndexTypeValue                 = "value"
	IndexTypeCount                 = "count"
	IndexTypeCountNotNull          = "count_not_null"
	IndexTypeCountUpdates          = "count_updates"
	IndexTypeSum                   = "sum"
	IndexTypeMaxEverLong           = "max_ever_long"
	IndexTypeMinEverLong           = "min_ever_long"
	IndexTypeMaxEverTuple          = "max_ever_tuple"
	IndexTypeMinEverTuple          = "min_ever_tuple"
	IndexTypeRank                  = "rank"
	IndexTypeVersion               = "version"
	IndexTypeMaxEverVersion        = "max_ever_version"
	IndexTypePermutedMin           = "permuted_min"
	IndexTypePermutedMax           = "permuted_max"
	IndexTypeBitmapValue           = "bitmap_value"
	IndexTypeText                  = "text"
	IndexTypeTimeWindowLeaderboard = "time_window_leaderboard"
	IndexTypeMultidimensional      = "multidimensional"
	IndexTypeVector                = "vector"
	// IndexTypeVectorSPFresh is the Go-only FDB-native vector index (RFC-094):
	// SPANN centroid + posting-list layout with two-level routing and SPFresh
	// incremental rebalancing. Java has no counterpart; deployments sharing
	// metadata with Java apps must keep this index out of shared metadata (a
	// Java app fails maintainer lookup for the unknown type). Records remain
	// fully Java-readable — the index writes only under its own subspace.
	IndexTypeVectorSPFresh = "vector_spfresh"

	// IndexTypeMinEver and IndexTypeMaxEver are the DEPRECATED bare spellings
	// that predate the _long/_tuple split. Java still accepts them and says
	// exactly why it cannot stop: "Deprecated for new usage, but this can't
	// really be removed because old meta-data might include it"
	// (IndexTypes.java:67-81). They are declared here for the same reason —
	// Java-authored metadata on a shared cluster may carry them, and this port
	// has to READ that metadata correctly.
	//
	// Java resolves each to its _LONG behaviour UNCONDITIONALLY:
	// getAtomicMutation returns MIN_EVER_LONG for `MIN_EVER_LONG || MIN_EVER` and
	// MAX_EVER_LONG for `MAX_EVER_LONG || MAX_EVER`
	// (AtomicMutationIndexMaintainer.java:100-106). There is no dependence on the
	// key type, and the doc comment on IndexTypes.MIN_EVER says the same thing
	// declaratively — "Compatible name for MIN_EVER_LONG".
	//
	// Never WRITE these. New metadata uses the _long or _tuple spelling; these
	// exist so old and Java-authored metadata resolves to the right maintainer
	// instead of falling off the end of a switch.
	IndexTypeMinEver = "min_ever"
	IndexTypeMaxEver = "max_ever"
)

Index type constants matching Java's IndexTypes.

View Source
const (
	IndexOptionUnique               = "unique"
	IndexOptionClearWhenZero        = "clearWhenZero"
	IndexOptionReplacedByPrefix     = "replacedBy"
	IndexOptionBitmapValueEntrySize = "bitmapValueEntrySize"

	// TEXT index options matching Java's IndexOptions.
	IndexOptionTextTokenizerName               = "textTokenizerName"
	IndexOptionTextTokenizerVersion            = "textTokenizerVersion"
	IndexOptionTextAddAggressiveConflictRanges = "textAddAggressiveConflictRanges"
	IndexOptionTextOmitPositions               = "textOmitPositions"

	// Runtime-only index option, always safe to change.
	// Matches Java's IndexOptions.ALLOWED_FOR_QUERY_OPTION.
	IndexOptionAllowedForQuery = "allowedForQuery"
)

Index option keys matching Java's IndexOptions.

View Source
const (
	IndexOptionRTreeMaxM   = "rtreeMaximumM"
	IndexOptionRTreeMinM   = "rtreeMinimumM"
	IndexOptionRTreeSplitS = "rtreeSplitS"

	// IndexOptionRTreeStorage controls the node storage strategy.
	// Matches Java's IndexOptions.RTREE_STORAGE.
	IndexOptionRTreeStorage = "rtreeStorage"

	// IndexOptionRTreeStoreHilbertValues controls whether Hilbert values are stored.
	// Matches Java's IndexOptions.RTREE_STORE_HILBERT_VALUES.
	IndexOptionRTreeStoreHilbertValues = "rtreeStoreHilbertValues"

	// IndexOptionRTreeUseNodeSlotIndex controls whether a node slot index is maintained.
	// Matches Java's IndexOptions.RTREE_USE_NODE_SLOT_INDEX.
	IndexOptionRTreeUseNodeSlotIndex = "rtreeUseNodeSlotIndex"
)

R-tree index option keys for configuring the Hilbert R-tree.

View Source
const (
	OrderFuncAscNullsFirst  = "order_asc_nulls_first"
	OrderFuncAscNullsLast   = "order_asc_nulls_last"
	OrderFuncDescNullsFirst = "order_desc_nulls_first"
	OrderFuncDescNullsLast  = "order_desc_nulls_last"
)

Order function names matching Java's OrderFunctionKeyExpressionFactory. Each name maps to a TupleOrdering.Direction encoding strategy.

View Source
const (
	IndexOptionRankNLevels         = "rankNLevels"
	IndexOptionRankHashFunction    = "rankHashFunction"
	IndexOptionRankCountDuplicates = "rankCountDuplicates"
)

Index option keys for RANK indexes. Matches Java's IndexOptions.RANK_*.

View Source
const (
	FunctionNameRank                   = "rank"
	FunctionNameTimeWindowRank         = "time_window_rank"
	FunctionNameTimeWindowRankAndEntry = "time_window_rank_and_entry"
)

Record function name constants matching Java's FunctionNames.

View Source
const (
	// VersionBytes is the total size of a record version.
	VersionBytes = 12
	// GlobalVersionBytes is the size of the FDB versionstamp portion.
	GlobalVersionBytes = 10
	// LocalVersionBytes is the size of the local version counter.
	LocalVersionBytes = 2
)
View Source
const (
	MaxRecordContextTags   = 5
	MaxRecordContextTagLen = 16
)

Java's tag limits (FDBRecordContextConfig.java:661-672). These are STRICTER than the FDB client's own 5/255 (TagThrottle.actor.cpp:35-39): the record layer caps a tag at 16 characters, so a tag the client would accept can still be rejected here. Validation happens at config time, as in Java, so a bad tag fails before any transaction is opened rather than mid-commit.

View Source
const (
	// IsolationLevelSnapshot uses snapshot reads, which see a consistent view of the database
	// at the time the transaction started. Snapshot reads do not conflict with writes.
	//
	// Java equivalent: SNAPSHOT
	IsolationLevelSnapshot IsolationLevel = iota

	// IsolationLevelSerializable uses serializable reads, which participate in conflict detection.
	// Serializable reads will cause conflicts if another transaction writes to the same keys.
	//
	// Java equivalent: SERIALIZABLE
	IsolationLevelSerializable

	// Legacy aliases for backwards compatibility
	SnapshotIsolation     = IsolationLevelSnapshot
	SerializableIsolation = IsolationLevelSerializable
)
View Source
const (
	// IndexOptionSPFreshNumDimensions is the vector dimensionality. Required.
	IndexOptionSPFreshNumDimensions = "spfreshNumDimensions"
	// IndexOptionSPFreshMetric is the distance metric (EUCLIDEAN_METRIC,
	// COSINE_METRIC, DOT_PRODUCT_METRIC — same names the HNSW index accepts).
	IndexOptionSPFreshMetric = "spfreshMetric"
	// IndexOptionSPFreshLmax is the posting-list split threshold in entries.
	// Sized so one posting fits a single range-reply (REPLY_BYTE_LIMIT = 80 KB).
	IndexOptionSPFreshLmax = "spfreshLmax"
	// IndexOptionSPFreshLminRatio divides Lmax to produce the merge threshold.
	IndexOptionSPFreshLminRatio = "spfreshLminRatio"
	// IndexOptionSPFreshCellTarget is the fine-centroids-per-cell build target;
	// sized so one L2 cell load fits a single range-reply.
	IndexOptionSPFreshCellTarget = "spfreshCellTarget"
	// IndexOptionSPFreshCellMax is the coarse-split threshold in fine centroids.
	IndexOptionSPFreshCellMax = "spfreshCellMax"
	// IndexOptionSPFreshReplication is the closure replication cap r.
	IndexOptionSPFreshReplication = "spfreshReplication"
	// IndexOptionSPFreshAlpha is the RNG closure threshold: keep centroid c_i of
	// the r nearest iff dist(v,c_i) <= alpha * dist(v,c_1). Must be > 1.0 or
	// only the nearest centroid is ever admitted (effective r=1).
	IndexOptionSPFreshAlpha = "spfreshAlpha"
	// IndexOptionSPFreshKn is the NPA reassignment neighborhood (centroids).
	IndexOptionSPFreshKn = "spfreshKn"
	// IndexOptionSPFreshBuildAssignCells is the bulk-build wave-B assignment
	// width w_b (RFC-099): how many nearest coarse cells supply candidate fine
	// centroids when assigning an imported vector. Build-time only — it changes
	// which fine a vector is assigned to, never the on-disk format. Must be ≥
	// the query probe width so build assignments are query-reachable.
	IndexOptionSPFreshBuildAssignCells = "spfreshBuildAssignCells"
	// IndexOptionSPFreshCooldownSec is the post-split merge cooldown.
	IndexOptionSPFreshCooldownSec = "spfreshCooldownSec"
	// IndexOptionSPFreshRaBitQNumExBits is the RaBitQ extended-bits parameter
	// for posting residual codes.
	IndexOptionSPFreshRaBitQNumExBits = "spfreshRaBitQNumExBits"
	// IndexOptionSPFreshSidecar enables the fp16 SIDECAR subspace. Default
	// true — and currently REQUIRED: the sidecar is not just the query
	// re-rank source, every rebalancer lifecycle reads it (split 2-means,
	// chunked drain, merge drain, GC re-home), so disabling it would brick
	// maintenance permanently. ValidateSPFreshConfig rejects false until a
	// source-record fallback exists for all of those paths. The option stays
	// (the wire layout reserves the choice); only the value is constrained.
	IndexOptionSPFreshSidecar = "spfreshSidecar"
)

SPFresh index options (RFC-094 §10). All structural options are immutable for an existing index — enforced by the metadata-evolution validator — because the lifecycle invariants (topology, posting sizes, closure replication) are derived from them. Runtime knobs (probe width w, k_c, ε, re-rank C, refresh interval, rebalancer pacing) are deliberately NOT index options: they are query/maintenance-time parameters and are never stored.

View Source
const (
	FunctionNameTimeWindowCount                = "time_window_count"
	FunctionNameScoreForTimeWindowRank         = "score_for_time_window_rank"
	FunctionNameScoreForTimeWindowRankElseSkip = "score_for_time_window_rank_else_skip"
	FunctionNameTimeWindowRankForScore         = "time_window_rank_for_score"
)

Aggregate function name constants for TIME_WINDOW_LEADERBOARD.

View Source
const AllTimeLeaderboardType = 0

AllTimeLeaderboardType is the type value for the all-time leaderboard. Matches Java's TimeWindowLeaderboard.ALL_TIME_LEADERBOARD_TYPE.

View Source
const DefaultCollectScannedBytesLimit int64 = 16 << 20

DefaultCollectScannedBytesLimit is the per-batch read bound when CollectOptions.ScannedBytesLimit is zero. Exported for the same reason as DefaultCollectTimeLimit. Generous enough that an ordinary batch never reaches it, small enough that a batch of split multi-hundred-KB records stops long before the time limit would have to.

View Source
const DefaultCollectTimeLimit = 3 * time.Second

DefaultCollectTimeLimit is the per-batch time bound when CollectOptions.TimeLimit is zero. It leaves room under FDB's 5s transaction limit for the read-version fetch and the commit around the scan itself.

Exported so an operator-facing description of collection can RENDER the bound rather than restate it: a help text carrying a hand-typed "3s" goes stale the first time this changes, and says so to the one reader who cannot check it.

View Source
const DefaultMaterializationLimit = 100_000
View Source
const DefaultTextTokenizerName = "default"

DefaultTextTokenizerName is the name of the default tokenizer.

View Source
const FunctionNameCardinality = "cardinality"

FunctionNameCardinality is the registry name for the CARDINALITY() key expression. Matches Java's FunctionNames.CARDINALITY ("cardinality") byte for byte — the proto Function.name written for a cardinality-keyed index is identical across engines, so a Go-written cardinality index round-trips wire-compatibly with Java.

View Source
const (
	FunctionNameVersion = "version"
)

Store function name constants matching Java's FunctionNames.

View Source
const GlobalMinVersion = 0

GlobalMinVersion is the absolute minimum tokenizer version. All tokenizers should begin at this version. Matches Java's TextTokenizer.GLOBAL_MIN_VERSION.

View Source
const GroupCountCompanionSuffix = "__GROUP_COUNT"

GroupCountCompanionSuffix is reserved on user-supplied index names. The DDL derives a companion COUNT(*) index's name by appending it to the owning index's name, so the name is deterministic: two stores built from the same DDL must contain the same stored bytes, which a randomized or counter-based name would break.

Reserving it narrows the DDL surface relative to Java, which accepts any name. That is deliberate and is the lesser of two evils: without reservation, create-if-absent could collide with a pre-existing user index that carries the derived name but a different structure, and the user would see a duplicate-name error at schema-creation time rather than at the point they chose the name.

The reservation applies at CREATE ONLY, never at load. A schema that already contains an index whose name ends with the suffix — written by Java, or by an older Go binary — must open and read normally. Companion matching is structural and the name carries no semantic weight, so such a name is inert at read time and there is nothing to reject; rejecting at load would let this design render an existing Java-written schema unreadable.

View Source
const IndexOptionHNSWEfConstruction = "hnswEfConstruction"

IndexOptionHNSWEfConstruction specifies the search factor used during index construction. Matches Java's IndexOptions.HNSW_EF_CONSTRUCTION.

View Source
const IndexOptionHNSWEfRepair = "hnswEfRepair"

IndexOptionHNSWEfRepair specifies the search factor used during repair operations. Matches Java's IndexOptions.HNSW_EF_REPAIR.

View Source
const IndexOptionHNSWM = "hnswM"

IndexOptionHNSWM specifies the connectivity factor M for the HNSW graph. Matches Java's IndexOptions.HNSW_M.

View Source
const IndexOptionHNSWMMax = "hnswMMax"

IndexOptionHNSWMMax specifies the maximum number of connections for non-zero layers. Matches Java's IndexOptions.HNSW_M_MAX.

View Source
const IndexOptionHNSWMMax0 = "hnswMMax0"

IndexOptionHNSWMMax0 specifies the maximum number of connections for layer 0. Matches Java's IndexOptions.HNSW_M_MAX_0.

View Source
const IndexOptionHNSWMaintainStatsProbability = "hnswMaintainStatsProbability"

IndexOptionHNSWMaintainStatsProbability controls the probability of maintaining stats. Runtime-only option, safe to change without rebuild. Matches Java's IndexOptions.HNSW_MAINTAIN_STATS_PROBABILITY.

View Source
const IndexOptionHNSWMaxNumConcurrentDeleteFromLayer = "hnswMaxNumConcurrentDeleteFromLayer"

IndexOptionHNSWMaxNumConcurrentDeleteFromLayer controls the maximum number of concurrent layer deletions during deletion of a record. Stored for Java round-trip compatibility. Matches Java's IndexOptions.HNSW_MAX_NUM_CONCURRENT_DELETE_FROM_LAYER.

View Source
const IndexOptionHNSWMaxNumConcurrentNeighborhoodFetches = "hnswMaxNumConcurrentNeighborhoodFetches"

IndexOptionHNSWMaxNumConcurrentNeighborhoodFetches controls the maximum number of concurrent neighborhood fetches during insert when neighbors are pruned. Stored for Java round-trip compatibility. Matches Java's IndexOptions.HNSW_MAX_NUM_CONCURRENT_NEIGHBORHOOD_FETCHES.

View Source
const IndexOptionHNSWMaxNumConcurrentNodeFetches = "hnswMaxNumConcurrentNodeFetches"

IndexOptionHNSWMaxNumConcurrentNodeFetches controls the maximum number of concurrent node fetches during search and modification operations. In Go's synchronous model this is not used for concurrency control, but is stored for Java round-trip compatibility. Matches Java's IndexOptions.HNSW_MAX_NUM_CONCURRENT_NODE_FETCHES.

View Source
const IndexOptionHNSWRaBitQNumExBits = "hnswRaBitQNumExBits"

IndexOptionHNSWRaBitQNumExBits specifies the number of extra bits for RaBitQ. Matches Java's IndexOptions.HNSW_RABITQ_NUM_EX_BITS.

View Source
const IndexOptionHNSWSampleVectorStatsProbability = "hnswSampleVectorStatsProbability"

IndexOptionHNSWSampleVectorStatsProbability controls the probability of sampling vector stats. Runtime-only option, safe to change without rebuild. Matches Java's IndexOptions.HNSW_SAMPLE_VECTOR_STATS_PROBABILITY.

View Source
const IndexOptionHNSWStatsThreshold = "hnswStatsThreshold"

IndexOptionHNSWStatsThreshold specifies the minimum number of vectors for stats. Runtime-only option, safe to change without rebuild. Matches Java's IndexOptions.HNSW_STATS_THRESHOLD.

View Source
const IndexOptionHNSWUseInlining = "hnswUseInlining"

IndexOptionHNSWUseInlining controls whether vector data is inlined into the HNSW node. Matches Java's IndexOptions.HNSW_USE_INLINING.

View Source
const IndexOptionHNSWUseRaBitQ = "hnswUseRaBitQ"

IndexOptionHNSWUseRaBitQ enables RaBitQ quantization for approximate nearest neighbor. Matches Java's IndexOptions.HNSW_USE_RABITQ.

View Source
const IndexOptionPermutedSize = "permutedSize"

IndexOptionPermutedSize specifies how many trailing grouping fields are permuted to after the value in the secondary index subspace. Matches Java's IndexOptions.PERMUTED_SIZE_OPTION.

View Source
const IndexOptionVectorExtendCandidates = "hnswExtendCandidates"

IndexOptionVectorExtendCandidates controls whether the candidate set is extended with neighbors-of-neighbors during neighbor selection (2nd-degree exploration). Matches Java's IndexOptions.HNSW_EXTEND_CANDIDATES.

View Source
const IndexOptionVectorKeepPrunedConnections = "hnswKeepPrunedConnections"

IndexOptionVectorKeepPrunedConnections controls whether pruned candidates are added back to fill up to M neighbors when the heuristic selection produces too few. Matches Java's IndexOptions.HNSW_KEEP_PRUNED_CONNECTIONS.

View Source
const IndexOptionVectorMetric = "hnswMetric"

IndexOptionVectorMetric specifies the distance metric. Matches Java's IndexOptions.HNSW_METRIC.

View Source
const IndexOptionVectorNumDimensions = "hnswNumDimensions"

IndexOptionVectorNumDimensions specifies the number of vector dimensions. Matches Java's IndexOptions.HNSW_NUM_DIMENSIONS.

View Source
const StatisticsFormatVersion = 1

StatisticsFormatVersion is written with every entry so a reader can refuse a layout it does not understand rather than misparse one. Bump it when the tuple below changes shape; readers reject anything they do not recognise.

Variables

View Source
var (
	CountSWItemAddedToWindowFilling  = Event{"sw_item_added_to_window_filling", "item added to window while filling up", KindCount}
	CountSWItemAddedToEntriesOnly    = Event{"sw_item_added_to_entries_only", "item worse than boundary added to entries", KindCount}
	CountSWDeleteUntracked           = Event{"sw_delete_untracked", "delete called for untracked record", KindCount}
	CountSWOverflowEntryDeleted      = Event{"sw_overflow_entry_deleted", "overflow entry deleted from entries", KindCount}
	CountSWWindowEntryDeleted        = Event{"sw_window_entry_deleted", "in-window non-boundary entry deleted", KindCount}
	CountSWItemPromotedFromOverflow  = Event{"sw_item_promoted_from_overflow", "item promoted from overflow into window", KindCount}
	CountSWWindowShrunkNoOverflow    = Event{"sw_window_shrunk_no_overflow", "window shrunk: no overflow available for re-election", KindCount}
	CountSWPartitionEmptied          = Event{"sw_partition_emptied", "partition emptied (no entries remain)", KindCount}
	CountSWEvictedRecordMissing      = Event{"sw_evicted_record_missing", "boundary record could not be loaded for eviction", KindCount}
	CountSWPromotedRecordMissing     = Event{"sw_promoted_record_missing", "overflow record could not be loaded for promotion", KindCount}
	CountSWPreemptiveDeleteWriteOnly = Event{"sw_preemptive_delete_write_only", "preemptive delete during write-only index build", KindCount}
	CountSWPartitionCleared          = Event{"sw_partition_cleared", "partition cleared via deleteWhere", KindCount}

	EventSWEvictAndReplace           = Event{"sw_evict_and_replace", "evict boundary and insert better entry", KindTimed}
	EventSWReElectFromOverflow       = Event{"sw_re_elect_from_overflow", "re-elect overflow entry into window", KindTimed}
	EventSWBoundaryRescanAfterEvict  = Event{"sw_boundary_rescan_after_evict", "rescan to locate new boundary after eviction", KindTimed}
	EventSWBoundaryRescanAfterDelete = Event{"sw_boundary_rescan_after_delete", "rescan to locate new boundary after boundary delete", KindTimed}
	EventSWDelegateInsert            = Event{"sw_delegate_insert", "insert into the delegate index", KindTimed}
	EventSWDelegateDelete            = Event{"sw_delegate_delete", "delete from the delegate index", KindTimed}

	SizeSWWindowCount = Event{"sw_window_count", "window count after update", KindSizeDistribution}
)

Instrumentation for the sliding window, matching Java's SlidingWindowIndexMaintainer.SlidingWindowCounter / SlidingWindowEvent / SlidingWindowSizeEvent enums (:722-792) name for name.

View Source
var (
	// Query path.
	EventSPFreshSearch = Event{"spfresh_search", "SPFresh Search", KindTimed}
	// Probed/pruned: the Eq.(3) pruning decomposition per search — probed
	// lists cost range reads, pruned ones were skipped.
	CountSPFreshPostingsProbed  = Event{"spfresh_postings_probed", "SPFresh Postings Probed", KindCount}
	CountSPFreshPostingsPruned  = Event{"spfresh_postings_pruned", "SPFresh Postings Pruned", KindCount}
	CountSPFreshEntriesScanned  = Event{"spfresh_entries_scanned", "SPFresh Entries Scanned", KindCount}
	CountSPFreshRerankReads     = Event{"spfresh_rerank_reads", "SPFresh Rerank Reads", KindCount}
	CountSPFreshStarvationWiden = Event{"spfresh_starvation_widenings", "SPFresh Starvation Widenings", KindCount}
	CountSPFreshForwardFollows  = Event{"spfresh_forward_follows", "SPFresh Forward Follows", KindCount}
	// Phase 2 reached: the VBASE relaxed-monotonicity termination (M_q^s > R_q)
	// latched during a one-shot search's traversal — the recently-traversed cells
	// no longer beat the running k-th best (RFC-156 Phase A). PURE TELEMETRY on
	// the one-shot wrapper path: nothing consults f.phase2 as a stop signal (it
	// does not truncate the exact horizon). The resumable Phase B/C streaming
	// cursor does NOT consult phase2 — it terminates on the relaxed-monotonicity
	// EMISSION BARRIER plus the budget/exhaustion caps (spfresh_stream.go), and
	// it never calls refreshPhase2, so this counter NEVER increments on a streaming
	// query (the SHARED searchInit probe still feeds observe(), but the streaming
	// WIDEN bursts gate it off — scoreCells, f.reranked==nil — since those queues
	// are dead weight once nothing will read phase2).
	CountSPFreshPhase2Reached = Event{"spfresh_phase2_reached", "SPFresh Phase 2 Reached", KindCount}
	// Capped posting reads: a search's posting fetch returned exactly the
	// 4×Lmax+1 cap — the posting is PAST the split-dispatch envelope and its
	// tail is invisible to queries. Nonzero means a split trigger was lost
	// (the read path re-files it; see CountSPFreshReadPathSplitFiles).
	CountSPFreshCappedPostingReads = Event{"spfresh_capped_posting_reads", "SPFresh Capped Posting Reads", KindCount}
	// Split tasks re-filed from the read path after a capped read found an
	// over-envelope posting with no pending split.
	CountSPFreshReadPathSplitFiles = Event{"spfresh_readpath_split_files", "SPFresh Read-Path Split Files", KindCount}
	// Stream widen batches: each demand-driven widening step of the RFC-156
	// Phase C ordered-stream cursor (a batch of ε-pruned/re-routed cells admitted
	// in d2 order because the consumer above drained the finalized prefix and
	// pulled for more). Batched, never one-cell-serial.
	CountSPFreshStreamWiden = Event{"spfresh_stream_widenings", "SPFresh Stream Widenings", KindCount}
	// Filtered truncation: the RFC-156 Phase C ordered-stream cursor hit its
	// budget cap (max cells probed / max candidates) BEFORE the consumer was
	// satisfied, and returned NoNextReason.ScanLimitReached + a positional
	// continuation rather than a silent < k. This is telemetry IN ADDITION to the
	// reason (RFC-156 §C) — the reason is the contract, this counts
	// how often the budget bound a filtered KNN.
	CountSPFreshFilteredTruncated = Event{"spfresh_filtered_truncated", "SPFresh Filtered Truncations", KindCount}

	// Write path.
	EventSPFreshInsert           = Event{"spfresh_insert", "SPFresh Insert", KindTimed}
	CountSPFreshInsertFenceReads = Event{"spfresh_insert_fence_reads", "SPFresh Insert Fence Reads", KindCount}
	CountSPFreshInsertReplicas   = Event{"spfresh_insert_replicas", "SPFresh Insert Replicas", KindCount}
	CountSPFreshStaleRouteRetry  = Event{"spfresh_stale_route_retries", "SPFresh Stale Route Retries", KindCount}

	// Maintenance (rebalancer / sweeper).
	CountSPFreshSplits       = Event{"spfresh_splits", "SPFresh Splits", KindCount}
	CountSPFreshMerges       = Event{"spfresh_merges", "SPFresh Merges", KindCount}
	CountSPFreshCSplits      = Event{"spfresh_csplits", "SPFresh Coarse Splits", KindCount}
	CountSPFreshNPAs         = Event{"spfresh_npas", "SPFresh NPA Reassignments", KindCount}
	CountSPFreshZombieCleans = Event{"spfresh_zombie_cleans", "SPFresh Zombie Cleanups", KindCount}
	CountSPFreshCSplitDefers = Event{"spfresh_csplit_defers", "SPFresh Coarse Split Deferrals", KindCount}
	CountSPFreshLeaseSkips   = Event{"spfresh_lease_skips", "SPFresh Lease Skips", KindCount}
	// Assignment refinement (RFC-104) fleet pass: vectors re-routed against the
	// converged topology, and tenants whose cursor wrapped a full cycle moving
	// nothing. Moves trending to zero while Converged rises = the fleet has
	// recovered ingest recall-drift and is quiescing; sustained nonzero Moves =
	// ongoing drift (e.g. a steady fast-ingest tenant) the refinement loop is
	// absorbing.
	CountSPFreshRefineMoves     = Event{"spfresh_refine_moves", "SPFresh Refinement Moves", KindCount}
	CountSPFreshRefineConverged = Event{"spfresh_refine_converged", "SPFresh Refinement Converged Tenants", KindCount}
	// Task handler errors in a rebalance pass: the pass SKIPS the failed
	// task and continues (a poisoned task at the deterministic queue head
	// must not starve everything behind it), then surfaces the joined
	// errors. Nonzero here with a stable queue depth = a poisoned task; the
	// runbook's "task queue growing" playbook keys off it.
	CountSPFreshTaskErrors = Event{"spfresh_task_errors", "SPFresh Task Errors", KindCount}
)

SPFresh instrumentation events, recorded into the context's StoreTimer — the same FDBStoreTimer idiom every other index uses (the TEXT index's InstrumentedBunchedMap is the precedent). StoreTimer methods are nil-receiver-safe, so an uninstrumented context costs one nil check per site and SPFresh internals thread the timer unconditionally.

Timed events record nanoseconds; counts record occurrences or sized quantities. These are the operator-facing signals the index needs in production: query cost decomposition (probed/pruned/scanned/reranked), write-path health (fence reads, replicas, stale-route retries), and maintenance progress (per-kind lifecycle actions, zombie cleanups, lease skips). Scrape via StoreTimer.Snapshot().

View Source
var (
	EventSaveRecord     = Event{"save_record", "Save Record", KindTimed}
	EventLoadRecord     = Event{"load_record", "Load Record", KindTimed}
	EventDeleteRecord   = Event{"delete_record", "Delete Record", KindTimed}
	EventCommit         = Event{"commit", "Commit", KindTimed}
	EventGetReadVersion = Event{"get_read_version", "Get Read Version", KindTimed}
	EventScanRecords    = Event{"scan_records", "Scan Records", KindTimed}
	EventScanIndex      = Event{"scan_index", "Scan Index", KindTimed}
	EventOpenStore      = Event{"open_store", "Open Store", KindTimed}
	EventRebuildIndex   = Event{"rebuild_index", "Rebuild Index", KindTimed}
)

Standard timed events — matching Java's FDBStoreTimer.Events.

EventOpenStore is the one entry with no Java Events counterpart: Java splits store opening into Counts.OPEN_CONTEXT (FDBStoreTimer.java:510) plus the timed Events.CHECK_VERSION (:198) and Events.LOAD_RECORD_STORE_STATE (:103). Go times the whole open as one event.

View Source
var (
	CountSaveRecordKey        = Event{"save_record_key", "Save Record Key", KindCount}
	CountSaveRecordKeyBytes   = Event{"save_record_key_bytes", "Save Record Key Bytes", KindSize}
	CountSaveRecordValueBytes = Event{"save_record_value_bytes", "Save Record Value Bytes", KindSize}
	CountDeleteRecordKey      = Event{"delete_record_key", "Delete Record Key", KindCount}
	CountDeleteRecordKeyBytes = Event{"delete_record_key_bytes", "Delete Record Key Bytes", KindSize}
	CountReads                = Event{"reads", "Reads", KindCount}
	CountWrites               = Event{"writes", "Writes", KindCount}
	CountBytesRead            = Event{"bytes_read", "Bytes Read", KindSize}
	CountBytesWritten         = Event{"bytes_written", "Bytes Written", KindSize}

	// Index-level count events — matching Java's FDBStoreTimer.Counts for index ops.
	// Used by InstrumentedBunchedMap (TEXT index) and other index maintainers.
	CountSaveIndexKey          = Event{"save_index_key", "Save Index Key", KindCount}
	CountSaveIndexKeyBytes     = Event{"save_index_key_bytes", "Save Index Key Bytes", KindSize}
	CountSaveIndexValueBytes   = Event{"save_index_value_bytes", "Save Index Value Bytes", KindSize}
	CountLoadIndexKey          = Event{"load_index_key", "Load Index Key", KindCount}
	CountLoadIndexKeyBytes     = Event{"load_index_key_bytes", "Load Index Key Bytes", KindSize}
	CountLoadIndexValueBytes   = Event{"load_index_value_bytes", "Load Index Value Bytes", KindSize}
	CountDeleteIndexKey        = Event{"delete_index_key", "Delete Index Key", KindCount}
	CountDeleteIndexKeyBytes   = Event{"delete_index_key_bytes", "Delete Index Key Bytes", KindSize}
	CountDeleteIndexValueBytes = Event{"delete_index_value_bytes", "Delete Index Value Bytes", KindSize}
)

Standard count events — matching Java's FDBStoreTimer.Counts. The KindSize entries are the ones whose Java constant is declared with isSize()==true.

View Source
var (
	// OrderAscNullsFirst: ascending order, nulls sort first (default tuple behavior).
	OrderAscNullsFirst = OrderDirection{/* contains filtered or unexported fields */}
	// OrderAscNullsLast: ascending order, nulls sort last (0xFE encoding).
	OrderAscNullsLast = OrderDirection{/* contains filtered or unexported fields */}
	// OrderDescNullsFirst: descending order, nulls sort first.
	OrderDescNullsFirst = OrderDirection{/* contains filtered or unexported fields */}
	// OrderDescNullsLast: descending order, nulls sort last.
	OrderDescNullsLast = OrderDirection{/* contains filtered or unexported fields */}
)

The four order directions matching Java's TupleOrdering.Direction enum values.

View Source
var TupleRangeAll = TupleRange{
	LowEndpoint:  EndpointTypeTreeStart,
	HighEndpoint: EndpointTypeTreeEnd,
}

TupleRangeAll covers all entries in the index. Matches Java's TupleRange.ALL.

Functions

func AbsolutizeFieldTypeNames

func AbsolutizeFieldTypeNames(fd *descriptorpb.FileDescriptorProto)

AbsolutizeFieldTypeNames is the exported form of absolutizeFieldTypeNames, for builders that retain a Java-shaped (relative-name) proto but need a protodesc-buildable clone.

IT TAKES NO DEPENDENCY SET, and that is deliberate rather than an oversight mirroring the unexported form's variadic tail -- the question is worth answering here because the sole caller (pkg/relational/core/metadata's buildFileDescriptor) holds two protoreflect.FileDescriptors two lines before calling this and visibly does not pass them.

It does not need to. That caller sets fd.Dependency to `tuple_fields.proto` and `record_metadata_options.proto`, both of which are generated Go and therefore in protoregistry.GlobalFiles, so absolutizeFieldTypeNames resolves and seeds them through the global-registry loop -- the same route stored metadata takes for the dependencies defaultExcludedDependencies strips. Threading them in explicitly as well would seed the identical symbols twice.

A caller whose dependencies are NOT globally registered must use the unexported form and pass them, or the walk will not see their types.

func AsList

func AsList[T any](ctx context.Context, cursor RecordCursor[T]) ([]T, error)

AsList collects all records from the cursor into a slice

func AsListWithContinuation

func AsListWithContinuation[T any](ctx context.Context, cursor RecordCursor[T]) ([]T, []byte, error)

AsListWithContinuation collects all records from the cursor into a slice and returns the final continuation bytes for pagination. Returns nil continuation when the source is exhausted. This is the common pattern for paginated APIs: drain page, return token.

func BuildSPFreshIndex

func BuildSPFreshIndex(ctx context.Context, db *FDBDatabase, storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error), indexName string, seed int64) error

BuildSPFreshIndex bulk-builds an SPFresh index over the store's existing records (RFC-094 §8) and flips it readable. The §8 step order is the foreground-interleaving contract: the COARSE table commits BEFORE the assignment scan, so every record save thereafter can route itself (UpdateWhileWriteOnly stages or goes live by cellfin state), and every record saved before it is covered by the assignment scan's later read versions. Double coverage (a save the scan also reads) is harmless — staging writes are idempotent Sets on the same key.

func CheckValidProtoBufCompliantName

func CheckValidProtoBufCompliantName(name string) error

CheckValidProtoBufCompliantName validates that name is a legal protobuf identifier. See protoname.CheckValidProtoBufCompliantName.

func CleanupAllHeartbeats

func CleanupAllHeartbeats(tx fdb.WritableTransaction, storeSubspace subspace.Subspace, index *Index)

CleanupAll removes ALL heartbeats for an index. Used during index rebuild or full clear operations.

func ClearStatistics

func ClearStatistics(
	ctx context.Context,
	db *FDBDatabase,
	stats StatisticsSubspace,
	storeSubspace subspace.Subspace,
	tags ...string,
) error

ClearStatistics removes a store's statistics.

func DecodeOnceIfReversible

func DecodeOnceIfReversible(stored string) string

DecodeOnceIfReversible decodes a stored name to its SQL identifier only when the result provably re-encodes. See protoname.DecodeOnceIfReversible.

func DeleteStore

func DeleteStore(ctx *FDBRecordContext, ss subspace.Subspace) error

DeleteStore completely removes all data in a store subspace. Matches Java's FDBRecordStore.deleteStore(context, subspace).

func Filter

func Filter[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T]

Filter returns a filtered sequence

func Filter2

func Filter2[T any](seq iter.Seq2[T, error], predicate func(T) bool) iter.Seq2[T, error]

Filter2 filters a Seq2 sequence

func FindRegisteredMessageType

func FindRegisteredMessageType(fullName protoreflect.FullName) protoreflect.MessageType

FindRegisteredMessageType returns the message type registered for fullName in the process-global protobuf registry (protoregistry.GlobalTypes) — the GENERATED Go type (e.g. *gen.Order) — or nil when the name is not registered (a dynamic, metadata-built schema type). This is the same registry-first policy Build applies to record-type message factories above: a caller that rebuilds a proto value from serialized bytes must prefer the generated type so the restored value has the SAME concrete Go type as a freshly-read record, falling back to dynamicpb over metadata descriptors only for dynamic schemas (where fresh records are dynamicpb too).

func First

func First[T any](ctx context.Context, cursor RecordCursor[T]) (*T, error)

First returns the first element from a cursor, or nil if empty. Matches Java's RecordCursor.first().

func ForEach

func ForEach[T any](ctx context.Context, cursor RecordCursor[T], fn func(T) error) error

ForEach applies a function to each record in the cursor

func GetCount

func GetCount[T any](ctx context.Context, cursor RecordCursor[T]) (int, error)

GetCount returns the number of elements in a cursor by consuming it. Matches Java's RecordCursor.getCount().

func GroupCountCompanionName

func GroupCountCompanionName(ownerIndexName string) string

GroupCountCompanionName derives the companion COUNT(*) index's name from the name of the aggregate index it serves.

func GroupingSignature

func GroupingSignature(g *GroupingKeyExpression) []byte

GroupingSignature returns a normalized, deterministic encoding of an index's grouping key expression. Two aggregate indexes group identically iff their signatures are byte-equal, which is what makes companion discovery structural (RFC-209 §5.2) rather than a stored reference.

The encoding is the grouping key expression's proto with proto2 defaults cleared, marshalled deterministically — the same normalization the wire conformance test applies before comparing Go's index metadata against Java's. Clearing defaults matters because Java materializes fields Go leaves unset, so a Java-written index and a Go-written one can describe the same grouping with different bytes.

Returns nil when no signature can be derived; callers must treat nil as "no companion match", never as "matches everything".

func IsGroupCountCompanionFor

func IsGroupCountCompanionFor(idx *Index, groupingSignature, predicateSignature []byte, recordTypeNames []string, md *RecordMetaData) bool

IsGroupCountCompanionFor reports whether idx is a COUNT(*) index that can act as the group-existence companion of an index with the given grouping signature and predicate signature, over the given record types.

Structural only: the name is never consulted, so a companion created by an older binary under a different name, or declared by the user, matches just as well as one this binary would emit.

func IsPermutedMinIndex

func IsPermutedMinIndex(idx *Index) bool

IsPermutedMinIndex reports whether idx is the MIN flavour of the permuted index, the only one whose stored extremum can be a NULL that hides real values. Callers use it to keep the repair off the MAX flavour, where a stored NULL is the correct answer.

func IsRecordTypeExpression

func IsRecordTypeExpression(expr KeyExpression) bool

IsRecordTypeExpression checks if a key expression starts with record type

func KeyExpressionHasRecordTypePrefix

func KeyExpressionHasRecordTypePrefix(expr KeyExpression) bool

KeyExpressionHasRecordTypePrefix is the exported form of the per-expression prefix check. Used by the SQL layer's covering-index pushdown to decide how to slice a primary-key tuple (first element is the record-type key when true).

func Limit

func Limit[T any](seq iter.Seq[T], n int) iter.Seq[T]

Limit returns at most n values from a sequence

func LiteralToProtoValue

func LiteralToProtoValue(v any) (*gen.Value, error)

LiteralToProtoValue serializes a Go literal into the key-expression Value proto encoding — Java's LiteralKeyExpression.toProtoValue (LiteralKeyExpression.java:176-197). Exported for the index-predicate producer (RFC-202 S5): a SimpleComparison's operand is stored in exactly this encoding (IndexComparison.java:253).

func LoadIndexStates

func LoadIndexStates(tx fdb.ReadTransaction, ss subspace.Subspace) (map[string]IndexState, error)

LoadIndexStates reads a record store's index-state map from a READ transaction, without opening the store. Only states that differ from the default are stored, so an absent index is READABLE — the same contract FDBRecordStore.GetIndexState applies (and Java's RecordStoreState).

It exists because the QUERY PLANNER needs index states before any store is open: Java assembles its planner's readable-index view from the store state it already holds (PlanContext.java:236-247), while Go plans from metadata alone and reaches FDB only at execution. Opening a full store just to read four bytes per exceptional index would pull in the header, the metadata version check and the store lock; this reads the one range it needs.

func MBRToTuple

func MBRToTuple(m MBR) tuple.Tuple

MBRToTuple serializes an MBR to a flat tuple [low0, ..., lowN-1, high0, ..., highN-1]. Matches Java's ChildSlot MBR serialization.

func Map

func Map[T, R any](seq iter.Seq[T], fn func(T) R) iter.Seq[R]

Map transforms values in a sequence

func NeedsGroupCountCompanion

func NeedsGroupCountCompanion(idx *Index) bool

NeedsGroupCountCompanion reports whether idx is a GROUPED aggregate index whose own key set can neither prove nor disprove that a group exists, so it needs a companion COUNT(*) to answer correctly (RFC-209 §5.2).

SUM and COUNT(col) qualify: a stored zero is indistinguishable from a vacated group, and an all-NULL group has no entry at all. A COUNT(*) index does not — it is its own oracle (§5.3(a)). MIN/MAX do not — they keep a real per-record entry that the record's deletion removes. The UNGROUPED spelling does not — its single group exists whether or not the table has rows (§5.4).

func NewHNSWGraph

func NewHNSWGraph(storage *hnswStorage, config HNSWConfig) *hnswGraph

NewHNSWGraph creates a new HNSW graph.

func PermutedMinIgnoringNulls

func PermutedMinIgnoringNulls(
	ctx context.Context,
	scan OrdinaryIndexScanFunc,
	indexName string,
	groupKey tuple.Tuple,
	valueStart, valueEnd int,
	callerProps ExecuteProperties,
) (tuple.Tuple, error)

PermutedMinIgnoringNulls returns the smallest NON-NULL value of one group of a PERMUTED_MIN index, or nil when the group holds no non-NULL value at all (in which case SQL MIN is genuinely NULL).

It reads the index's ORDINARY subspace, whose entries are (grouping..., grouped..., primaryKey...) in key order. Every NULL-valued entry of the group shares the prefix (groupKey..., NULL), so an exclusive low endpoint on that prefix seeks past the whole NULL run in one operation — the answer is then the first entry returned, whatever the group's NULL count.

groupKey is the group in ORIGINAL column order (not the permuted order the secondary subspace stores). valueStart/valueEnd bound the aggregated columns within an ordinary entry's key.

func PredicateSignature

func PredicateSignature(idx *Index) []byte

PredicateSignature returns a normalized, deterministic encoding of an index's sparse (WHERE) predicate, or nil for a dense index.

A companion must count exactly the rows the owning aggregate index summed. A dense COUNT(*) over a SPARSE SUM lists groups the owner never saw, so driving the merge from it invents groups; a sparse COUNT(*) whose predicate differs from the owner's does the same for a different set. Comparing the serialized predicate is the only structural way to tell those apart, since the predicate is exactly what changes which rows the maintainer wrote.

RFC-209 §5.2 defines the structural match on the grouping key alone. That is incomplete for sparse indexes; this file therefore matches on grouping key AND predicate. The RFC's rationale (matching is structural, never a stored reference) is untouched — the predicate is itself stored structure.

Programmatic Go predicates have no proto and therefore no signature; they return a distinct opaque marker so they can never compare equal to a dense index nor to any serialized predicate.

func ReadHeartbeats

func ReadHeartbeats(tx fdb.ReadTransaction, storeSubspace subspace.Subspace, index *Index) ([]*gen.IndexBuildHeartbeat, []string, error)

ReadHeartbeats reads all heartbeats for an index. Useful for diagnostics.

func ReadStatisticsAtWithRefusal

func ReadStatisticsAtWithRefusal(
	ctx context.Context,
	db *FDBDatabase,
	stats StatisticsSubspace,
	storeSubspace subspace.Subspace,
	tags ...string,
) (StoreStatistics, StatisticsReadRefusal, int64, error)

ReadStatisticsAt is ReadStatistics plus the cluster version the read was taken at, from the SAME transaction.

The freshness gate needs both, and taking them separately costs a second round-trip on every uncached plan — for two numbers that are only meaningful relative to each other. Reading them together also removes a real (if narrow) window in which the entry could be replaced between the two reads, which would compare one run's stamp against a version drawn after another run.

func RebalanceSPFreshIndex

func RebalanceSPFreshIndex(ctx context.Context, db *FDBDatabase, storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error), indexName string) (int, error)

RebalanceSPFreshIndex drains the index's task queue to quiescence: scan, act, repeat until a round acts on nothing (splits enqueue NPA follow-ups, so multiple rounds are normal). Bounded against pathological re-triggering. Returns the total number of lifecycle actions taken.

func Reduce

func Reduce[T any, R any](ctx context.Context, cursor RecordCursor[T], initial R, fn func(R, T) R) (R, error)

Reduce folds all cursor values into a single result using the given function. Matches Java's RecordCursor.reduce().

func RefineSPFreshIndex

func RefineSPFreshIndex(ctx context.Context, db *FDBDatabase, storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error), indexName string, budget int) (int, bool, error)

RefineSPFreshIndex runs ONE budgeted refinement pass over the index (RFC-104): it advances the persistent round-robin cursor by up to `budget` vectors, re-routing each against the current topology and moving the stale ones to recover the closure replication fast ingest never fired. It does NOT drain to quiescence — the deployment runs it on its own cadence (a refinement loop beside the rebalancer loop). Returns (moves, cycleConverged): cycleConverged is true when this call wrapped the cursor AND the whole cycle since the last wrap moved nothing — the signal a caller uses to back off a converged tenant. It is NOT merely "this pass reached the end": a budgeted pass whose tail moves zero does not imply the cycle's earlier passes did.

func RefineSPFreshIndexAll

func RefineSPFreshIndexAll(ctx context.Context, db *FDBDatabase, storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error), indexName string) (int, error)

RefineSPFreshIndexAll is the exported one-shot validation entry (refine every vector once). Production callers use the budgeted RefineSPFreshIndex.

func RegisterFunction

func RegisterFunction(name string, evaluator FunctionEvaluator)

RegisterFunction registers a named function evaluator in the global registry. Call this before building metadata that uses the function.

func RunSPFreshMaintenance

func RunSPFreshMaintenance(ctx context.Context, db *FDBDatabase, opts SPFreshMaintenanceOptions) error

RunSPFreshMaintenance runs the maintenance loop until ctx is canceled, then returns nil. It does an immediate sweep on entry (so a freshly-started worker makes progress without waiting a full interval), then sweeps on SweepInterval and refines on RefineInterval. Per-pass results/errors flow to OnSweep / OnRefine; nothing here fails the loop. With no tenants it is a quiet no-op loop (each pass returns an empty result), so it is safe to start before the tenant list is populated.

func SPFreshAuditTrail

func SPFreshAuditTrail(fineID int64) []string

SPFreshAuditTrail returns the recorded trail for a fineID.

func SPFreshDebugIntegrity

func SPFreshDebugIntegrity(rtx *FDBRecordContext, store *FDBRecordStore, indexName string, sample int) string

SPFreshDebugIntegrity samples up to `sample` pks evenly from the index's OWN membership rows (no assumption about pk shape) and reports, for each, whether every membership target holds the posting entry and what state the target centroid is in. Diagnostics only: it streams the membership keyspace (O(index) reads) — never call it on a production write path.

func SPFreshDebugTopology

func SPFreshDebugTopology(rtx *FDBRecordContext, store *FDBRecordStore, indexName string) string

SPFreshDebugTopology dumps an index's coarse/fine topology summary (benchmark/operational diagnostics).

func SPFreshDisableAudit

func SPFreshDisableAudit()

SPFreshDisableAudit turns the audit log off and releases its memory. Flag first, then the map: a concurrent spfreshAudit that already passed the flag check still finds a (stale, discarded) map under the mutex.

func SPFreshEnableAudit

func SPFreshEnableAudit()

SPFreshEnableAudit turns on the centroid audit log (diagnostics).

func SPFreshHasPendingMaintenance

func SPFreshHasPendingMaintenance(ctx context.Context, db *FDBDatabase, storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error), indexName string) (bool, error)

SPFreshHasPendingMaintenance reports whether the index has any pending maintenance task rows: one snapshot generation read + one limit-1 snapshot range read — the cheap probe that lets a sweeper skip idle tenants without paying a full task scan. Un-bootstrapped indexes have no work by definition.

func SafeDecoderOver

func SafeDecoderOver(decoded, verbatim []string) func(string) string

SafeDecoderOver returns one decoding policy for a whole rendered output. See protoname.SafeDecoderOver.

func SamePredicateSignature

func SamePredicateSignature(a, b []byte) bool

SamePredicateSignature reports whether two predicate signatures denote the same row population. Two opaque markers are NOT equal: an unserializable predicate is unmatchable in principle, so it must decline against everything including another one.

func Seq2

func Seq2[T any](cursor RecordCursor[T], ctx context.Context) iter.Seq2[T, error]

Seq2 returns an iterator sequence over (value, error) pairs.

func SerializeVector

func SerializeVector(vec []float64) []byte

SerializeVector encodes a float64 vector into the on-disk byte format the HNSW vector index reads (RealVector.fromBytes). Exported so callers/tests can populate a record's vector column.

func ToProtoBufCompliantName

func ToProtoBufCompliantName(name string) (string, error)

ToProtoBufCompliantName escapes a user identifier into a protobuf-compliant name. See protoname.ToProtoBufCompliantName.

func ToUserIdentifier

func ToUserIdentifier(protoIdentifier string) string

ToUserIdentifier reverses ToProtoBufCompliantName. See protoname.ToUserIdentifier.

func TranslatePrimaryKeyToValues

func TranslatePrimaryKeyToValues(
	pk KeyExpression,
	normalizeName func(string) string,
	flowedType values.Type,
) []values.Value

TranslatePrimaryKeyToValues translates a common primary-key KeyExpression into a flat []values.Value that encodes STRUCTURE — record-type-key prefixes (RecordTypeValue) and completely resolved ordinal paths — the Go analog of Java's ScalarTranslationVisitor.translateKeyExpression used by PrimaryKeyProperty.visitIndexPlan (RFC-189 B3).

PK identity must compare by STRUCTURE, not by bare column names: Field("ID") and Concat(RecordTypeKey(), Field("ID")) share the flat name list ["ID"] but are DIFFERENT primary keys, and conflating them lets ImplementDistinctUnionRule dedup two union legs that must both survive (dropped rows — the exact hazard the by-name M5 was reverted for). The structural translation makes them unequal under values.ValuesStructurallyEqual.

Returns nil if any component is not translatable (fan-out, version, function, or any unrecognized key), or if flowedType cannot resolve the complete path — fail-safe: the property then abstains, disabling the dedup optimization rather than risking a wrong one. Field names are request metadata only; every published FieldValue is rooted in one exact QOV and carries the ordinal path resolved against flowedType. normalizeName maps a raw protobuf field name into the caller's namespace (the SQL layer uppercases; a nil transform means identity). The translated field names MUST live in the SAME namespace as the plan's ordering/column values or values.ValuesStructurallyEqual never matches and the dedup silently never fires (the field-casing mismatch that made the structural common-PK inert).

func ValidateEvolution

func ValidateEvolution(oldMetaData, newMetaData *RecordMetaData) error

ValidateEvolution is a convenience function using the default (strictest) validator.

func ValidateHNSWConfig

func ValidateHNSWConfig(c HNSWConfig) error

ValidateHNSWConfig validates the HNSW configuration. Matches Java's Config validation.

func ValidateRTreeConfig

func ValidateRTreeConfig(config RTreeConfig) error

ValidateRTreeConfig validates the R-tree configuration parameters. Checks: MinM >= 1, MaxM >= 2, SplitS >= 1, NumDimensions >= 1, and S * MaxM >= (S+1) * MinM (split/fuse ratio constraint).

func ValidateSPFreshConfig

func ValidateSPFreshConfig(c SPFreshConfig) error

ValidateSPFreshConfig enforces the invariants the RFC-094 lifecycle and sizing arguments depend on. Called by the maintainer at construction; a violation is a config error, never a silently degraded index.

func ValidateTags

func ValidateTags(tags []string) ([]string, error)

ValidateTags applies Java's setTags checks in Java's order — the COUNT first, then each tag's length (FDBRecordContextConfig.java:662-669). The order is observable: an over-long tag in an over-large set reports the count error. Note this is the opposite of the FDB client's TagSet::addTag, which checks length first because it validates one tag at a time rather than a whole set.

Returns the deduplicated tag set; Java's Set<String> collapses duplicates before the size check, so ("a","a","b") is two tags, not three.

func ValidateTokenizerVersion

func ValidateTokenizerVersion(t TextTokenizer, version int) error

ValidateTokenizerVersion checks that the given version is within the tokenizer's supported range. Returns an error if out of bounds. Matches Java's TextTokenizer.validateVersion().

func WriteRecordMetaData

func WriteRecordMetaData(meta *RecordMetaData, w io.Writer) error

WriteRecordMetaData serializes a RecordMetaData to the given writer in the canonical `com.apple.foundationdb.record.RecordMetaDataProto.MetaData` wire format — the same bytes FDBMetaDataStore writes into FDB.

Intended for apps with programmatic metadata that want to ship a .pb artifact alongside their binaries so operator tooling (e.g. `frl --meta-file`) can introspect the store without adopting FDBMetaDataStore. Typical use:

func main() {
    meta := buildMyMetaData() // normal RecordMetaDataBuilder
    f, err := os.Create("meta.pb")
    // ... error handling ...
    if err := recordlayer.WriteRecordMetaData(meta, f); err != nil { … }
}

The serialized MetaData includes the embedded `records` FileDescriptorProto and its transitive `dependencies`, so consumers don't need out-of-band access to the app's .proto files to decode records.

Types

type AggregateFunctionNotSupportedError

type AggregateFunctionNotSupportedError struct {
	Function string
	Operand  string
}

AggregateFunctionNotSupportedError is Java's AggregateFunctionNotSupportedException (thrown by FDBRecordStore.evaluateAggregateFunction, FDBRecordStore.java:2409-2412) — no readable, queryable index over the requested record types can evaluate the function.

It is a distinct type because callers branch on it: Java's IndexBuildState.loadIndexBuildStateAsync catches exactly this exception to report "total unknown" and lets every other failure propagate (IndexBuildState.java:76-82).

func (*AggregateFunctionNotSupportedError) Error

type BackendCapabilityError

type BackendCapabilityError struct {
	Op string // the unavailable operation, e.g. "CreateTransaction"
}

BackendCapabilityError is returned when an operation is not supported on the configured fdb backend. The libfdb_c escape hatch (RFC-109) drives the Run / RunRead gold path, standalone transactions (CreateWritableTransaction — used by the FDBDatabaseRunner and SQL BeginTx), and LocalityGetBoundaryKeys (online MUTUAL indexing) all through backend interfaces, so those work on libfdb_c. Only the pure-Go-typed CreateTransaction (which returns the concrete fdb.Transaction) is pure-Go-only in v1; it — and any operation a custom transactor genuinely can't provide — fail fast with this error.

func (*BackendCapabilityError) Error

func (e *BackendCapabilityError) Error() string

type BunchedEntry

type BunchedEntry[K any, V any] struct {
	Key   K
	Value V
}

BunchedEntry is a key-value pair in a BunchedMap.

type BunchedMap

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

BunchedMap is a FoundationDB-backed map that bunches close keys together. Wire-compatible with Java's com.apple.foundationdb.map.BunchedMap.

Currently specialized for TEXT index use (K = tuple.Tuple, V = []int).

When a StoreTimer is set, BunchedMap instruments all writes, deletes, and range reads with index-level counters matching Java's InstrumentedBunchedMap.

func NewBunchedMap

func NewBunchedMap(bunchSize int) *BunchedMap

NewBunchedMap creates a new BunchedMap with the given bunch size.

func NewInstrumentedBunchedMap

func NewInstrumentedBunchedMap(bunchSize int, timer *StoreTimer) *BunchedMap

NewInstrumentedBunchedMap creates a BunchedMap with instrumentation enabled. Matches Java's InstrumentedBunchedMap which wraps BunchedMap with timer hooks.

func (*BunchedMap) Compact

func (m *BunchedMap) Compact(tx fdb.WritableTransaction, ss subspace.Subspace, keyLimit int, continuation []byte) ([]byte, error)

Compact repacks entries in the map to minimize the number of FDB keys used. Each call processes up to keyLimit FDB keys (0 = all keys) and returns a continuation token for multi-transaction compaction. Returns (nil, nil) when compaction is complete. Matches Java's BunchedMap.compact().

func (*BunchedMap) ContainsKey

func (m *BunchedMap) ContainsKey(tx fdb.WritableTransaction, ss subspace.Subspace, key tuple.Tuple) (bool, error)

ContainsKey checks if a key exists in the map. Matches Java's BunchedMap.containsKey().

func (*BunchedMap) Get

Get retrieves the value associated with a key from the map. Returns (value, true, nil) if found, (nil, false, nil) if not found. Always adds a read conflict key on the map key for serializability, matching Java's BunchedMap.get() which takes TransactionContext.

func (*BunchedMap) Put

func (m *BunchedMap) Put(tx fdb.WritableTransaction, ss subspace.Subspace, key tuple.Tuple, value []int) ([]int, bool, error)

Put inserts or updates a key-value pair in the map. Returns (oldValue, true, nil) if the key already existed, (nil, false, nil) if new. Matches Java's BunchedMap.put().

func (*BunchedMap) Remove

func (m *BunchedMap) Remove(tx fdb.WritableTransaction, ss subspace.Subspace, key tuple.Tuple) ([]int, bool, error)

Remove removes a key from the map. Returns (oldValue, true, nil) if found and removed, (nil, false, nil) if not found. Matches Java's BunchedMap.remove().

func (*BunchedMap) Scan

func (m *BunchedMap) Scan(tx fdb.ReadTransaction, ss subspace.Subspace, continuation []byte, limit int, reverse bool) *BunchedMapIterator

Scan iterates over all entries in a single BunchedMap within the given subspace. Supports continuation, limit, and reverse scan. Matches Java's BunchedMap.scan().

func (*BunchedMap) VerifyIntegrity

func (m *BunchedMap) VerifyIntegrity(tx fdb.ReadTransaction, ss subspace.Subspace) error

VerifyIntegrity checks that all signpost keys and entries within bunches are in sorted order. Returns nil if the map is consistent. Matches Java's BunchedMap.verifyIntegrity().

type BunchedMapException

type BunchedMapException struct {
	Message string
}

BunchedMapException is the error type for BunchedMap operations.

func (*BunchedMapException) Error

func (e *BunchedMapException) Error() string

type BunchedMapIterator

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

BunchedMapIterator iterates over a single BunchedMap's entries. Matches Java's BunchedMapIterator.

func (*BunchedMapIterator) Err

func (it *BunchedMapIterator) Err() error

Err returns the first error encountered during iteration, if any.

func (*BunchedMapIterator) GetContinuation

func (it *BunchedMapIterator) GetContinuation() []byte

GetContinuation returns a continuation token for resuming the scan.

func (*BunchedMapIterator) HasNext

func (it *BunchedMapIterator) HasNext() bool

HasNext returns true if there are more entries.

func (*BunchedMapIterator) Next

func (it *BunchedMapIterator) Next() *bunchedEntry

Next returns the next entry.

type BunchedMapMultiIterator

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

BunchedMapMultiIterator iterates over multiple BunchedMaps within a parent subspace. Entries are returned sorted first by subspace, then by key within subspace. Streams from FDB lazily — only one bunch is in memory at a time. Matches Java's BunchedMapMultiIterator.

func NewBunchedMapMultiIterator

func NewBunchedMapMultiIterator(
	tx fdb.ReadTransaction,
	parentSubspace subspace.Subspace,
	splitter SubspaceSplitter,
	beginBytes, endBytes []byte,
	continuation []byte,
	limit int,
	reverse bool,
	serializer *TextIndexBunchedSerializer,
) *BunchedMapMultiIterator

NewBunchedMapMultiIterator creates a multi-map iterator. Streams from FDB lazily — only one bunch in memory at a time.

func NewBunchedMapMultiIteratorWithCallback

func NewBunchedMapMultiIteratorWithCallback(
	tx fdb.ReadTransaction,
	parentSubspace subspace.Subspace,
	splitter SubspaceSplitter,
	beginBytes, endBytes []byte,
	continuation []byte,
	limit int,
	callback KVCallback,
	reverse bool,
	serializer *TextIndexBunchedSerializer,
) *BunchedMapMultiIterator

NewBunchedMapMultiIteratorWithCallback creates a multi-map iterator with a per-KV callback for byte tracking. The callback fires once per raw FDB key-value pair read, before deserialization. Matches Java's scanMulti() with Consumer<KeyValue> callback parameter.

func (*BunchedMapMultiIterator) Cancel

func (it *BunchedMapMultiIterator) Cancel()

Cancel stops the iterator.

func (*BunchedMapMultiIterator) Err

func (it *BunchedMapMultiIterator) Err() error

Err returns the first error encountered during iteration, if any.

func (*BunchedMapMultiIterator) GetContinuation

func (it *BunchedMapMultiIterator) GetContinuation() []byte

GetContinuation returns a continuation token for resuming the scan. Returns nil if the scan is exhausted.

func (*BunchedMapMultiIterator) HasNext

func (it *BunchedMapMultiIterator) HasNext() bool

HasNext returns true if there are more entries to return.

func (*BunchedMapMultiIterator) Next

Next returns the next entry and advances the iterator.

type BunchedMapScanEntry

type BunchedMapScanEntry struct {
	Subspace    subspace.Subspace
	SubspaceTag tuple.Tuple
	Key         tuple.Tuple
	Value       []int
}

BunchedMapScanEntry is a single entry from a multi-map scan. Contains the entry data plus the subspace it belongs to.

type BunchedSerializationError

type BunchedSerializationError struct {
	Message string
	Data    []byte
}

BunchedSerializationError is returned when bunched map serialization/deserialization fails.

func (*BunchedSerializationError) Error

func (e *BunchedSerializationError) Error() string

type BunchedSerializer

type BunchedSerializer[K any, V any] interface {
	SerializeKey(key K) []byte
	SerializeEntry(key K, value V) ([]byte, error)
	SerializeEntries(entries []BunchedEntry[K, V]) ([]byte, error)
	DeserializeKey(data []byte, offset, length int) (K, error)
	DeserializeEntries(key K, data []byte) ([]BunchedEntry[K, V], error)
	DeserializeKeys(key K, data []byte) ([]K, error)
	CanAppend() bool
}

BunchedSerializer serializes keys and values for BunchedMap storage. Keys must serialize in a way that preserves sort order (unsigned lexicographic). Matches Java's com.apple.foundationdb.map.BunchedSerializer interface.

type BytesContinuation

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

BytesContinuation is a simple continuation that wraps a byte array

func NewBytesContinuation

func NewBytesContinuation(b []byte) *BytesContinuation

NewBytesContinuation creates a BytesContinuation with the given bytes. A nil bytes value means end-of-cursor (IsEnd returns true).

func (*BytesContinuation) IsEnd

func (c *BytesContinuation) IsEnd() bool

IsEnd returns true if this is an end continuation

func (*BytesContinuation) ToBytes

func (c *BytesContinuation) ToBytes() ([]byte, error)

ToBytes returns the continuation bytes

type CardinalityFunctionKeyExpression

type CardinalityFunctionKeyExpression struct {
	FunctionKeyExpression
}

CardinalityFunctionKeyExpression is the root key expression of a CARDINALITY() index. It counts the argument array's elements to produce the single key column. Ports Java's CardinalityFunctionKeyExpression:

  • getMinArguments()==getMaxArguments()==1, getColumnSize()==1
  • createsDuplicates()==false (a count is single-valued — see createsDuplicates)
  • evaluateMessage's two protobuf fast paths (plain repeated field, nullable wrapper) plus the materialize-and-count fallback for deeper nestings.

It embeds the generic FunctionKeyExpression so it serialises to the identical proto (Function{name:"cardinality", arguments:…}) and reuses FieldNames / ColumnSize / Name / Arguments. Only Evaluate is overridden to add the fast paths; the result is identical to the registry fallback either way.

func CardinalityExpr

func CardinalityExpr(arguments KeyExpression) *CardinalityFunctionKeyExpression

CardinalityExpr builds a CARDINALITY() index root over the given argument key expression. The argument is field("arr", Concatenate) for a plain array column, or field("arr").nest(field("values", Concatenate)) for a Java-written nullable-array wrapper. Mirrors Java's function("cardinality", field("arr", …)).

func (*CardinalityFunctionKeyExpression) Evaluate

Evaluate applies Java's two protobuf fast paths against the record message, falling back to materialize-and-count for shapes the fast paths do not recognise (deeper nesting, the null cases).

Fast path 1 — nullable-array wrapper: the argument is field("wrapper").nest(field("values", Concatenate)). If the wrapper message is present, count its "values" field directly (a present wrapper with zero elements is a non-null empty array → 0). If absent, the array is NULL → fall through to the materialized null result.

Fast path 2 — plain repeated field: the argument is field("arr", Concatenate). Count the repeated field directly (Message.getRepeatedFieldCount). A zero count keys 0, NOT NULL: a plain repeated field is the NOT NULL array's storage shape, and an empty one is [] whose cardinality is 0. NULL keys arise only where the array itself can be absent — an absent wrapper, or a null parent on a deeper nesting — both of which reach the materialized fallback.

type ChildSlot

type ChildSlot struct {
	SmallestHV  *big.Int
	SmallestKey tuple.Tuple
	LargestHV   *big.Int
	LargestKey  tuple.Tuple
	ChildID     []byte
	ChildMBR    MBR
}

ChildSlot is an intermediate node slot referencing a child node.

func (ChildSlot) GetMBR

func (s ChildSlot) GetMBR() MBR

GetMBR returns the MBR of the child subtree.

type CollectOptions

type CollectOptions struct {
	// BatchSize is the maximum number of records scanned per transaction.
	// Collection is continuation-driven, so a large store costs many small
	// transactions rather than one big one.
	//
	// It is a ROW bound and therefore NOT what keeps a transaction inside FDB's
	// 5s limit: a record can be a hundred bytes or, split across KV pairs,
	// hundreds of kilobytes, so a fixed row count is anywhere from ~100KB to
	// hundreds of megabytes of reading. TimeLimit and ScannedBytesLimit are the
	// bounds that hold whatever the records weigh; a batch ends at whichever of
	// the three comes first.
	BatchSize int
	// MaxRecordsPerType ABORTS the collection as soon as any one record type
	// exceeds this many rows. Nothing is stored, and the report names the type
	// that blew the budget. Zero means no cap.
	//
	// Abort rather than skip-and-continue, because skipping cannot produce a
	// usable outcome. A type with no entry fails the reader's schema-wide
	// completeness gate, so the OTHER types' counts are refused along with it —
	// the old behaviour bought a full scan and an unusable result, which is
	// strictly worse than not collecting. Aborting reaches the same end state
	// having read a bounded prefix, and says which type to look at.
	//
	// It also cannot bound work any other way: collection is a SINGLE PASS over
	// all records (a per-type scan needs a record-type-prefixed primary key,
	// which not every layout has), so there is no way to keep scanning while
	// skipping one type's rows.
	MaxRecordsPerType int64
	// TimeLimit bounds how long ONE scan transaction spends reading before it
	// stops and hands back a continuation. Zero means DefaultCollectTimeLimit.
	//
	// This, not BatchSize, is what keeps a transaction inside FDB's 5s limit.
	// BatchSize bounds ROWS, and a row is not a fixed number of bytes.
	TimeLimit time.Duration
	// ScannedBytesLimit bounds how many bytes ONE scan transaction reads before
	// it stops and hands back a continuation. Zero means
	// DefaultCollectScannedBytesLimit.
	//
	// Both bounds are drivable from a test, and neither is a wall-clock bound:
	// every elapsed-time decision goes through ScanLimiterState.Elapsed, which is
	// env.Since, so TimeLimit runs on the simulation clock when one is installed.
	// An earlier version of this comment claimed TimeLimit was nondeterministic
	// and used that to justify leaving its arm undriven -- the claim was false and
	// the gap it excused was real; both arms now have a test.
	ScannedBytesLimit int64
	// Tags are FDB transaction tags applied to EVERY transaction this collection
	// opens — each scan batch and the replacing write.
	//
	// EmbeddedConnection.CollectStatistics OVERWRITES this with the connection's
	// own tags: at that layer the connection owns them, and a caller passing
	// CollectOptions should not have to know they exist. Callers of this package
	// directly (the fleet fan-out, tests) set it themselves and it is honoured.
	//
	// Threaded explicitly rather than wrapped around the database, because a
	// wrapper has to reconstruct an *FDBDatabase and this package's copy-method
	// gate forbids the field-by-field form for good reason: the first attempt
	// dropped env, which silently swaps a persisted timestamp's seeded clock for
	// the wall clock. A parameter is visible at every call site; a dropped field
	// is visible nowhere.
	Tags []string
}

CollectOptions tunes a collection run.

type CollectionReport

type CollectionReport struct {
	// Collected is the per-type statistics written.
	Collected map[string]RecordTypeStatistic
	// Skipped maps a record type to why it has NO statistic. Present so a
	// caller can tell "not collected" from "collected as zero" — the two are
	// different facts and only one of them is a table with no rows.
	Skipped map[string]string
	// RecordsScanned is the total records read across all types.
	RecordsScanned int64
}

CollectionReport describes one run.

func CollectStatistics

func CollectStatistics(
	ctx context.Context,
	db *FDBDatabase,
	storeBuilder func(*FDBRecordContext) (*FDBRecordStore, error),
	stats StatisticsSubspace,
	opts CollectOptions,
) (*CollectionReport, error)

CollectStatistics scans a record store and writes per-record-type counts into stats -- exact for a store at rest; see RecordTypeStatistic for what concurrent primary-key moves do to the total.

It is an OFFLINE maintenance job, not a query path: it reads every record. The signature mirrors RebalanceSPFreshIndex so it composes with the maintainers already in this library.

Counting is a SINGLE PASS over all records, tallying by type, rather than one scan per type. That is deliberate: a per-type scan is only cheaper when the primary key carries a record-type prefix, and this must work for every store layout, including the ones that do not. One pass is uniform and correct everywhere.

type CommitCheckFunc

type CommitCheckFunc func() error

CommitCheckFunc is a pre-commit check that runs before the transaction commits. If it returns an error, the commit is aborted. Matches Java's CommitCheckAsync interface.

type ComparisonKeyFunc

type ComparisonKeyFunc[T any] func(T) (tuple.Tuple, error)

ComparisonKeyFunc extracts a comparison key from a cursor element. The returned tuple is used for merge ordering via FDB's order-preserving tuple encoding. Matches Java's KeyedMergeCursorState.comparisonKeyFunction: there a key-extraction failure propagates as a RecordCoreException through the cursor's future chain — an error to the caller, never a crash — so the Go closure carries an error return, which advance() surfaces as the child cursor's error.

type CompositeKeyExpression

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

CompositeKeyExpression combines multiple key expressions

func (*CompositeKeyExpression) ColumnSize

func (c *CompositeKeyExpression) ColumnSize() int

ColumnSize returns the sum of all child column sizes.

func (*CompositeKeyExpression) Evaluate

func (c *CompositeKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate computes the Cartesian product of all child expression results. Matches Java's ThenKeyExpression which crosses children:

child0 returns [[1], [2]]
child1 returns [[a], [b]]
result = [[1,a], [1,b], [2,a], [2,b]]

For the common case where each child returns exactly one tuple, the result is a single tuple that is the concatenation of all child tuples — identical to the old flat-append behavior.

func (*CompositeKeyExpression) EvaluateFlat

func (c *CompositeKeyExpression) EvaluateFlat(record *FDBStoredRecord[proto.Message], msg proto.Message) ([]any, error)

EvaluateFlat returns the single concatenated tuple directly, avoiding the outer [][]any wrapper. Only valid when no child uses fan-out. Saves 1 alloc per child (no [][]any) + 1 alloc for the result (no outer wrapper).

func (*CompositeKeyExpression) FieldNames

func (c *CompositeKeyExpression) FieldNames() []string

FieldNames returns all field names from component expressions

func (*CompositeKeyExpression) PackDirect

func (c *CompositeKeyExpression) PackDirect(pk *tuple.Packer, record *FDBStoredRecord[proto.Message], msg proto.Message) bool

PackDirect encodes all child fields directly into a Packer. Returns false if any child can't be directly packed.

func (*CompositeKeyExpression) SubKeyExpressions

func (c *CompositeKeyExpression) SubKeyExpressions() []KeyExpression

SubKeyExpressions returns the child key expressions in key order. Mirrors Java's ThenKeyExpression.getChildren(); used by callers that need to inspect the composite's structure (e.g. tagging which columns are function-keyed). The returned slice is the live backing slice — callers must not mutate it.

func (*CompositeKeyExpression) ToKeyExpression

func (c *CompositeKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a CompositeKeyExpression (ThenKeyExpression) to proto. Matches Java's ThenKeyExpression.toKeyExpression().

type ContinuationParseError

type ContinuationParseError struct {
	Message  string
	RawBytes []byte
	Cause    error
}

ContinuationParseError is returned when continuation bytes fail to parse as their wrapper proto. Matches Java's RecordCoreException("error parsing continuation") with the "raw_bytes" log info key (e.g. OrElseCursor's constructor, RecordCursor.fromList): a corrupt continuation is a caller error and must surface, never be silently treated as a fresh start.

Message overrides the default "error parsing continuation" text for the Java classes whose RecordCoreException wording differs (ConcatCursor uses "Error parsing ConcatCursor continuation", DedupCursor uses "Error parsing continuation"). Leave empty for the common wording.

func (*ContinuationParseError) Error

func (e *ContinuationParseError) Error() string

func (*ContinuationParseError) Unwrap

func (e *ContinuationParseError) Unwrap() error

type Counter

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

Counter tracks an occurrence count plus a cumulative value. Matches Java's StoreTimer.Counter (StoreTimer.java:408-517), including which of the two fields carries the payload for each Kind:

  • KindTimed: Count() is occurrences, CumulativeValue() is total nanoseconds.
  • KindCount / KindSize: Count() carries everything (occurrences, or a byte total for KindSize) and CumulativeValue() stays 0.

The second bullet is the non-obvious one, and it is Java's: Counter.increment(int) adds to `count` alone (StoreTimer.java:484-487), which is why Java can use `getTimeNanos() == 0` as a proof that an event is a counter rather than a timer (RecordLayerMetricCollector.java:90).

All operations are goroutine-safe.

func (*Counter) Count

func (c *Counter) Count() int64

Count returns the number of observations recorded.

func (*Counter) CumulativeValue

func (c *Counter) CumulativeValue() int64

CumulativeValue returns the sum of all recorded values.

func (*Counter) Event

func (c *Counter) Event() Event

Event returns the event this counter records.

func (*Counter) Increment

func (c *Counter) Increment(amount int64)

Increment adds amount to the occurrence count, leaving the cumulative value untouched — Java's Counter.increment (StoreTimer.java:484-487). For a KindSize event the byte total therefore accumulates in Count(), which is where Java keeps it too (getKeysAndValues emits a size Count as a plain `_count` key, StoreTimer.java:754).

func (*Counter) Record

func (c *Counter) Record(value int64)

Record records a single observation with the given value: one occurrence, plus value added to the cumulative total. Java's Counter.record (StoreTimer.java:473-477). For timed events, value is nanoseconds.

func (*Counter) Reset

func (c *Counter) Reset()

Reset zeroes the counter.

type CounterSnapshot

type CounterSnapshot struct {
	Event           Event
	Count           int64
	CumulativeValue int64
}

CounterSnapshot is an immutable point-in-time snapshot of a Counter. Matches Java's StoreTimerSnapshot.CounterSnapshot (StoreTimerSnapshot.java:179-226).

Event is carried alongside the numbers because the numbers are not self-describing: without the Kind, a consumer cannot tell whether Count is occurrences or bytes, nor whether CumulativeValue is a duration or meaningless. Java recovers that from the map key's static type; Go carries it.

type CursorFactory

type CursorFactory[T any] func(continuation []byte) RecordCursor[T]

CursorFactory creates a cursor from an optional continuation. nil continuation means start from the beginning.

type CursorStreamingMode

type CursorStreamingMode int

CursorStreamingMode controls how FDB fetches data

const (
	// StreamingModeSmall fetches small amounts at a time
	StreamingModeSmall CursorStreamingMode = iota
	// StreamingModeMedium fetches medium amounts at a time
	StreamingModeMedium
	// StreamingModeLarge fetches large amounts at a time
	StreamingModeLarge
	// StreamingModeSerial fetches one at a time
	StreamingModeSerial
	// StreamingModeWantAll fetches as much as possible
	StreamingModeWantAll
	// StreamingModeIterator is the default iterator mode
	StreamingModeIterator
)

func (CursorStreamingMode) ToFDB

ToFDB converts to FDB's StreamingMode

type DefaultTextTokenizer

type DefaultTextTokenizer struct{}

DefaultTextTokenizer is the default tokenizer for full-text indexes. It splits text using Unicode word boundary rules, normalizes to NFKD, case-folds to lowercase, and strips diacritical marks. Matches Java's com.apple.foundationdb.record.provider.common.text.DefaultTextTokenizer.

func DefaultTextTokenizerInstance

func DefaultTextTokenizerInstance() *DefaultTextTokenizer

DefaultTextTokenizerInstance returns the singleton default tokenizer.

func (*DefaultTextTokenizer) MaxVersion

func (t *DefaultTextTokenizer) MaxVersion() int

MaxVersion returns the same as MinVersion (only one version exists).

func (*DefaultTextTokenizer) MinVersion

func (t *DefaultTextTokenizer) MinVersion() int

MinVersion returns 0 (GLOBAL_MIN_VERSION).

func (*DefaultTextTokenizer) Name

func (t *DefaultTextTokenizer) Name() string

Name returns "default".

func (*DefaultTextTokenizer) Tokenize

func (t *DefaultTextTokenizer) Tokenize(text string, version int, mode TokenizerMode) (TokenIterator, error)

Tokenize returns an iterator over tokens from the input text. Returns an error if the version is out of bounds. The tokenization process:

  1. Segment text into words using Unicode word boundary rules (matching Java's BreakIterator.getWordInstance(Locale.ROOT))
  2. NFKD normalize each segment
  3. Filter segments that don't contain any letter or digit
  4. Lowercase and strip combining marks (\p{M})

func (*DefaultTextTokenizer) TokenizeToList

func (t *DefaultTextTokenizer) TokenizeToList(text string, version int, mode TokenizerMode) ([]string, error)

TokenizeToList returns all tokens as a list.

func (*DefaultTextTokenizer) TokenizeToMap

func (t *DefaultTextTokenizer) TokenizeToMap(text string, version int, mode TokenizerMode) (map[string][]int, error)

TokenizeToMap returns a map from token to list of positions.

type DefaultTextTokenizerFactory

type DefaultTextTokenizerFactory struct{}

DefaultTextTokenizerFactory creates DefaultTextTokenizer instances.

func (*DefaultTextTokenizerFactory) GetTokenizer

func (f *DefaultTextTokenizerFactory) GetTokenizer() TextTokenizer

func (*DefaultTextTokenizerFactory) Name

type DimensionsKeyExpression

type DimensionsKeyExpression struct {
	WholeKey       KeyExpression
	PrefixSize     int
	DimensionsSize int
}

DimensionsKeyExpression splits a key expression into prefix, dimensions, and suffix. Used by MULTIDIMENSIONAL indexes to separate grouping, spatial, and key-suffix components. Matches Java's DimensionsKeyExpression.

func Dimensions

func Dimensions(wholeKey KeyExpression, prefixSize, dimensionsSize int) *DimensionsKeyExpression

Dimensions creates a DimensionsKeyExpression.

func (*DimensionsKeyExpression) ColumnSize

func (d *DimensionsKeyExpression) ColumnSize() int

ColumnSize returns the column size of the whole key.

func (*DimensionsKeyExpression) Evaluate

func (d *DimensionsKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate delegates to the whole key expression.

func (*DimensionsKeyExpression) FieldNames

func (d *DimensionsKeyExpression) FieldNames() []string

FieldNames delegates to the whole key.

func (*DimensionsKeyExpression) SplitIndexEntry

func (d *DimensionsKeyExpression) SplitIndexEntry(entry tuple.Tuple) (prefix, dims, suffix tuple.Tuple)

SplitIndexEntry splits an evaluated index entry tuple into prefix, dimensions, and suffix.

func (*DimensionsKeyExpression) SuffixSize

func (d *DimensionsKeyExpression) SuffixSize() int

SuffixSize returns the number of suffix columns (after prefix + dimensions).

func (*DimensionsKeyExpression) ToKeyExpression

func (d *DimensionsKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes to proto.

type DirectPacker

type DirectPacker interface {
	PackDirect(pk *tuple.Packer, record *FDBStoredRecord[proto.Message], msg proto.Message) bool
}

DirectPacker can encode field values directly into a Packer without any boxing.

type EmptyKeyExpression

type EmptyKeyExpression struct{}

EmptyKeyExpression produces an empty tuple — used for ungrouped record counting. When used as a recordCountKey, a single total count is maintained.

func (*EmptyKeyExpression) ColumnSize

func (e *EmptyKeyExpression) ColumnSize() int

ColumnSize returns 0 — an empty expression produces no tuple elements.

func (*EmptyKeyExpression) Evaluate

Evaluate returns one empty tuple (no key components).

func (*EmptyKeyExpression) EvaluateFlat

func (e *EmptyKeyExpression) EvaluateFlat(_ *FDBStoredRecord[proto.Message], _ proto.Message) ([]any, error)

EvaluateFlat returns empty (no elements to append).

func (*EmptyKeyExpression) FieldNames

func (e *EmptyKeyExpression) FieldNames() []string

FieldNames returns no field names.

func (*EmptyKeyExpression) ToKeyExpression

func (e *EmptyKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes an EmptyKeyExpression to proto. Matches Java's EmptyKeyExpression.toKeyExpression().

type EndContinuation

type EndContinuation struct{}

EndContinuation represents the end of a cursor's iteration. INVARIANT: Only valid when NoNextReason is SourceExhausted. Matches Java's RecordCursorEndContinuation.

func (*EndContinuation) IsEnd

func (c *EndContinuation) IsEnd() bool

IsEnd always returns true for end continuations

func (*EndContinuation) ToBytes

func (c *EndContinuation) ToBytes() ([]byte, error)

ToBytes always returns nil for end continuations

type EndpointType

type EndpointType int

EndpointType represents how to treat range endpoints in scans

const (
	// EndpointTypeTreeStart indicates the start of the entire tree
	EndpointTypeTreeStart EndpointType = iota
	// EndpointTypeTreeEnd indicates the end of the entire tree
	EndpointTypeTreeEnd
	// EndpointTypeRangeInclusive includes the endpoint in the range
	EndpointTypeRangeInclusive
	// EndpointTypeRangeExclusive excludes the endpoint from the range
	EndpointTypeRangeExclusive
	// EndpointTypeContinuation indicates continuation from a previous scan
	EndpointTypeContinuation
	// EndpointTypePrefixString for prefix-string-based ranges.
	// When used, the tuple is packed and the trailing null byte (string
	// terminator in FDB tuple encoding) is stripped.  For the low
	// endpoint the stripped bytes are used directly.  For the high
	// endpoint the stripped bytes are strinc'd (trailing 0xFF bytes are
	// removed, then the last byte is incremented).
	// Matches Java's EndpointType.PREFIX_STRING.
	EndpointTypePrefixString
)

type Event

type Event struct {
	Name  string // machine-readable, e.g. "save_record"
	Title string // human-readable, e.g. "Save Record"
	Kind  Kind   // how the counter's fields are to be read
}

Event represents a measurable instrumentation event. Matches Java's FDBStoreTimer.Events / FDBStoreTimer.Counts.

type ExecuteProperties

type ExecuteProperties struct {
	// IsolationLevel for the execution
	IsolationLevel IsolationLevel

	// ReturnedRowLimit is the maximum number of records to return
	// 0 or negative means no limit
	ReturnedRowLimit int

	// ScannedRecordsLimit is the maximum number of records to scan
	// 0 or negative means no limit
	ScannedRecordsLimit int

	// ScannedBytesLimit is the maximum number of bytes to scan
	// 0 or negative means no limit
	ScannedBytesLimit int64

	// TimeLimit is the maximum time for the operation
	// Zero duration means no limit
	TimeLimit time.Duration

	// DefaultCursorStreamingMode is the default streaming mode for cursors
	DefaultCursorStreamingMode CursorStreamingMode

	// FailOnScanLimitReached determines if hitting scan limits should fail the operation
	FailOnScanLimitReached bool

	// Skip is the number of records to skip before returning results.
	// Matches Java's ExecuteProperties.getSkip().
	Skip int

	// MaterializationLimit caps the number of rows that may be buffered
	// in memory by operators that materialize an entire relation (NLJ
	// inner, UNION buffered, recursive CTE levels). Zero means use
	// DefaultMaterializationLimit.
	MaterializationLimit int

	// State is the statement-scoped mutable counter object (RFC-130) — a
	// Go-only extension (the statement-wide in-memory buffering byte budget;
	// Java has no memory-specific SQLSTATE or ExecuteState field for this) that
	// borrows the SAME reference-holding PATTERN Java's ExecuteState uses
	// (a pointer inside the otherwise value-copied ExecuteProperties), but is
	// NOT a content port of Java's ExecuteState — see ScanState below for the
	// field that actually corresponds structurally to Java's ExecuteState
	// (the RecordScanLimiter/ByteScanLimiter pair). It is a POINTER, so every
	// value-copy of ExecuteProperties (the WithX helpers, ClearSkipAndLimit,
	// per-operator innerProps) shares ONE counter and none of them reset it —
	// the statement-wide memory byte budget survives all inner-plan resets
	// structurally, which is the whole point. It is always minted once per
	// statement (never nil) where the statement's ExecuteProperties is first
	// built; the "no budget" case is memLimit<=0, NOT a nil State, so a missed
	// accumulation site charges an unlimited counter rather than silently
	// no-oping. None of the WithX helpers below clear it: they copy the value
	// struct, which copies the pointer.
	State *ExecuteState

	// ScanState is the execution-scoped mutable counter set (scanned records,
	// scanned bytes, start-of-execution time) that ScannedRecordsLimit /
	// ScannedBytesLimit / TimeLimit are checked against — the type that
	// actually corresponds structurally to Java's ExecuteState
	// (ExecuteState.java: the RecordScanLimiter + ByteScanLimiter pair), named
	// ScanLimiterState in Go only because the name ExecuteState was already
	// taken by the unrelated Go-only RFC-130 type above (State's doc comment).
	// Do not read the two fields' similar shape as the SAME Java concept split
	// two ways — State has no Java analog at all; ScanState is the one with a
	// direct Java counterpart. Like State, ScanState is a POINTER shared by
	// every value-copy of ExecuteProperties, so every leg of an IN-join/
	// IN-union (which recurses via ClearSkipAndLimit, never a fresh
	// DefaultExecuteProperties) charges the SAME counters — see
	// ScanLimiterState's doc comment for the hang this fixes. UNLIKE State,
	// ScanState is page/transaction-scoped, not statement-scoped: it is minted
	// fresh by EVERY call to DefaultExecuteProperties (never nil from that
	// constructor, matching Java's @Nonnull ExecuteState), and
	// paginatingRows.executeProps() calls DefaultExecuteProperties exactly once
	// per page, INSIDE the retried transaction closure — so every retry
	// attempt and every page gets its own honest budget, matching Java's
	// CursorLimitManager being rebuilt from a fresh FDBRecordContext each
	// attempt. A raw ExecuteProperties struct literal leaves ScanState nil,
	// and every leaf cursor falls back to a private per-cursor instance in
	// that case — identical to the pre-fix behavior.
	ScanState *ScanLimiterState

	// DryRun, when true, makes data-modification execution validate and PREVIEW
	// each mutation via the store's DryRun* primitives instead of staging a real
	// write — Java's ExecuteProperties.setDryRun / isDryRun(). Statement-scoped:
	// it is sourced from the SQL OPTIONS (DRY RUN) clause carried on
	// paginatingRows, never from a connection option (a connection-scoped flag
	// would go sticky across pooled statements).
	DryRun bool
}

ExecuteProperties holds properties that pertain to an entire execution

func DefaultExecuteProperties

func DefaultExecuteProperties() ExecuteProperties

DefaultExecuteProperties returns properties with sensible defaults. Mints a fresh ScanState every call (never nil) — matching Java's ExecuteProperties.Builder.build(), which always constructs an ExecuteState (@Nonnull) even when no limit is configured (ExecuteProperties.java:598-608). Every caller of DefaultExecuteProperties (directly or via ForwardScan/ ReverseScan) therefore gets its OWN counter set, shared with every downstream copy of the returned value (WithX helpers, ClearSkipAndLimit, IN-join/IN-union leg recursion) but never with a DIFFERENT top-level call — exactly the per-execution-attempt scope CursorLimitManager gets from a fresh FDBRecordContext per transaction.

func DefaultExecutePropertiesIn

func DefaultExecutePropertiesIn(env *dst.Env) ExecuteProperties

DefaultExecutePropertiesIn is DefaultExecuteProperties with its scan/time budget anchored on env's clock. A nil env is production (wall clock) and is exactly what DefaultExecuteProperties passes, so the two are byte-identical outside a simulation.

It exists as a sibling rather than as a signature change because DefaultExecuteProperties has ~900 call sites and only ONE of them arms a time limit under simulation — the SQL page loop. Threading an env through the other 899 would be churn that obscures the one edit that matters.

func (ExecuteProperties) ClearRowAndTimeLimits

func (e ExecuteProperties) ClearRowAndTimeLimits() ExecuteProperties

ClearRowAndTimeLimits returns a copy with row limit and time limit cleared. Matches Java's ExecuteProperties.clearRowAndTimeLimits().

func (ExecuteProperties) ClearSkipAndLimit

func (e ExecuteProperties) ClearSkipAndLimit() ExecuteProperties

ClearSkipAndLimit returns a copy with skip and row limit cleared. Matches Java's ExecuteProperties.clearSkipAndLimit().

func (ExecuteProperties) GetMaterializationLimit

func (e ExecuteProperties) GetMaterializationLimit() int

GetMaterializationLimit returns the effective materialization limit, falling back to DefaultMaterializationLimit when zero.

func (ExecuteProperties) WithDryRun

func (e ExecuteProperties) WithDryRun(dryRun bool) ExecuteProperties

WithDryRun returns a copy with the dry-run flag set. Java's ExecuteProperties.setDryRun.

func (ExecuteProperties) WithIsolationLevel

func (e ExecuteProperties) WithIsolationLevel(level IsolationLevel) ExecuteProperties

WithIsolationLevel returns a copy with the specified isolation level

func (ExecuteProperties) WithMaterializationLimit

func (e ExecuteProperties) WithMaterializationLimit(limit int) ExecuteProperties

WithMaterializationLimit returns a copy with the specified materialization limit.

func (ExecuteProperties) WithReturnedRowLimit

func (e ExecuteProperties) WithReturnedRowLimit(limit int) ExecuteProperties

WithReturnedRowLimit returns a copy with the specified row limit

func (ExecuteProperties) WithScannedBytesLimit

func (e ExecuteProperties) WithScannedBytesLimit(limit int64) ExecuteProperties

WithScannedBytesLimit returns a copy with the specified scanned bytes limit.

func (ExecuteProperties) WithScannedRecordsLimit

func (e ExecuteProperties) WithScannedRecordsLimit(limit int) ExecuteProperties

WithScannedRecordsLimit returns a copy with the specified scanned records limit.

func (ExecuteProperties) WithSkip

func (e ExecuteProperties) WithSkip(skip int) ExecuteProperties

WithSkip returns a copy with the specified skip count.

func (ExecuteProperties) WithTimeLimit

func (e ExecuteProperties) WithTimeLimit(limit time.Duration) ExecuteProperties

WithTimeLimit returns a copy with the specified time limit

type ExecuteState

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

ExecuteState holds the mutable, statement-scoped counters for a single statement execution. It mirrors Java's com.apple.foundationdb.record.ExecuteState (ExecuteState.java:44-47): the counters live in a small mutable object held BY REFERENCE inside the otherwise value-copied ExecuteProperties, so every value-copy of the properties (the WithX helpers, ClearSkipAndLimit, per-operator innerProps) shares one counter and none of them reset it. Statement-wide survival is therefore STRUCTURAL — exactly as in Java, where clearSkipAndLimit (:240-245) preserves the ExecuteState for free because it only zeroes skip/rowLimit.

RFC-130 minted the memory byte budget that bounds in-memory buffering operators (the row-count MaterializationLimit cannot stop 100k LARGE rows from OOMing). recursionLevels below is a second, unrelated statement-scoped counter set that reuses the same by-reference survival pattern for a different purpose (see its own doc comment). The scan/byte/time counters Go currently tracks per-cursor are the correct future home here too, to fully match Java — out of scope for RFC-130, noted for the divergence ledger.

Concurrency: the executor is single-threaded per statement (zero goroutine launches in pkg/recordlayer/query/executor — pinned by package_invariant_test.go), so ChargeMemory/IncrementRecursionLevel need no mutex/atomic. If a future parallel-union breaks that invariant the pinned test fires and the counters move to atomic / a mutex.

func NewExecuteState

func NewExecuteState(memLimit int64) *ExecuteState

NewExecuteState mints a fresh statement-scoped state with the given memory byte limit. A limit <= 0 means unlimited (no budget). The state is ALWAYS minted once per statement — never nil — so a missed accumulation site charges an unlimited counter (a no-op charge) rather than silently no-oping via a nil receiver, which would make a missed wiring an invisible bypass instead of a visible (if unbounded) charge.

func (*ExecuteState) ChargeMemory

func (s *ExecuteState) ChargeMemory(n int64) error

ChargeMemory adds n bytes to the statement's running memory total and returns a *MemoryLimitExceededError if the total then exceeds the budget.

A nil receiver or a non-positive memLimit means "no budget" and is a no-op (memUsed is still accumulated for an active budget, but a <=0 limit short- circuits before touching the counter so the unlimited path stays free). The charge is applied BEFORE the check, so the error carries the would-be total — the buffer that breaches the budget is never kept by the caller, which errors out instead of appending.

func (*ExecuteState) HasMemLimit

func (s *ExecuteState) HasMemLimit() bool

HasMemLimit reports whether a positive memory budget is active. Callers MUST gate any expensive byte-estimate computation (e.g. the per-row proto Size() walk in estimateQueryResultBytes) on this, so the default/unlimited path pays nothing — RFC-130's zero-overhead-when-off invariant. nil receiver or memLimit<=0 → false.

func (*ExecuteState) IncrementRecursionLevel

func (s *ExecuteState) IncrementRecursionLevel(id any) int

IncrementRecursionLevel bumps and returns the level count tracked under id.

This is the STATEMENT-scoped half of the streaming level-union recursive CTE's cycle guard: RecordQueryRecursiveLevelUnionPlan's UNION ALL arm is a pure streaming cursor with no fixpoint/dedup (Java has none either — this cap is a Go-only safety extension), so a cyclic self-referential CTE only terminates via a hard recursion-depth cap. That cap must count LEVELS FOR THE WHOLE STATEMENT, but the streaming cursor itself is rebuilt fresh on every page (a new *recursiveUnionCursor per fetchPage, per the SQL layer's "no cursor persists across transactions" architecture) — a counter living on the cursor resets to zero every page and a cyclic CTE that pages mid-recursion would never trip it. ExecuteState is the vehicle already used for exactly this shape of problem (RFC-130's memory budget): a pointer the SQL layer mints ONCE per statement (cascades_generator.go's paginatingRows.execState) and threads into every page's ExecuteProperties.State, so it survives the per-page cursor rebuild structurally.

id anchors the count to one INVOCATION of a recursive construct — the caller (recursiveUnionCursor) passes a key combining the recursive-CTE plan node's own pointer (stable across every page of one statement execution: the SQL layer rebuilds the cursor hierarchy each page but reuses the same plan tree) with a branch scope identifying which concurrently-live invocation this is, when more than one can execute the same plan pointer at once (a sorted RecordQueryInUnionPlan runs one cursor per IN value, merged in lockstep — see recursionInvocationKey's doc comment in recursive_union_cursor.go for why plan pointer alone is not a safe key). ExecuteState itself doesn't interpret id beyond map-key equality; it just needs to be stable across one invocation's own resumes and distinct across concurrently-live invocations, whatever shape the caller builds it in. Two independent recursive CTEs in the same statement get INDEPENDENT budgets (distinct plan pointers, hence distinct keys), matching the eager recursion arms' own per-call-local bound rather than sharing one statement-wide total.

SCOPE, stated exactly: the guard covers one *ExecuteState's lifetime, i.e. one Execute() call / one statement execution — cascades_generator.go mints a fresh ExecuteState per Execute and never reuses one across statements or connections. A continuation resumed by a DIFFERENT statement execution cannot inherit the count (there is no such thing today: Go's SQL layer rejects caller-supplied continuations outright, see cascadesPlan.Execute, so this is not a live gap, just an honest boundary of the mechanism).

A nil receiver returns 0 on every call — it cannot persist anything, by construction. This is a Go-only safety cap, not an RFC-130 resource budget, so callers must not treat a nil ExecuteState as "no cap": the caller (recursiveUnionCursor.checkDepth) always combines this with its own per-cursor-instance counter and uses whichever is larger, so a nil State still bounds runaway growth within one page/cursor lifetime — identical to the pre-fix behavior — even though it can no longer see across pages.

func (*ExecuteState) MemLimit

func (s *ExecuteState) MemLimit() int64

MemLimit returns the configured memory byte budget (<= 0 == unlimited).

func (*ExecuteState) MemUsed

func (s *ExecuteState) MemUsed() int64

MemUsed returns the bytes charged against this state so far. Test/diagnostic accessor; production code never reads it.

func (*ExecuteState) ReleaseMemory

func (s *ExecuteState) ReleaseMemory(n int64)

ReleaseMemory returns n previously ChargeMemory'd bytes to the statement budget. The budget bounds LIVE buffered bytes: a buffer that outlives its cursor by round-tripping through a continuation is re-charged at resume injection, so the producing cursor must release its charges at teardown — otherwise a multi-page statement re-accounts the same buffer once per page and a compliant query trips the limit merely by crossing page boundaries. Callers release exactly what they charged; nil receiver / no budget is a no-op mirroring ChargeMemory.

func (*ExecuteState) ResetRecursionLevel

func (s *ExecuteState) ResetRecursionLevel(id any)

ResetRecursionLevel zeroes (deletes) the level count tracked under id. The caller uses this at two distinct moments with the same effect — freeing an entry that must not linger — for different reasons:

  1. At the start of a FRESH invocation (continuation==nil at cursor construction), to guard against a stale entry from a prior invocation that shared this exact id and somehow didn't clean up via (2). Without this, a recursive CTE nested in a correlated subquery — re-invoked once per outer row (recursive_union_cursor.go's newRecursiveUnionCursor is called fresh, continuation==nil, from flatMapCursor.OnNext for every outer row — see its doc comment) and getting the SAME id every row, since sequential invocations aren't distinguished by branch scope — could have its count over-accumulate across invocations instead of starting each at zero. A resume of an in-flight invocation (paging mid-recursion) always carries a non-nil continuation and must NOT reset, or the original bug (a cyclic recursion that pages mid-recursion streaming forever because its counter resets every page) returns.
  2. When an invocation ENDS — naturally (source exhausted, no further continuation) or by aborting (the depth cap itself firing) — so a statement driving many sequential or concurrent invocations doesn't accumulate unbounded dead map entries for ids that will never be touched again. See recursiveUnionCursor.OnNext and checkDepth for where each fires.

A nil receiver or a never-populated map is a no-op — nothing to reset.

func (*ExecuteState) RestoreRecursionLevels

func (s *ExecuteState) RestoreRecursionLevels(snapshot map[any]int)

RestoreRecursionLevels puts the recursion-level counts back to a snapshot taken by SnapshotRecursionLevels, discarding anything counted since. A nil snapshot restores the empty state — which is the correct meaning, not a no-op: it is what a snapshot taken before the first level was counted says.

Restoring rather than subtracting is deliberate: an attempt may have both incremented some ids and RESET others (checkDepth frees an entry when an invocation ends or aborts), so only wholesale replacement returns the map to what the retried work will re-derive.

func (*ExecuteState) SnapshotRecursionLevels

func (s *ExecuteState) SnapshotRecursionLevels() map[any]int

SnapshotRecursionLevels copies the recursion-level counts so a caller that is about to run a RETRYABLE unit of work can restore them if that work is re-executed. Returns nil when there is nothing tracked (the overwhelmingly common case — no recursive CTE in the statement), which RestoreRecursionLevels handles as "restore to empty".

WHY ONLY THIS MEMBER. ExecuteState's two mutable members answer different questions and therefore roll back differently:

  • memUsed is a LIVE-OCCUPANCY gauge, not a running total. Every charge has a matching release on teardown (boundedBuffer.charged → ReleaseMemory, chargeReleasingCursor.Close), and the SQL layer's page closure closes its result set on the way out of a failed attempt as well as a successful one. A re-executed attempt therefore returns the gauge to its own entry value without help, and rolling it back HERE would be wrong twice over: it would double-release what teardown already released, and it would discard charges belonging to buffers that are still live because they outlive the page. Statement-cumulative by design, deliberately not rolled back.
  • recursionLevels is a POSITION: it counts how far into a resumable recursion this statement has walked, and a resumed cursor deliberately does NOT reset it (newRecursiveUnionCursor only resets on a nil continuation, or a cyclic CTE that pages mid-recursion would never trip the cap). That is exactly what makes it unsafe under re-execution — an attempt that walked k levels and then failed leaves those k counted, and the re-execution walks the same k again. A finite CTE approaching the cap then fails with a depth error it did not earn. Position, so it rolls back with the rest of the page's position.

A member added later belongs on one of those two sides, and the question to ask is not "is it a counter" but "does re-executing the work that moved it move it again": if yes it is position and belongs here.

type FDBDatabase

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

FDBDatabase provides access to the underlying FoundationDB database or tenant and manages transaction execution with retry logic. This is the Record Layer equivalent of Java's FDBDatabase.

The transactor field can be either an fdb.Database or fdb.Tenant, both of which implement the fdb.Transactor interface. This allows transparent support for both regular database operations and tenant-isolated operations.

func NewFDBDatabase

func NewFDBDatabase(db fdb.Database) *FDBDatabase

NewFDBDatabase creates a new FDBDatabase wrapping the core FDB database

func NewFDBDatabaseFromTenant

func NewFDBDatabaseFromTenant(tenant fdb.Tenant) *FDBDatabase

NewFDBDatabaseFromTenant creates a new FDBDatabase wrapping an FDB tenant for tenant-isolated operations. All operations will be scoped to the tenant's keyspace.

func NewFDBDatabaseWithBackend

func NewFDBDatabaseWithBackend(backend fdb.BackendDatabase) *FDBDatabase

NewFDBDatabaseWithBackend creates an FDBDatabase driven by a build-selected fdb backend (RFC-109) — e.g. the libfdb_c escape hatch, opened via fdbclient.Open (which a build tag points at the pure-Go or libfdb_c client). The backend drives the Run / RunRead gold path (record save/load, query, index maintenance) through the Transactor interface.

The concrete-db slot is left empty for a non-pure-Go backend. Standalone transactions go through CreateWritableTransaction and locality through LocalityGetBoundaryKeys — both delegate to the backend's own interface-returning methods, so explicit transactions, the FDBDatabaseRunner, AND online MUTUAL indexing all work on libfdb_c. The one remaining concrete-only path is the pure-Go-typed CreateTransaction (it returns the concrete fdb.Transaction a non-pure-Go backend cannot build), which stays pure-Go-only in v1 and fails fast with BackendCapabilityError (not a nil panic) — the same scope boundary the RFC draws around tenants.

func NewFDBDatabaseWithTransactor

func NewFDBDatabaseWithTransactor(transactor fdb.Transactor, db fdb.Database) *FDBDatabase

NewFDBDatabaseWithTransactor creates a new FDBDatabase with a custom Transactor. The transactor is used for Run()/RunWithVersionstamp() (transaction execution), while the db is used for CreateTransaction() (direct transaction creation). This enables wrapping the transactor for fault injection, tracing, or middleware.

func (*FDBDatabase) CreateTransaction

func (d *FDBDatabase) CreateTransaction() (fdb.Transaction, error)

CreateTransaction creates a new transaction without retry logic. This is primarily used for testing scenarios where manual transaction control is needed, such as testing isolation levels with concurrent transactions. For tenant-isolated databases, the transaction will be scoped to the tenant's keyspace.

func (*FDBDatabase) CreateWritableTransaction

func (d *FDBDatabase) CreateWritableTransaction() (fdb.WritableTransaction, error)

CreateWritableTransaction creates a standalone, non-retry transaction as the backend-agnostic WritableTransaction interface. Unlike CreateTransaction (which returns the concrete pure-Go fdb.Transaction and is therefore pure-Go-only), this works on ANY backend — including the libfdb_c escape hatch — so the SQL engine's database/sql explicit transactions (BeginTx / COMMIT, which span multiple driver calls and so can't use the closure-based Run gold path) are not silently pure-Go-only. Prefer this over CreateTransaction wherever a concrete pure-Go handle isn't specifically required.

func (*FDBDatabase) Env

func (d *FDBDatabase) Env() *dst.Env

Env returns the database's DST environment, or nil (which the *dst.Env accessors treat as production). Nil-safe on the receiver, like FDBRecordContext.Env: an accessor whose whole contract is "nil means production" must not be the one call in the chain that panics.

func (*FDBDatabase) GetStoreStateCache

func (d *FDBDatabase) GetStoreStateCache() FDBRecordStoreStateCache

GetStoreStateCache returns the current store state cache. Matches Java's FDBDatabase.getStoreStateCache().

func (*FDBDatabase) LocalityGetBoundaryKeys

func (d *FDBDatabase) LocalityGetBoundaryKeys(r fdb.ExactRange, limit int, readVersion int64) ([]fdb.Key, error)

LocalityGetBoundaryKeys returns the FDB shard boundary keys within r, working on both the pure-Go and libfdb_c backends (the online MUTUAL indexer uses them to partition the keyspace into fragments for concurrent building). It's a read of the \xff/keyServers system range — byte-identical on the pure-Go and libfdb_c clients against the same cluster — so mutual indexing parallelizes on either backend. A handle that can't provide it (a tenant-backed FDBDatabase, or a custom transactor) returns BackendCapabilityError; callers that want graceful degradation (1 fragment) treat an error as "no boundaries".

Unlike CreateWritableTransaction there is no tenant branch: shard boundaries are a cluster-wide property of the keyServers map, independent of any tenant.

func (*FDBDatabase) NewRecordContext

func (d *FDBDatabase) NewRecordContext(tx fdb.WritableTransaction) *FDBRecordContext

NewRecordContext wraps an externally-created FDB transaction in a context that inherits everything this database gives the contexts it opens itself: the DST environment and the StoreTimer. It is the form every caller holding an *FDBDatabase should use — SQL's explicit BeginTx→COMMIT path among them.

The point is that per-database state is threaded in ONE place. NewFDBRecordContext takes the fields one by one, which is why env was silently dropped on the explicit-transaction path until someone noticed; adding the timer as a second such parameter would have set up the identical failure for instrumentation, where the symptom is even quieter (a metric that reads zero looks exactly like a workload that did nothing).

func (*FDBDatabase) Run

func (d *FDBDatabase) Run(ctx context.Context, fn func(rtx *FDBRecordContext) (any, error)) (any, error)

Run executes a function within a transaction with automatic retry handling. Before committing, flushes any queued versionstamp mutations. Matches Java's FDBRecordContext.commitAsync() behavior.

func (*FDBDatabase) RunRead

func (d *FDBDatabase) RunRead(ctx context.Context, fn func(rtx fdb.ReadTransaction) (any, error)) (any, error)

RunRead executes a read-only function with automatic retry but no commit. Uses ReadTransact under the hood — no write conflict ranges, no commit round-trip. Suitable for statistics, metadata reads, or any read-only operation where a full read-write transaction would waste a commit.

ctx bounds the read-retry loop + backoff when the transactor supports it (fdb.CtxReadTransactor); the entry check also returns early if already cancelled.

func (*FDBDatabase) RunWithVersionstamp

func (d *FDBDatabase) RunWithVersionstamp(ctx context.Context, fn func(rtx *FDBRecordContext) (any, error)) (any, []byte, error)

RunWithVersionstamp is like Run but also returns the committed versionstamp. Use this when you need the versionstamp after commit (e.g. for record versioning). Returns (result, versionstamp, error). Versionstamp is nil for read-only transactions.

func (*FDBDatabase) RunWithWeakReads

func (d *FDBDatabase) RunWithWeakReads(ctx context.Context, weak WeakReadSemantics, fn func(rtx *FDBRecordContext) (any, error)) (any, error)

RunWithWeakReads is like Run but applies weak read semantics to the transaction. If IsCausalReadRisky is set, the transaction reads from any replica. Matches Java's FDBDatabase.openContext(config, timer, weakReadSemantics, ...).

func (*FDBDatabase) SetEnv

func (d *FDBDatabase) SetEnv(env *dst.Env) *FDBDatabase

SetEnv installs the DST environment inherited by every context this database opens. A simulation driver calls this with dst.NewSim(seed) right after constructing the database (typically over a SimFDB backend); production leaves it unset. Returns d for chaining.

func (*FDBDatabase) SetStoreStateCache

func (d *FDBDatabase) SetStoreStateCache(cache FDBRecordStoreStateCache)

SetStoreStateCache sets the cache used for store state across transactions. Matches Java's FDBDatabase.setStoreStateCache().

func (*FDBDatabase) SetTimer

func (d *FDBDatabase) SetTimer(timer *StoreTimer)

SetTimer installs the StoreTimer that every context this database opens will record into, replacing any previous one. Passing nil disables instrumentation.

Matches Java's FDBDatabaseRunner.setTimer / FDBRecordContextConfig.Builder.setTimer (FDBDatabaseRunner.java:112-119, FDBRecordContextConfig.java:357-368): the timer is a caller-supplied object whose lifetime and scope the caller chooses, never something the database owns or creates for you. Contexts already open keep the timer they were built with — the read happens once at context construction, as in Java.

func (*FDBDatabase) Timer

func (d *FDBDatabase) Timer() *StoreTimer

Timer returns the StoreTimer installed by SetTimer, or nil when the database is uninstrumented. Matches Java's FDBDatabaseRunner.getTimer (FDBDatabaseRunner.java:105-110).

type FDBDatabaseFactory

type FDBDatabaseFactory struct {

	// StoreStateCacheFactory creates a store state cache for each new database.
	// If nil, PassThroughStoreStateCache is used.
	StoreStateCacheFactory func() FDBRecordStoreStateCache
	// contains filtered or unexported fields
}

FDBDatabaseFactory caches FDBDatabase instances by cluster file path. Thread-safe. Matches Java's FDBDatabaseFactory.getDatabase(clusterFile).

func NewFDBDatabaseFactory

func NewFDBDatabaseFactory() *FDBDatabaseFactory

NewFDBDatabaseFactory creates a factory for caching database instances.

func (*FDBDatabaseFactory) GetDatabase

func (f *FDBDatabaseFactory) GetDatabase(clusterFile string) (*FDBDatabase, error)

GetDatabase returns a cached FDBDatabase for the given cluster file path. Creates a new one on first call for each unique path. Matches Java's FDBDatabaseFactory.getDatabase(clusterFile).

type FDBDatabaseRunner

type FDBDatabaseRunner struct {

	// MaxAttempts is the maximum number of retry attempts (default 10).
	MaxAttempts int

	// InitialDelay is the initial delay between retries (default 10ms).
	InitialDelay time.Duration

	// MaxDelay is the maximum delay between retries (default 1s).
	MaxDelay time.Duration

	// ContextConfig is applied to each transaction.
	ContextConfig *RecordContextConfig
	// contains filtered or unexported fields
}

FDBDatabaseRunner provides configurable retry logic for FDB transactions. Matches Java's FDBDatabaseRunnerImpl.

func NewFDBDatabaseRunner

func NewFDBDatabaseRunner(db *FDBDatabase) *FDBDatabaseRunner

NewFDBDatabaseRunner creates a runner with default settings.

func (*FDBDatabaseRunner) OpenContext

func (r *FDBDatabaseRunner) OpenContext(ctx context.Context) (*FDBRecordContext, error)

OpenContext creates a new FDBRecordContext with a fresh transaction, applying the runner's context configuration. Matches Java's FDBDatabaseRunner.openContext(). The caller is responsible for committing or cancelling the transaction.

func (*FDBDatabaseRunner) RunWithRetry

func (r *FDBDatabaseRunner) RunWithRetry(ctx context.Context, fn func(rtx *FDBRecordContext) (any, error)) (any, error)

RunWithRetry executes fn with configurable retry logic and exponential backoff. Retries on FDB retryable errors (conflict, etc.) up to MaxAttempts times. Non-retryable errors are returned immediately. Matches Java's FDBDatabaseRunnerImpl.run().

func (*FDBDatabaseRunner) SetContextConfig

func (r *FDBDatabaseRunner) SetContextConfig(config *RecordContextConfig) *FDBDatabaseRunner

SetContextConfig sets the transaction context configuration.

func (*FDBDatabaseRunner) SetInitialDelay

func (r *FDBDatabaseRunner) SetInitialDelay(d time.Duration) *FDBDatabaseRunner

SetInitialDelay sets the initial retry delay.

func (*FDBDatabaseRunner) SetMaxAttempts

func (r *FDBDatabaseRunner) SetMaxAttempts(n int) *FDBDatabaseRunner

SetMaxAttempts sets the maximum retry attempts.

func (*FDBDatabaseRunner) SetMaxDelay

func (r *FDBDatabaseRunner) SetMaxDelay(d time.Duration) *FDBDatabaseRunner

SetMaxDelay sets the maximum retry delay.

type FDBIndexedRecord

type FDBIndexedRecord struct {
	IndexEntry *IndexEntry
	Record     *FDBStoredRecord[proto.Message]
}

FDBIndexedRecord wraps a record that was found via an index scan. Contains both the index entry used to locate the record and the record itself. Matches Java's com.apple.foundationdb.record.provider.foundationdb.FDBIndexedRecord.

type FDBMetaDataStore

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

FDBMetaDataStore stores and retrieves RecordMetaData from FDB. The metadata is stored as a serialized protobuf at a well-known key, enabling runtime schema evolution without application redeployment. Matches Java's FDBMetaDataStore.

func NewFDBMetaDataStore

func NewFDBMetaDataStore(ss subspace.Subspace) *FDBMetaDataStore

NewFDBMetaDataStore creates a metadata store at the given subspace. The evolution validator defaults to the strictest configuration, matching Java's `evolutionValidator = MetaDataEvolutionValidator.getDefaultInstance()`.

func (*FDBMetaDataStore) LoadRecordMetaDataProto

func (s *FDBMetaDataStore) LoadRecordMetaDataProto(tx fdb.WritableTransaction) (*gen.MetaData, error)

LoadRecordMetaDataProto loads the current MetaData proto from FDB. Returns nil if no metadata has been stored. Uses SplitHelper for wire compatibility with Java's FDBMetaDataStore. Matches Java's FDBMetaDataStore.loadRecordMetaData().

func (*FDBMetaDataStore) LoadRecordMetaDataProtoAtVersion

func (s *FDBMetaDataStore) LoadRecordMetaDataProtoAtVersion(tx fdb.WritableTransaction, version int32) (*gen.MetaData, error)

LoadRecordMetaDataProtoAtVersion loads a historical version of the metadata. Returns nil if the version doesn't exist. Uses SplitHelper for wire compatibility with Java's FDBMetaDataStore.

func (*FDBMetaDataStore) SaveRecordMetaData

func (s *FDBMetaDataStore) SaveRecordMetaData(tx fdb.WritableTransaction, metaDataProto *gen.MetaData) error

SaveRecordMetaData saves a MetaData proto to FDB, with the full validation Java runs in the same transaction — this is NOT a raw persist. In order (matching Java's FDBMetaDataStore.saveAndSetCurrent):

  1. the new proto must build into a RecordMetaData;
  2. if metadata is already stored: the new version must be strictly greater (MetaDataVersionMustIncreaseError otherwise), the evolution validator must pass old → new, and the old serialized bytes are archived at HISTORY_KEY_PREFIX + oldVersion;
  3. the new metadata is written at CURRENT_KEY.

Because all of this happens inside the caller's transaction, FDB conflict detection serializes concurrent evolvers — two racing saves against the same old version cannot both commit.

Uses SplitHelper for wire compatibility with Java's FDBMetaDataStore, which stores metadata with split support (unsplit suffix 0).

func (*FDBMetaDataStore) SetEvolutionValidator

func (s *FDBMetaDataStore) SetEvolutionValidator(v *MetaDataEvolutionValidator)

SetEvolutionValidator replaces the validator SaveRecordMetaData runs against the currently stored metadata. Matches Java's FDBMetaDataStore.setEvolutionValidator().

func (*FDBMetaDataStore) Subspace

func (s *FDBMetaDataStore) Subspace() subspace.Subspace

Subspace returns the subspace this metadata store uses.

type FDBRecordContext

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

func NewFDBRecordContext

func NewFDBRecordContext(tx fdb.WritableTransaction, env *dst.Env) *FDBRecordContext

NewFDBRecordContext wraps an externally-created FDB transaction — the path SQL's explicit BeginTx→COMMIT takes, plus direct-transaction-control tests.

env is REQUIRED rather than defaulted, and that is the point. This is the sixth construction site of an FDBRecordContext; the other five (Run/RunAsync/ReadOnly in this file and the two in runner.go) all thread d.env, and this one silently did not. The consequence was that every explicit transaction ran with env==nil — production seams, wall clock, crypto/rand — even under a fully seeded simulation environment. That is precisely the path an explicit-COMMIT semantics RFC needs to replay, so it was the one path the seam did not cover. A default here would reintroduce the same silence; passing nil is allowed but has to be written down.

Callers holding an *FDBDatabase should NOT call this — call db.NewRecordContext(tx), which threads every piece of per-database state at once. Enumerating the fields at each call site is what let env go unthreaded here in the first place, and a second such field (the StoreTimer) would have repeated it exactly.

func (*FDBRecordContext) AddCommitCheck

func (rc *FDBRecordContext) AddCommitCheck(check CommitCheckFunc)

AddCommitCheck registers a pre-commit check function. All checks run before the transaction commits. If any returns an error, the commit is aborted with that error. Goroutine-safe via commitMu (matches Java's synchronized blocks). Matches Java's FDBRecordContext.addCommitCheck(CommitCheckAsync).

func (*FDBRecordContext) AddPostCommit

func (rc *FDBRecordContext) AddPostCommit(hook PostCommitFunc)

AddPostCommit registers a post-commit callback. All callbacks run after the transaction successfully commits. Goroutine-safe via commitMu (matches Java's synchronized blocks). Matches Java's FDBRecordContext.addPostCommit(PostCommit).

func (*FDBRecordContext) AddReadConflictRange

func (rc *FDBRecordContext) AddReadConflictRange(r fdb.ExactRange) error

AddReadConflictRange adds a read conflict range and tracks it for diagnostics. Goroutine-safe via conflictMu (FDB transaction is already goroutine-safe).

func (*FDBRecordContext) AddToLocalVersionCache

func (rc *FDBRecordContext) AddToLocalVersionCache(versionKey []byte, localVersion int)

AddToLocalVersionCache caches a local version for a version key within this transaction. Goroutine-safe via versionMu. Matches Java's FDBRecordContext.addToLocalVersionCache().

func (*FDBRecordContext) AddVersionMutation

func (rc *FDBRecordContext) AddVersionMutation(mutationType VersionMutationType, versionKey []byte, value []byte)

AddVersionMutation queues a versionstamp mutation to be applied at commit. mutationType selects SET_VERSIONSTAMPED_KEY or SET_VERSIONSTAMPED_VALUE. The key or value (depending on type) must include the versionstamp placeholder bytes. Goroutine-safe via versionMu. Matches Java's FDBRecordContext.addVersionMutation(MutationType, key, value).

func (*FDBRecordContext) Cancel

func (rc *FDBRecordContext) Cancel()

Cancel cancels the transaction

func (*FDBRecordContext) CheckTransactionSize

func (rc *FDBRecordContext) CheckTransactionSize() error

CheckTransactionSize checks the approximate transaction size against the configured warning and error thresholds. Returns TransactionSizeExceededError if the error threshold is exceeded, TransactionSizeWarningError if the warning threshold is exceeded (once per transaction), or nil.

func (*FDBRecordContext) ClaimLocalVersion

func (rc *FDBRecordContext) ClaimLocalVersion() int

ClaimLocalVersion atomically claims the next local version number. Goroutine-safe via atomic.Int32 (matches Java's AtomicInteger.getAndIncrement()).

func (*FDBRecordContext) Commit

func (rc *FDBRecordContext) Commit() error

Commit commits the transaction.

Records EventCommit, as every commit path must. Java has ONE commit method (FDBRecordContext.commitAsync) and it records the event unconditionally (FDBRecordContext.java:513-533); Go splits that method three ways — here, CommitWithHooks and CommitWithVersionstamp — and a split where only one arm records turns the metric into a function of which API the caller happened to pick. Only CommitWithVersionstamp recorded, which is why explicit SQL transactions committed on every COMMIT statement and reported none.

func (*FDBRecordContext) CommitWithHooks

func (rc *FDBRecordContext) CommitWithHooks() error

CommitWithHooks runs pre-commit checks, flushes pending version mutations, commits the FDB transaction, and runs post-commit callbacks. Use this instead of Commit() when the context was created manually (not via Run()).

The EventCommit span starts before the pre-commit checks and ends when the commit resolves, which is Java's interval — its startTimeNanos is taken at the first line of commitAsync, ahead of runCommitChecks, and the post-commit hooks are chained after the event is recorded (FDBRecordContext.java:478, :513-533).

func (*FDBRecordContext) CommitWithVersionstamp

func (rc *FDBRecordContext) CommitWithVersionstamp() ([]byte, error)

CommitWithVersionstamp commits the transaction, first running pre-commit checks, then flushing all queued versionstamp mutations. Returns the committed versionstamp (10 bytes) or nil for read-only transactions / no versionstamp mutations. Runs post-commit hooks after successful commit. Matches Java's FDBRecordContext.commitAsync() which always runs checks and hooks.

func (*FDBRecordContext) Context

func (rc *FDBRecordContext) Context() context.Context

Context returns the Go context

func (*FDBRecordContext) Env

func (rc *FDBRecordContext) Env() *dst.Env

Env returns the DST environment for this context (Clock + Randomness + Buggify), inherited from the FDBDatabase. Never nil in the sense that matters: the returned *dst.Env may be nil, and the *dst.Env accessors (Now/Read/Fault) treat nil as production. Use this at every persisted-byte site instead of time.Now / crypto.rand so a simulation run is byte-reproducible.

func (*FDBRecordContext) GetApproximateTransactionSize

func (rc *FDBRecordContext) GetApproximateTransactionSize() (int64, error)

GetApproximateTransactionSize returns the approximate size in bytes of the transaction's mutations so far. Useful for monitoring proximity to FDB's 10MB transaction size limit. Matches Java's FDBRecordContext.getApproximateTransactionSize().

func (*FDBRecordContext) GetConflictingKeys

func (rc *FDBRecordContext) GetConflictingKeys() []fdb.KeyRange

GetConflictingKeys attempts to identify conflicting keys after a commit failure. Reads the transaction's read conflict ranges. This is a best-effort diagnostic tool. Goroutine-safe via conflictMu. Matches Java's FDBRecordContext.reportConflictingKeys() (diagnostic, not exact).

func (*FDBRecordContext) GetLocalVersion

func (rc *FDBRecordContext) GetLocalVersion(versionKey []byte) (int, bool)

GetLocalVersion retrieves a cached local version for the given key. Returns (localVersion, true) if found, (0, false) otherwise. Goroutine-safe via versionMu.

func (*FDBRecordContext) GetMetaDataVersionStamp

func (rc *FDBRecordContext) GetMetaDataVersionStamp() ([]byte, error)

GetMetaDataVersionStamp reads the metadata version stamp at snapshot isolation. Returns nil if the stamp was written in this transaction (dirty) or doesn't exist. On ACCESSED_UNREADABLE errors (FDB code 1036), marks the stamp as dirty and returns nil. Goroutine-safe via atomic.Bool + goroutine-safe FDB transaction. Matches Java's FDBRecordContext.getMetaDataVersionStampAsync().

func (*FDBRecordContext) GetReadVersion

func (rc *FDBRecordContext) GetReadVersion() (int64, error)

GetReadVersion returns the transaction's read version. Matches Java's FDBRecordContext.getReadVersion().

func (*FDBRecordContext) HasDirtyStoreState

func (rc *FDBRecordContext) HasDirtyStoreState() bool

HasDirtyStoreState returns true if any store state was modified in this transaction. When true, cached store state should not be used. Goroutine-safe via atomic.Bool. Matches Java's FDBRecordContext.hasDirtyStoreState().

func (*FDBRecordContext) HasVersionMutations

func (rc *FDBRecordContext) HasVersionMutations() bool

HasVersionMutations returns true if there are pending version mutations. Goroutine-safe via versionMu.

func (*FDBRecordContext) PutSession

func (rc *FDBRecordContext) PutSession(key string, value any)

PutSession stores a transaction-scoped value under key. Matches Java's FDBRecordContext session storage; values die with the context.

func (*FDBRecordContext) ReadTransaction

func (rc *FDBRecordContext) ReadTransaction(snapshot bool) fdb.ReadTransaction

ReadTransaction returns the transaction a READ should go through for the given isolation: the snapshot view (no read conflict ranges) when snapshot is true, the plain transaction (conflict-tracking) otherwise.

This is the ONE place the snapshot/serializable choice is made, so that "serializable" is a property of the mechanism rather than of each leaf remembering to ask. Java has exactly this method — FDBRecordContext.readTransaction(boolean) (FDBRecordContext.java:660-666) — and every Java leaf resolves through it with the same expression: context.readTransaction(scanProperties.getExecuteProperties() .getIsolationLevel().isSnapshot()) (KeyValueCursorBase.java:358, VectorIndexMaintainer.java:201-202, TextIndexMaintainer.java:542, RankIndexMaintainer.java:292).

A leaf that calls Transaction().Snapshot() directly instead is not making a local choice — it is silently overriding the caller's isolation, which reads as isolated and commits as non-serializable.

func (*FDBRecordContext) RemoveLocalVersion

func (rc *FDBRecordContext) RemoveLocalVersion(versionKey []byte)

RemoveLocalVersion removes a cached local version entry. Goroutine-safe via versionMu.

func (*FDBRecordContext) RemoveLocalVersionsInRange

func (rc *FDBRecordContext) RemoveLocalVersionsInRange(begin, end fdb.Key)

RemoveLocalVersionsInRange removes all cached local versions whose key falls in [begin, end). Goroutine-safe via versionMu. Matches Java's FDBRecordContext.removeLocalVersionRange().

func (*FDBRecordContext) RemoveVersionMutation

func (rc *FDBRecordContext) RemoveVersionMutation(versionKey []byte)

RemoveVersionMutation removes a queued version mutation. Goroutine-safe via versionMu.

func (*FDBRecordContext) RemoveVersionMutationsInRange

func (rc *FDBRecordContext) RemoveVersionMutationsInRange(begin, end fdb.Key)

RemoveVersionMutationsInRange removes all queued version mutations whose key falls in [begin, end). Goroutine-safe via versionMu. Matches Java's FDBRecordContext.removeVersionMutationRange().

func (*FDBRecordContext) Session

func (rc *FDBRecordContext) Session(key string) any

Session returns the transaction-scoped value stored under key, or nil. Matches Java's FDBRecordContext.getSession.

func (*FDBRecordContext) SetDirtyStoreState

func (rc *FDBRecordContext) SetDirtyStoreState(dirty bool)

SetDirtyStoreState marks that store state was modified in this transaction. Goroutine-safe via atomic.Bool. Matches Java's FDBRecordContext.setDirtyStoreState().

func (*FDBRecordContext) SetMetaDataVersionStamp

func (rc *FDBRecordContext) SetMetaDataVersionStamp()

SetMetaDataVersionStamp schedules a SET_VERSIONSTAMPED_VALUE mutation on the metadata version key. After commit, this key will contain the commit versionstamp, which invalidates any cached store state entries with older stamps. Goroutine-safe via atomic.Bool + goroutine-safe FDB transaction. Matches Java's FDBRecordContext.setMetaDataVersionStamp().

func (*FDBRecordContext) SetReadVersion

func (rc *FDBRecordContext) SetReadVersion(version int64)

SetReadVersion sets the transaction's read version explicitly. Matches Java's FDBRecordContext.setReadVersion().

func (*FDBRecordContext) SetTimer

func (rc *FDBRecordContext) SetTimer(timer *StoreTimer)

SetTimer sets the instrumentation timer for this context. Goroutine-safe via atomic.Pointer. Matches Java's FDBRecordContext.setTimer().

func (*FDBRecordContext) SetTransactionPriority

func (rc *FDBRecordContext) SetTransactionPriority(priority TransactionPriority) error

SetTransactionPriority sets the priority for this transaction. Matches Java's FDBRecordContext applying FDBTransactionPriority.

func (*FDBRecordContext) Timer

func (rc *FDBRecordContext) Timer() *StoreTimer

Timer returns the instrumentation timer for this context, or nil if not set. Goroutine-safe via atomic.Pointer. Matches Java's FDBRecordContext.getTimer(). Nil-safe on the receiver, for the same reason FDBRecordContext.Env is: every StoreTimer method tolerates a nil timer, so "no instrumentation" is a supported state all the way down — and an accessor at the head of that chain must not be the one call in it that panics. Call sites are of the form store.context.Timer().RecordSince(...), where the nil check would otherwise have to be repeated at each one.

func (*FDBRecordContext) Transaction

func (rc *FDBRecordContext) Transaction() fdb.WritableTransaction

Transaction returns the underlying FDB transaction

func (*FDBRecordContext) TransactionID

func (rc *FDBRecordContext) TransactionID() int64

TransactionID returns a unique ID for this record context. Useful for logging and tracing. Matches Java's FDBRecordContext transaction ID.

func (*FDBRecordContext) UpdateVersionMutation

func (rc *FDBRecordContext) UpdateVersionMutation(mutationType VersionMutationType, versionKey []byte, value []byte, merge func(oldValue, newValue []byte) []byte)

UpdateVersionMutation queues or updates a versionstamp mutation with a merge function. If a mutation for the same key already exists, the merge function decides which value to keep. Goroutine-safe via versionMu. Matches Java's FDBRecordContext.updateVersionMutation(MutationType, key, value, BiFunction).

type FDBRecordStore

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

FDBRecordStore provides record storage operations within a transaction context. This is the main struct for storing and retrieving records.

func (*FDBRecordStore) AcquireReadLock

func (store *FDBRecordStore) AcquireReadLock(key string)

func (*FDBRecordStore) AcquireWriteLock

func (store *FDBRecordStore) AcquireWriteLock(key string)

lockRegistry delegation — matches Java's LockRegistry on FDBRecordContext.

func (*FDBRecordStore) AddBuildProgress

func (store *FDBRecordStore) AddBuildProgress(index *Index, count int64)

AddBuildProgress atomically increments the records-scanned counter for an index build. Matches Java's IndexingBase.tieredMergeAndCommit() → MutationType.ADD at IndexingSubspaces.indexBuildScannedRecordsSubspace().

func (*FDBRecordStore) AddRecordReadConflict

func (store *FDBRecordStore) AddRecordReadConflict(primaryKey tuple.Tuple) error

AddRecordReadConflict adds a read conflict range for the given primary key. This ensures that if another transaction modifies this record before this transaction commits, this transaction will fail with a conflict error.

Java equivalent: FDBRecordStore.addRecordReadConflict(Tuple primaryKey) Java location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java:1222

func (*FDBRecordStore) AddRecordWriteConflict

func (store *FDBRecordStore) AddRecordWriteConflict(primaryKey tuple.Tuple) error

AddRecordWriteConflict adds a write conflict range for the given primary key. This ensures that if another transaction reads this record before this transaction commits, that transaction will fail with a conflict error.

Java equivalent: FDBRecordStore.addRecordWriteConflict(Tuple primaryKey) Java location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java:1228

func (*FDBRecordStore) AddUniquenessViolation

func (store *FDBRecordStore) AddUniquenessViolation(index *Index, indexKey tuple.Tuple, primaryKey tuple.Tuple) error

AddUniquenessViolation records a uniqueness violation for the given index. Used during WRITE_ONLY index builds when a uniqueness conflict is detected. Stores empty bytes as the value (no cross-reference to conflicting PK). Use AddUniquenessViolationWithExisting to store the conflicting PK in the value.

func (*FDBRecordStore) AddUniquenessViolationWithExisting

func (store *FDBRecordStore) AddUniquenessViolationWithExisting(index *Index, indexKey tuple.Tuple, primaryKey tuple.Tuple, existingKey tuple.Tuple) error

AddUniquenessViolationWithExisting records a uniqueness violation with the conflicting PK. Matches Java's StandardIndexMaintainer.addUniquenessViolation(valueKey, primaryKey, existingKey). When existingKey is non-nil, its packed bytes are stored as the FDB value. When existingKey is nil, empty bytes are stored.

func (*FDBRecordStore) AsBuilder

func (store *FDBRecordStore) AsBuilder() *StoreBuilder

AsBuilder creates a new StoreBuilder pre-configured with this store's subspace, metadata, and index rebuild policy. Uses the same context. Matches Java's FDBRecordStore.asBuilder().

func (*FDBRecordStore) ClearAndMarkIndexWriteOnly

func (store *FDBRecordStore) ClearAndMarkIndexWriteOnly(indexName string) (bool, error)

ClearAndMarkIndexWriteOnly clears all index data and sets state to WRITE_ONLY. Used to start a fresh index rebuild. Matches Java's FDBRecordStore.clearAndMarkIndexWriteOnly().

func (*FDBRecordStore) ClearHeaderUserField

func (store *FDBRecordStore) ClearHeaderUserField(key string) error

ClearHeaderUserField removes a user-defined field from the store header. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.clearHeaderUserField().

func (*FDBRecordStore) ClearStoreLockState

func (store *FDBRecordStore) ClearStoreLockState() error

ClearStoreLockState removes the lock state from the store header. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.clearStoreLockStateAsync().

func (*FDBRecordStore) Context

func (store *FDBRecordStore) Context() *FDBRecordContext

Context returns the record context this store is using

func (*FDBRecordStore) CopyBuilder

func (store *FDBRecordStore) CopyBuilder(newContext *FDBRecordContext) *StoreBuilder

CopyBuilder creates a new StoreBuilder with this store's configuration but for a different context (transaction). Used to open the same store in a new transaction. Matches Java's FDBRecordStore.copyBuilder().

func (*FDBRecordStore) CountRecords

func (store *FDBRecordStore) CountRecords(
	ctx context.Context,
	low, high tuple.Tuple,
	lowEndpoint, highEndpoint EndpointType,
	continuation []byte,
	scanProperties ScanProperties,
) (int, error)

CountRecords counts records in a range by scanning the records subspace. Unlike GetRecordCount() which uses atomic counters, this actually scans records. Matches Java's FDBRecordStore.countRecords().

func (*FDBRecordStore) DeleteAllRecords

func (store *FDBRecordStore) DeleteAllRecords() error

DeleteAllRecords deletes all records from the store. Clears all data subspaces except StoreInfoKey (0) and IndexStateSpaceKey (5). Matches Java's FDBRecordStore.deleteAllRecords() which clears everything from records through index state begin, then from index state end through store end.

func (*FDBRecordStore) DeleteIndexEntries

func (store *FDBRecordStore) DeleteIndexEntries(index *Index)

DeleteIndexEntries clears all entries for the given index. Matches Java's StandardIndexMaintainer.deleteWhere() with no predicate.

func (*FDBRecordStore) DeleteIndexEntriesInRange

func (store *FDBRecordStore) DeleteIndexEntriesInRange(index *Index, prefix tuple.Tuple) error

DeleteIndexEntriesInRange clears index entries matching the given tuple prefix. For example, passing tuple.Tuple{"alice"} clears all entries where the first indexed value is "alice".

func (*FDBRecordStore) DeleteRecord

func (store *FDBRecordStore) DeleteRecord(primaryKey tuple.Tuple) (bool, error)

DeleteRecord deletes a record by its primary key, following Java's deleteRecordAsync semantics. Returns true if a record was deleted, false if no record existed with that key. Handles both split and unsplit records via SplitHelper. Matches Java's FDBRecordStore.deleteRecordAsync(Tuple primaryKey)

func (*FDBRecordStore) DeleteRecordsWhere

func (store *FDBRecordStore) DeleteRecordsWhere(prefix tuple.Tuple) error

DeleteRecordsWhere deletes all records whose primary key starts with the given prefix, along with all associated index entries, record versions, and record counts. This is a pure range-clear operation — no scanning.

The prefix must align with every active index's key expression so that index entries can be cleared via range operations. Type-specific indexes for matching types are cleared entirely. Universal indexes must have leading key expression columns that match the PK columns covered by the prefix.

Matches Java's FDBRecordStore.deleteRecordsWhereAsync().

func (*FDBRecordStore) DryRunDeleteRecord

func (store *FDBRecordStore) DryRunDeleteRecord(primaryKey tuple.Tuple) (bool, error)

DryRunDeleteRecord checks whether a record with the given primary key exists and could be deleted, without actually deleting it. Returns true if the record exists (and would be deleted), false if not found. Note: does NOT check store lock state, matching Java's dryRunDeleteRecordAsync which only loads the record without calling validateRecordUpdateAllowed. Matches Java's FDBRecordStore.dryRunDeleteRecordAsync().

func (*FDBRecordStore) DryRunSaveRecord

func (store *FDBRecordStore) DryRunSaveRecord(
	record proto.Message,
	existenceCheck RecordExistenceCheck,
) (*FDBStoredRecord[proto.Message], error)

DryRunSaveRecord validates a save and returns what the stored record would look like if saved, without actually writing data.

Scope — matches Java's FDBRecordStore.saveTypedRecord(isDryRun=true), which early-returns at FDBRecordStore.java:578 BEFORE the store-lock check (validateRecordUpdateAllowed, line 584), serializeAndSaveRecord (staging, line 586) and updateSecondaryIndexes (line 594). The PRIMARY-KEY existence/type checks run EARLIER (Java lines 561-577, before the isDryRun branch), so this keeps them. It deliberately does NOT validate store-lock state, does NOT stage the write, and does NOT run secondary-index maintenance. Consequences, all Java-faithful:

  • a DRY RUN on a FORBID_RECORD_UPDATE-locked store previews SUCCESS (the lock check is skipped — Java's early-return precedes it), and
  • a secondary-UNIQUE conflict is NOT detected in dry-run (only the real save's updateSecondaryIndexes catches it), and
  • a duplicate PK introduced WITHIN the same statement is not seen by a later row (no write is staged between rows).

Doing any of these would make Go's preview STRICTER than Java's — a conformance divergence (Go rejecting a DRY RUN that Java previews as success). Pinned by TestFDB_DmlDryRun_MatchesJavaLightweightValidation and TestFDB_DmlDryRun_LockedStorePreviews.

Matches Java's FDBRecordStore.dryRunSaveRecordAsync().

func (*FDBRecordStore) Env

func (store *FDBRecordStore) Env() *dst.Env

Env exposes the record context's DST environment to index maintainers. Nil-safe (a nil *dst.Env means production); index maintainers draw persisted nonces through it so a simulation run is byte-reproducible.

func (*FDBRecordStore) EstimateIndexSize

func (store *FDBRecordStore) EstimateIndexSize(index *Index) (int64, error)

EstimateIndexSize returns the estimated size in bytes of a specific index. Uses FDB's native GetEstimatedRangeSizeBytes on the index's subspace.

func (*FDBRecordStore) EstimateRecordsSize

func (store *FDBRecordStore) EstimateRecordsSize() (int64, error)

EstimateRecordsSize returns the estimated byte size of the records subspace only. Matches Java's FDBRecordStore.estimateRecordsSizeAsync().

func (*FDBRecordStore) EstimateRecordsSizeInRange

func (store *FDBRecordStore) EstimateRecordsSizeInRange(tupleRange TupleRange) (int64, error)

EstimateRecordsSizeInRange returns the estimated size in bytes of records within the given tuple range. Uses FDB's native GetEstimatedRangeSizeBytes. Matches Java's FDBRecordStore.estimateRecordsSizeAsync(TupleRange).

func (*FDBRecordStore) EstimateStoreSize

func (store *FDBRecordStore) EstimateStoreSize() (int64, error)

EstimateStoreSize returns the estimated byte size of the entire store subspace. Uses FDB's GetEstimatedRangeSizeBytes which provides an approximation. Matches Java's FDBRecordStore.estimateStoreSizeAsync().

func (*FDBRecordStore) EvaluateAggregateFunction

func (store *FDBRecordStore) EvaluateAggregateFunction(
	ctx context.Context,
	recordTypeNames []string,
	fn *IndexAggregateFunction,
	scanRange TupleRange,
	isolationLevel IsolationLevel,
) (tuple.Tuple, error)

EvaluateAggregateFunction evaluates an aggregate function using the best matching index. Returns the aggregate result as a tuple, or nil if no matching entries exist.

For COUNT/SUM indexes: scans all group entries and reduces them. For MIN_EVER/MAX_EVER indexes: scans all group entries and reduces them. For VALUE indexes with MIN/MAX: scans 1 entry in the right direction.

Matches Java's FDBRecordStore.evaluateAggregateFunction().

func (*FDBRecordStore) EvaluateRecordFunction

func (store *FDBRecordStore) EvaluateRecordFunction(
	fn *IndexRecordFunction,
	record *FDBStoredRecord[proto.Message],
) (*int64, error)

EvaluateRecordFunction evaluates an index record function for a specific record. For RANK indexes, this returns the rank of the record's score within its group. Returns nil if the record's value is not in the index (e.g. null field).

Matches Java's FDBRecordStore.evaluateIndexRecordFunction().

func (*FDBRecordStore) EvaluateStoreFunction

func (store *FDBRecordStore) EvaluateStoreFunction(
	fn *StoreRecordFunction,
	record *FDBStoredRecord[proto.Message],
) (any, error)

EvaluateStoreFunction evaluates a store-level function on a record. Unlike EvaluateRecordFunction (which uses indexes), store functions access store state directly.

Currently supported functions:

  • "version": returns the record's FDBRecordVersion (nil if none)

Matches Java's FDBRecordStore.evaluateStoreFunction().

func (*FDBRecordStore) FirstUnbuiltRange

func (store *FDBRecordStore) FirstUnbuiltRange(index *Index) (*RangeSetRange, error)

FirstUnbuiltRange returns the first range of the index that hasn't been built yet. Returns nil, nil if the index is fully built. Matches Java's FDBRecordStore.firstUnbuiltRange(index).

func (*FDBRecordStore) GetAllIndexStates

func (store *FDBRecordStore) GetAllIndexStates() map[string]IndexState

GetAllIndexStates returns a map of all index names to their current states. Indexes without an explicit state entry default to READABLE. Matches Java's FDBRecordStore.getAllIndexStates().

func (*FDBRecordStore) GetAllIndexStatesMap

func (store *FDBRecordStore) GetAllIndexStatesMap() map[string]IndexState

GetAllIndexStatesMap returns a copy of the raw index states map (non-READABLE only). Goroutine-safe via stateMu (read lock). For a complete map including defaulted READABLE states, use GetAllIndexStates().

func (*FDBRecordStore) GetContext

func (store *FDBRecordStore) GetContext() *FDBRecordContext

GetContext returns the record context (transaction wrapper) for this store. Matches Java's FDBRecordStore.getRecordContext().

func (*FDBRecordStore) GetDisabledIndexes

func (store *FDBRecordStore) GetDisabledIndexes() []*Index

GetDisabledIndexes returns all indexes that are in DISABLED state. These indexes are not maintained or readable. Matches Java's FDBRecordStoreBase.getDisabledIndexes() (derived from getAllIndexStates).

func (*FDBRecordStore) GetEnabledIndexes

func (store *FDBRecordStore) GetEnabledIndexes() []*Index

GetEnabledIndexes returns all indexes that are NOT in DISABLED state. Matches Java's FDBRecordStoreBase.getEnabledIndexes().

func (*FDBRecordStore) GetEnabledUniversalIndexes

func (store *FDBRecordStore) GetEnabledUniversalIndexes() []*Index

GetEnabledUniversalIndexes returns universal indexes NOT in DISABLED state. Matches Java's FDBRecordStore.getEnabledUniversalIndexes().

func (*FDBRecordStore) GetFormatVersion

func (store *FDBRecordStore) GetFormatVersion() int32

GetFormatVersion returns the store format version. Goroutine-safe via stateMu (read lock). Matches Java's FDBRecordStore.getFormatVersion().

func (*FDBRecordStore) GetHeaderUserField

func (store *FDBRecordStore) GetHeaderUserField(key string) ([]byte, error)

GetHeaderUserField returns a user-defined field from the store header, or nil if the field is not set. Goroutine-safe via stateMu (read lock).

GATED like Java. getHeaderUserField() routes through validateCanAccessHeaderUserFields() — the SHARED validator at `:3221-3226`, reached from getHeaderUserField at `:3249` — which throws below HEADER_USER_FIELDS(8); Go returned nil, which a caller cannot tell apart from an unset field. Same ambiguity as GetIncarnation, and the same fix: "not set" and "this format cannot hold user fields at all" are different answers.

SetHeaderUserField already gated on this exact version, so the pair was ASYMMETRIC — a write that refused loudly beside a read that answered "absent", which is the shape that makes a caller conclude its write silently failed to persist.

func (*FDBRecordStore) GetIncarnation

func (store *FDBRecordStore) GetIncarnation() (int32, error)

GetIncarnation returns the incarnation counter from the store header. Used for cross-cluster data migration versioning. Goroutine-safe via stateMu (read lock).

GATED like Java. FDBRecordStore.getIncarnation() (`:3502-3506`) throws when the format version is below INCARNATION(13), and Go returned 0 — which a caller cannot tell apart from a store whose incarnation genuinely is 0. That is not a leniency, it is an AMBIGUITY: "this store has no incarnation yet" and "this store's format is too old to HAVE one" are different facts that were returning the same value. A migration keyed off incarnation reads a too-old store as a never-migrated one.

The error is the write gate's twin (UnsupportedFeatureForFormatVersionError), not a new type: read and write are gated on the same version by the same predicate, so a caller that can write the field can read it back.

func (*FDBRecordStore) GetIndexMaintainer

func (store *FDBRecordStore) GetIndexMaintainer(index *Index) (IndexMaintainer, error)

GetIndexMaintainer returns the index maintainer for the given index. Matches Java's FDBRecordStore.getIndexMaintainer().

func (*FDBRecordStore) GetIndexState

func (store *FDBRecordStore) GetIndexState(indexName string) IndexState

GetIndexState returns the state of the given index. Returns READABLE if no explicit state is stored (matching Java's default behavior). Goroutine-safe via stateMu (read lock).

func (*FDBRecordStore) GetIndexesToBuildSince

func (store *FDBRecordStore) GetIndexesToBuildSince(version int) []*Index

GetIndexesToBuildSince returns all indexes that need to be built because they were added or modified since the given metadata version. Matches Java's FDBRecordStore.getIndexesToBuildSince(version).

func (*FDBRecordStore) GetKeySizeLimit

func (store *FDBRecordStore) GetKeySizeLimit() int

GetKeySizeLimit returns the maximum key size in bytes for index entries. Matches Java's FDBRecordStore.getKeySizeLimit().

func (*FDBRecordStore) GetMetaData

func (store *FDBRecordStore) GetMetaData() *RecordMetaData

GetMetaData returns the metadata for this store. Matches Java's FDBRecordStore.getRecordMetaData().

func (*FDBRecordStore) GetMetaDataVersion

func (store *FDBRecordStore) GetMetaDataVersion() int32

GetMetaDataVersion returns the metadata version stored in the header. Goroutine-safe via stateMu (read lock).

func (*FDBRecordStore) GetRangeSplitPoints

func (store *FDBRecordStore) GetRangeSplitPoints(chunkSize int64) ([]fdb.Key, error)

GetRangeSplitPoints returns split points that divide the store's records subspace into approximately equal-sized chunks of the given byte size. Useful for parallelizing scans across multiple transactions. Uses FDB's native GetRangeSplitPoints which is efficient for large datasets.

func (*FDBRecordStore) GetReadableIndexes

func (store *FDBRecordStore) GetReadableIndexes() []*Index

GetReadableIndexes returns all indexes that are in READABLE or READABLE_UNIQUE_PENDING state. Matches Java's FDBRecordStoreBase.getReadableIndexes().

func (*FDBRecordStore) GetReadableUniversalIndexes

func (store *FDBRecordStore) GetReadableUniversalIndexes() []*Index

GetReadableUniversalIndexes returns universal indexes in READABLE state. Matches Java's FDBRecordStore.getReadableUniversalIndexes().

func (*FDBRecordStore) GetRecordCount

func (store *FDBRecordStore) GetRecordCount() (int64, error)

GetRecordCount returns the total number of records in the store.

This is Java's getSnapshotRecordCount() (FDBRecordStore.java:2283, called with EmptyKeyExpression.EMPTY / Key.Evaluated.EMPTY), and it is a TOTAL under every record-count key: a GROUPED count key is rolled up by summing every group, not read out of the ungrouped slot that no grouped store ever writes to.

func (*FDBRecordStore) GetRecordMetaData

func (store *FDBRecordStore) GetRecordMetaData() *RecordMetaData

GetRecordMetaData returns the metadata associated with this store. Matches Java's FDBRecordStore.getRecordMetaData().

func (*FDBRecordStore) GetRecordStoreState

func (store *FDBRecordStore) GetRecordStoreState() *RecordStoreState

GetRecordStoreState returns the current state of the store (header + index states). Goroutine-safe via stateMu (read lock). Matches Java's FDBRecordStore.getRecordStoreState().

func (*FDBRecordStore) GetSnapshotRecordCount

func (store *FDBRecordStore) GetSnapshotRecordCount(countKey tuple.Tuple) (int64, error)

GetSnapshotRecordCount returns the count of records matching the given evaluated value of the store's record-count key. Uses snapshot (non-conflicting) reads.

For ungrouped counting (EmptyKeyExpression), pass an empty tuple. For per-type counting, pass a tuple with the record type key.

Port of Java's getSnapshotRecordCount(key, value, filter) (FDBRecordStore.java:2283-2323). The maintained counters answer when they can (see snapshotRecordCountFromCountKey); otherwise Java does NOT fail — it falls through to evaluateAggregateFunction(emptyList(), count(key), allOf(value), SNAPSHOT, filter) (FDBRecordStore.java:2320-2322), so a store with no record-count key at all, or whose counters are WRITE_ONLY/DISABLED, is still counted from a universal COUNT index. The empty record-type list is what restricts that to universal indexes (IndexFunctionHelper.java:180-181).

Returns 0 if no count exists (null in FDB means 0, matching Java).

func (*FDBRecordStore) GetSnapshotRecordCountForRecordType

func (store *FDBRecordStore) GetSnapshotRecordCountForRecordType(recordTypeName string) (int64, error)

GetSnapshotRecordCountForRecordType returns the count of records for a specific record type. Requires that the metadata uses RecordTypeKeyExpression as the count key. Matches Java's getSnapshotRecordCountForRecordType().

func (*FDBRecordStore) GetStoreHeader

func (store *FDBRecordStore) GetStoreHeader() *gen.DataStoreInfo

GetStoreHeader returns a copy of the current store header proto. Goroutine-safe via stateMu (read lock). Matches Java's FDBRecordStore.getRecordStoreState().getStoreHeader().

func (*FDBRecordStore) GetSubspace

func (store *FDBRecordStore) GetSubspace() subspace.Subspace

GetSubspace returns the FDB subspace for this store. Matches Java's FDBRecordStore.getSubspace().

func (*FDBRecordStore) GetUserVersion

func (store *FDBRecordStore) GetUserVersion() int32

GetUserVersion returns the user-defined store version. Goroutine-safe via stateMu (read lock). Matches Java's FDBRecordStore.getUserVersion().

func (*FDBRecordStore) GetValueSizeLimit

func (store *FDBRecordStore) GetValueSizeLimit() int

GetValueSizeLimit returns the maximum value size in bytes for index entries. Matches Java's FDBRecordStore.getValueSizeLimit().

func (*FDBRecordStore) GetWriteOnlyIndexes

func (store *FDBRecordStore) GetWriteOnlyIndexes() []*Index

GetWriteOnlyIndexes returns all indexes that are in WRITE_ONLY state. These are indexes currently being built by an OnlineIndexer. Matches Java's FDBRecordStoreBase.getWriteOnlyIndexes() (derived from getAllIndexStates).

func (*FDBRecordStore) IndexSecondarySubspace

func (store *FDBRecordStore) IndexSecondarySubspace(index *Index) subspace.Subspace

IndexSecondarySubspace returns the secondary subspace for a specific index. Matches Java's FDBRecordStore.indexSecondarySubspace(index).

func (*FDBRecordStore) IndexStateSubspace

func (store *FDBRecordStore) IndexStateSubspace() subspace.Subspace

IndexStateSubspace returns the subspace where index states are stored. Matches Java's FDBRecordStore.indexStateSubspace().

func (*FDBRecordStore) IndexSubspace

func (store *FDBRecordStore) IndexSubspace(index *Index) subspace.Subspace

IndexSubspace returns the subspace for a specific index's entries. Matches Java's FDBRecordStore.indexSubspace(index).

func (*FDBRecordStore) InsertRecord

func (store *FDBRecordStore) InsertRecord(record proto.Message) (*FDBStoredRecord[proto.Message], error)

InsertRecord saves a record and throws an error if it already exists. This is equivalent to SaveRecordWithOptions(record, RecordExistenceCheckErrorIfExists).

Java equivalent: FDBRecordStore.insertRecordAsync(Message rec) Java location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java:629

Returns:

  • *FDBStoredRecord: The saved record with metadata
  • error: RecordAlreadyExistsError if a record with the same primary key already exists

func (*FDBRecordStore) IsCacheable

func (store *FDBRecordStore) IsCacheable() bool

IsCacheable returns whether the store state is marked as cacheable in the header. Goroutine-safe via stateMu (read lock). Matches Java's FDBRecordStore.getRecordStoreState().getStoreHeader().getCacheable().

func (*FDBRecordStore) IsIndexDisabled

func (store *FDBRecordStore) IsIndexDisabled(indexName string) bool

IsIndexDisabled returns true if the index is in DISABLED state.

func (*FDBRecordStore) IsIndexReadable

func (store *FDBRecordStore) IsIndexReadable(indexName string) bool

IsIndexReadable returns true if the index is in READABLE state.

func (*FDBRecordStore) IsIndexReadableUniquePending

func (store *FDBRecordStore) IsIndexReadableUniquePending(indexName string) bool

IsIndexReadableUniquePending returns true if the index is in READABLE_UNIQUE_PENDING state. This state means the unique index is fully indexed but may have duplicate entries. Matches Java's FDBRecordStoreBase.isIndexReadableUniquePending().

func (*FDBRecordStore) IsIndexScannable

func (store *FDBRecordStore) IsIndexScannable(indexName string) bool

IsIndexScannable returns true if the index can be scanned (READABLE or READABLE_UNIQUE_PENDING).

func (*FDBRecordStore) IsIndexWriteOnly

func (store *FDBRecordStore) IsIndexWriteOnly(indexName string) bool

IsIndexWriteOnly returns true if the index is in WRITE_ONLY state.

func (*FDBRecordStore) IsStateCacheable

func (store *FDBRecordStore) IsStateCacheable() bool

IsStateCacheable returns whether this store's state is currently cacheable. Goroutine-safe via stateMu (read lock).

func (*FDBRecordStore) IsVersionChanged

func (store *FDBRecordStore) IsVersionChanged() bool

IsVersionChanged returns true if the metadata version changed during the most recent Open/CreateOrOpen (i.e., checkPossiblyRebuild detected that the stored version < current metadata version). Matches Java's FDBRecordStore.isVersionChanged().

func (*FDBRecordStore) LoadBuildProgress

func (store *FDBRecordStore) LoadBuildProgress(index *Index) (int64, error)

LoadBuildProgress reads the records-scanned counter for an index build. Returns 0 if no progress has been tracked. Matches Java's IndexBuildState.loadRecordsScannedAsync().

func (*FDBRecordStore) LoadIndexingTypeStamp

func (store *FDBRecordStore) LoadIndexingTypeStamp(index *Index) (*gen.IndexBuildIndexingStamp, error)

LoadIndexingTypeStamp loads the indexing method stamp for an index. Returns nil if no stamp exists. Matches Java's FDBRecordStore.loadIndexingTypeStampAsync().

func (*FDBRecordStore) LoadRecord

func (store *FDBRecordStore) LoadRecord(primaryKey tuple.Tuple) (*FDBStoredRecord[proto.Message], error)

LoadRecord loads a record by its primary key. Handles both unsplit (suffix 0) and split (suffixes 1, 2, ...) records via SplitHelper, matching Java's FDBRecordStore.loadRecordAsync().

func (*FDBRecordStore) LoadRecordVersion

func (store *FDBRecordStore) LoadRecordVersion(primaryKey tuple.Tuple, snapshot bool) (*FDBRecordVersion, error)

LoadRecordVersion loads the version associated with a record. Returns nil if no version is stored or versioning is not enabled. Matches Java's FDBRecordStore.loadRecordVersionAsync().

func (*FDBRecordStore) MarkIndexDisabled

func (store *FDBRecordStore) MarkIndexDisabled(indexName string) (bool, error)

MarkIndexDisabled transitions an index to DISABLED state and clears all index data. Returns true if the state was changed. Matches Java's FDBRecordStore.markIndexDisabled().

func (*FDBRecordStore) MarkIndexReadable

func (store *FDBRecordStore) MarkIndexReadable(indexName string) (bool, error)

MarkIndexReadable transitions an index to READABLE state. Returns true if the state was changed, false if already READABLE. Returns an error if the index is not fully built or if a unique index has violations. Matches Java's FDBRecordStore.markIndexReadable(index, allowUniquePending=false).

func (*FDBRecordStore) MarkIndexReadableOrUniquePending

func (store *FDBRecordStore) MarkIndexReadableOrUniquePending(indexName string) (bool, error)

MarkIndexReadableOrUniquePending transitions a unique index to READABLE if it has no uniqueness violations, or to READABLE_UNIQUE_PENDING if violations exist. For non-unique indexes, always transitions to READABLE. Returns true if the state was changed. Returns an error if the index is not fully built. Matches Java's FDBRecordStore.markIndexReadableOrUniquePending().

func (*FDBRecordStore) MarkIndexWriteOnly

func (store *FDBRecordStore) MarkIndexWriteOnly(indexName string) (bool, error)

MarkIndexWriteOnly transitions an index to WRITE_ONLY state. Returns true if the state was changed. Matches Java's FDBRecordStore.markIndexWriteOnly().

func (*FDBRecordStore) OverrideLockSaveRecord

func (store *FDBRecordStore) OverrideLockSaveRecord(
	record proto.Message,
	existenceCheck RecordExistenceCheck,
) (*FDBStoredRecord[proto.Message], error)

OverrideLockSaveRecord saves a record even when the store is locked for record updates (FORBID_RECORD_UPDATE). This is used by the OnlineIndexer to write index maintenance records while the store is locked. Goroutine-safe: uses parameter-based override instead of mutable field. Matches Java's FDBRecordStore.overrideLockSaveRecordAsync().

func (*FDBRecordStore) RebuildAllIndexes

func (store *FDBRecordStore) RebuildAllIndexes() error

RebuildAllIndexes rebuilds all indexes that are not in READABLE state. Matches Java's FDBRecordStore.rebuildAllIndexes().

func (*FDBRecordStore) RebuildIndex

func (store *FDBRecordStore) RebuildIndex(index *Index) error

RebuildIndex rebuilds an index within the current transaction. Clears existing index data, scans all records, and re-indexes them. Upon completion, the index is marked READABLE.

Because this runs in a single transaction, it is limited by FDB's 5-second time limit and 10MB transaction size. For large stores, use OnlineIndexer.BuildIndex() which splits work across transactions.

Matches Java's FDBRecordStore.rebuildIndex() which delegates to IndexingBase.rebuildIndexAsync() for the in-transaction path.

func (*FDBRecordStore) RecordExists

func (store *FDBRecordStore) RecordExists(primaryKey tuple.Tuple, isolationLevel IsolationLevel) (bool, error)

RecordExists checks if a record exists with the given primary key. Handles both split and unsplit records via SplitHelper.

Java equivalent: FDBRecordStore.recordExistsAsync(Tuple primaryKey, IsolationLevel isolationLevel)

func (*FDBRecordStore) RecordsSubspace

func (store *FDBRecordStore) RecordsSubspace() subspace.Subspace

RecordsSubspace returns the subspace where records are stored. Matches Java's FDBRecordStore.recordsSubspace().

func (*FDBRecordStore) ReleaseReadLock

func (store *FDBRecordStore) ReleaseReadLock(key string)

func (*FDBRecordStore) ReleaseWriteLock

func (store *FDBRecordStore) ReleaseWriteLock(key string)

func (*FDBRecordStore) ReloadRecordStoreState

func (store *FDBRecordStore) ReloadRecordStoreState() error

ReloadRecordStoreState forces a reload of the store state from FDB. Useful when another transaction may have changed the state. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.loadRecordStoreStateAsync() force reload path.

func (*FDBRecordStore) ResolveUniquenessViolation

func (store *FDBRecordStore) ResolveUniquenessViolation(index *Index, indexKey tuple.Tuple, primaryKey tuple.Tuple) error

ResolveUniquenessViolation removes a single uniqueness violation entry. Call this after manually resolving the conflict (e.g., deleting the duplicate record). Matches Java's StandardIndexMaintainer.resolveUniquenessViolation().

func (*FDBRecordStore) ResolveUniquenessViolationByDeletion

func (store *FDBRecordStore) ResolveUniquenessViolationByDeletion(
	index *Index,
	valueKey tuple.Tuple,
	remainPrimaryKey tuple.Tuple,
) error

ResolveUniquenessViolationByDeletion resolves uniqueness violations for a specific index value key by deleting all records that violate uniqueness, except for the record with remainPrimaryKey (if non-nil). If remainPrimaryKey is nil, all violating records are deleted. Matches Java's FDBRecordStore.resolveUniquenessViolation(index, valueKey, remainPrimaryKey).

func (*FDBRecordStore) SaveIndexingTypeStamp

func (store *FDBRecordStore) SaveIndexingTypeStamp(index *Index, stamp *gen.IndexBuildIndexingStamp) error

SaveIndexingTypeStamp persists the indexing method stamp for an index. Matches Java's FDBRecordStore.saveIndexingTypeStamp().

func (*FDBRecordStore) SaveRecord

func (store *FDBRecordStore) SaveRecord(record proto.Message) (*FDBStoredRecord[proto.Message], error)

SaveRecord saves a protobuf record to the store. Equivalent to SaveRecordWithOptions(record, RecordExistenceCheckNone). Java equivalent: FDBRecordStore.saveRecord(Message rec)

func (*FDBRecordStore) SaveRecordBatch

func (store *FDBRecordStore) SaveRecordBatch(
	records []proto.Message,
) ([]*FDBStoredRecord[proto.Message], error)

SaveRecordBatch saves multiple records with pipelined existence checks.

Instead of N sequential blocking FDB reads (one per record in SaveRecord), this method issues all existence-check Gets up front as futures, then collects them in one batch. This turns N round trips into ~1 round trip for the existence checks, significantly improving throughput for batch inserts.

Semantically equivalent to calling SaveRecord N times. All records are saved in the current transaction.

func (*FDBRecordStore) SaveRecordWithOptions

func (store *FDBRecordStore) SaveRecordWithOptions(
	record proto.Message,
	existenceCheck RecordExistenceCheck,
) (*FDBStoredRecord[proto.Message], error)

SaveRecordWithOptions saves a protobuf record with existence checking. This is the full-featured save method that supports all RecordExistenceCheck modes.

Java equivalent: FDBRecordStore.saveRecordAsync(Message rec, RecordExistenceCheck existenceCheck, FDBRecordVersion version, VersionstampSaveBehavior behavior) Java location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java:496

Parameters:

  • record: The protobuf message to save
  • existenceCheck: Validation to perform (NONE, ERROR_IF_EXISTS, etc.)

Returns:

  • *FDBStoredRecord: The saved record with metadata
  • error: RecordAlreadyExistsError, RecordDoesNotExistError, or RecordTypeChangedError based on existenceCheck

Note: Version and versionstamp support will be added in Phase 2

func (*FDBRecordStore) ScanIndex

func (store *FDBRecordStore) ScanIndex(
	index *Index,
	scanRange TupleRange,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*IndexEntry]

ScanIndex scans a secondary index and returns a cursor over IndexEntry results. Returns an error cursor if the index is not in a scannable state (DISABLED or WRITE_ONLY). Dispatches to the appropriate maintainer's Scan() method (VALUE vs COUNT). Matches Java's FDBRecordStore.scanIndex().

func (*FDBRecordStore) ScanIndexByType

func (store *FDBRecordStore) ScanIndexByType(
	index *Index,
	scanType IndexScanType,
	scanRange TupleRange,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*IndexEntry]

ScanIndexByType scans a secondary index with an explicit scan type. For BY_VALUE, delegates to the maintainer's Scan. For BY_RANK, converts rank range to score range and scans the B-tree. Matches Java's FDBRecordStore.scanIndex(index, scanType, range, ...).

func (*FDBRecordStore) ScanIndexRecords

func (store *FDBRecordStore) ScanIndexRecords(
	indexName string,
	scanRange TupleRange,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*FDBIndexedRecord]

ScanIndexRecords scans a secondary index and fetches the actual records. For each index entry, it loads the record by primary key. An orphan index entry (pointing to a record that no longer exists) raises a RecordCoreStorageError — this mirrors Java's scanIndexRecords, whose default IndexOrphanBehavior is ERROR (not SKIP), so store corruption surfaces loudly instead of returning fewer records. Matches Java's FDBRecordStore.scanIndexRecords().

func (*FDBRecordStore) ScanRecordKeys

func (store *FDBRecordStore) ScanRecordKeys(
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[tuple.Tuple]

ScanRecordKeys scans only the primary keys of records without deserializing record data. This is significantly faster than ScanRecords when you only need the keys (avoids protobuf deserialization overhead). Split records (multiple KV entries per PK) are automatically deduplicated. Matches Java's FDBRecordStore.scanRecordKeys().

func (*FDBRecordStore) ScanRecords

func (store *FDBRecordStore) ScanRecords(continuation []byte, scanProperties ScanProperties) RecordCursor[*FDBStoredRecord[proto.Message]]

ScanRecords scans all records in the store. For forward scans, continuation sets the low endpoint (start after last returned key). For reverse scans, continuation sets the high endpoint (end before last returned key). Matches Java's KeyValueCursorBase behavior.

func (*FDBRecordStore) ScanRecordsByType

func (store *FDBRecordStore) ScanRecordsByType(recordTypeName string, continuation []byte, scanProperties ScanProperties) RecordCursor[*FDBStoredRecord[proto.Message]]

ScanRecordsByType scans records filtered to a specific record type. When the primary key uses RecordTypeKey() as its first component (common pattern), this does a prefix scan on just that type's range — O(records of this type) instead of O(all records). Falls back to full scan + filter if RecordTypeKey is not used. Matches Java's RecordQuery with RecordTypeFilter.

func (*FDBRecordStore) ScanRecordsInRange

func (store *FDBRecordStore) ScanRecordsInRange(
	low, high tuple.Tuple,
	lowEndpoint, highEndpoint EndpointType,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*FDBStoredRecord[proto.Message]]

ScanRecordsInRange scans records in a key range.

This is the ONE place EventScanRecords is recorded, because it is the one method every record scan funnels through — ScanRecords, ScanRecordsByType and the query executor's range scans all end here. Java makes the same choice for the same reason: its single recording site is the innermost private scanTypedRecords (FDBRecordStore.java:1441), never the outer convenience wrappers, so no entry point can scan records without the event and none can record it twice. Instrumenting the outer wrapper instead left the executor's own scans — which enter here directly — entirely uncounted.

func (*FDBRecordStore) ScanTimeWindowLeaderboard

func (store *FDBRecordStore) ScanTimeWindowLeaderboard(
	index *Index,
	scanType IndexScanType,
	leaderboardType int,
	leaderboardTimestamp int64,
	scanRange TupleRange,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*IndexEntry]

ScanTimeWindowLeaderboard scans a TIME_WINDOW_LEADERBOARD index within a specific time window. The scanType determines how the range is interpreted:

  • BY_TIME_WINDOW / BY_VALUE: scanRange contains score bounds
  • BY_RANK: scanRange contains rank bounds (converted to score bounds via RankedSet)

Matches Java's FDBRecordStore.scanIndex() with TimeWindowScanRange.

func (*FDBRecordStore) ScanUniquenessViolations

func (store *FDBRecordStore) ScanUniquenessViolations(index *Index) ([]UniquenessViolation, error)

ScanUniquenessViolations returns all uniqueness violations recorded for the given index. Violations are stored in the IndexUniquenessViolationsKey (7) subspace. Matches Java's StandardIndexMaintainer.scanUniquenessViolations().

func (*FDBRecordStore) ScanUniquenessViolationsForValue

func (store *FDBRecordStore) ScanUniquenessViolationsForValue(
	index *Index,
	valueKey tuple.Tuple,
) ([]UniquenessViolation, error)

ScanUniquenessViolationsForValue returns uniqueness violations for a specific index value key. This filters violations to only those matching the given value key. Matches Java's StandardIndexMaintainer.scanUniquenessViolations(index, valueKey).

func (*FDBRecordStore) ScanVectorIndex

func (store *FDBRecordStore) ScanVectorIndex(
	index *Index,
	queryVector []float64,
	k int,
	efSearch int,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*IndexEntry]

ScanVectorIndex scans a VECTOR index with BY_DISTANCE semantics, returning results as a cursor. This is the cursor-based API matching Java's VectorIndexMaintainer.scan(VectorIndexScanBounds, ...).

Each result is an IndexEntry with Key = primaryKey and Value = tuple{distance}. Results are sorted by distance (closest first).

For prefix-partitioned indexes, use ScanVectorIndexWithPrefix instead.

func (*FDBRecordStore) ScanVectorIndexWithPrefix

func (store *FDBRecordStore) ScanVectorIndexWithPrefix(
	index *Index,
	prefix tuple.Tuple,
	queryVector []float64,
	k int,
	efSearch int,
	continuation []byte,
	scanProperties ScanProperties,
) RecordCursor[*IndexEntry]

ScanVectorIndexWithPrefix scans a VECTOR index with BY_DISTANCE semantics, scoped to a specific prefix partition. Pass nil prefix for non-grouped indexes.

Each result is an IndexEntry with Key = primaryKey and Value = tuple{distance}. Results are sorted by distance (closest first).

func (*FDBRecordStore) SearchVectorIndex

func (store *FDBRecordStore) SearchVectorIndex(
	index *Index,
	queryVector []float64,
	k int,
	efSearch int,
) ([]VectorSearchResult, error)

SearchVectorIndex performs a k-nearest-neighbor search on a VECTOR index. Matches Java's VectorIndexMaintainer scan with VectorIndexScanBounds.

For prefix-partitioned indexes, use SearchVectorIndexWithPrefix instead.

func (*FDBRecordStore) SearchVectorIndexRecords

func (store *FDBRecordStore) SearchVectorIndexRecords(
	ctx context.Context,
	index *Index,
	queryVector []float64,
	k int,
	efSearch int,
) ([]*FDBIndexedRecord, error)

SearchVectorIndexRecords performs a kNN search and fetches the corresponding records.

For prefix-partitioned indexes, use SearchVectorIndexRecordsWithPrefix instead.

func (*FDBRecordStore) SearchVectorIndexRecordsWithPrefix

func (store *FDBRecordStore) SearchVectorIndexRecordsWithPrefix(
	ctx context.Context,
	index *Index,
	prefix tuple.Tuple,
	queryVector []float64,
	k int,
	efSearch int,
) ([]*FDBIndexedRecord, error)

SearchVectorIndexRecordsWithPrefix performs a kNN search scoped to a prefix partition and fetches the corresponding records.

func (*FDBRecordStore) SearchVectorIndexWithPrefix

func (store *FDBRecordStore) SearchVectorIndexWithPrefix(
	index *Index,
	prefix tuple.Tuple,
	queryVector []float64,
	k int,
	efSearch int,
) ([]VectorSearchResult, error)

SearchVectorIndexWithPrefix performs a k-nearest-neighbor search on a VECTOR index, scoped to a specific prefix partition. Pass nil prefix for non-grouped indexes.

func (*FDBRecordStore) SetFormatVersion

func (store *FDBRecordStore) SetFormatVersion(version int32) error

SetFormatVersion updates the store's format version in the header. This is used during store migration to enable new features. Matches Java's FDBRecordStore.setFormatVersion().

func (*FDBRecordStore) SetHeaderUserField

func (store *FDBRecordStore) SetHeaderUserField(key string, value []byte) error

SetHeaderUserField sets a user-defined field in the store header. The value is persisted immediately. Keep values small since the entire header is loaded on every store open. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.setHeaderUserField().

func (*FDBRecordStore) SetStateCacheability

func (store *FDBRecordStore) SetStateCacheability(cacheable bool) (bool, error)

SetStateCacheability sets whether this store's state can be cached across transactions. When enabled, the metadata version stamp is initialized so cache invalidation can work. Requires FormatVersion >= CACHEABLE_STATE (7). Returns true if the cacheability was changed, false if already at the desired state. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.setStateCacheabilityAsync().

func (*FDBRecordStore) SetStoreLockState

func (store *FDBRecordStore) SetStoreLockState(state gen.DataStoreInfo_StoreLockState_State, reason string) error

SetStoreLockState sets the store lock state in the header and persists it. Use FORBID_RECORD_UPDATE to prevent record mutations, or FULL_STORE to prevent the store from being opened entirely. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.setStoreLockStateAsync().

func (*FDBRecordStore) SetUserVersion

func (store *FDBRecordStore) SetUserVersion(version int32) error

SetUserVersion updates the user-defined store version and writes it to FDB. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.setUserVersion().

func (*FDBRecordStore) Subspace

func (store *FDBRecordStore) Subspace() subspace.Subspace

Subspace returns the subspace this store is using

func (*FDBRecordStore) UpdateIncarnation

func (store *FDBRecordStore) UpdateIncarnation(updater func(current int32) int32) error

UpdateIncarnation atomically updates the incarnation counter using the provided function. The new value must be strictly greater than the current value. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.updateIncarnation().

func (*FDBRecordStore) UpdateRecord

func (store *FDBRecordStore) UpdateRecord(record proto.Message) (*FDBStoredRecord[proto.Message], error)

UpdateRecord saves a record and throws an error if it does not already exist or if its type has changed. This is equivalent to SaveRecordWithOptions(record, RecordExistenceCheckErrorIfNotExistsOrTypeChanged).

Java equivalent: FDBRecordStore.updateRecordAsync(Message rec) Java location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java:649

Returns:

  • *FDBStoredRecord: The saved record with metadata
  • error: RecordDoesNotExistError if no record exists with this primary key
  • error: RecordTypeChangedError if an existing record has a different type

func (*FDBRecordStore) UpdateRecordCountState

func (store *FDBRecordStore) UpdateRecordCountState(newState gen.DataStoreInfo_RecordCountState) error

UpdateRecordCountState transitions the record count state. Valid transitions: READABLE↔WRITE_ONLY, any→DISABLED. DISABLED is terminal. When transitioning to DISABLED, clears all count data. Goroutine-safe via stateMu (write lock). Matches Java's FDBRecordStore.updateRecordCountStateAsync().

func (*FDBRecordStore) VacuumReadableIndexesBuildData

func (store *FDBRecordStore) VacuumReadableIndexesBuildData()

VacuumReadableIndexesBuildData clears build artifacts (range sets, stamps, progress counters) for indexes that are already READABLE. Matches Java's FDBRecordStore.vacuumReadableIndexesBuildData().

func (*FDBRecordStore) ValidateIndex

func (store *FDBRecordStore) ValidateIndex(ctx context.Context, index *Index) (*IndexValidationResult, error)

ValidateIndex checks the consistency of an index against the actual records. Detects missing entries (record exists but index entry missing) and orphaned entries (index entry exists but no corresponding record). Matches Java's StandardIndexMaintainer.validateEntries().

type FDBRecordStoreStateCache

type FDBRecordStoreStateCache interface {
	// Get retrieves store state, using the cache if possible.
	// On cache hit, adds a read conflict on the STORE_INFO key to ensure
	// the transaction fails if concurrent modifications occur.
	// On cache miss, loads fresh state from FDB.
	Get(store *FDBRecordStore, existenceCheck StoreExistenceCheck) (*FDBRecordStoreStateCacheEntry, error)

	// Clear removes all cached entries.
	Clear()
}

FDBRecordStoreStateCache caches store state (header + index states) across transactions to avoid repeated reads of the store info key on every store open. Matches Java's FDBRecordStoreStateCache interface.

func PassThroughStoreStateCache

func PassThroughStoreStateCache() FDBRecordStoreStateCache

PassThroughStoreStateCache returns the singleton pass-through cache.

type FDBRecordStoreStateCacheEntry

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

FDBRecordStoreStateCacheEntry holds a cached snapshot of store state along with the metadata version stamp that was current when the state was loaded. Matches Java's FDBRecordStoreStateCacheEntry.

func (*FDBRecordStoreStateCacheEntry) GetMetaDataVersionStamp

func (e *FDBRecordStoreStateCacheEntry) GetMetaDataVersionStamp() []byte

GetMetaDataVersionStamp returns the metadata version stamp captured at load time.

func (*FDBRecordStoreStateCacheEntry) GetRecordStoreState

func (e *FDBRecordStoreStateCacheEntry) GetRecordStoreState() *RecordStoreState

GetRecordStoreState returns the cached store state.

type FDBRecordVersion

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

FDBRecordVersion represents a version associated with a record in the store. Matches Java's FDBRecordVersion exactly: - 12 bytes total: 10-byte global version (FDB versionstamp) + 2-byte local version - Global version: 8-byte DB commit version (big-endian) + 2-byte batch ordering - Local version: per-transaction counter (big-endian uint16)

Versions can be "complete" (global version known) or "incomplete" (pending commit). Incomplete versions have all-0xFF in the global version bytes.

func CompleteVersionFromBytes

func CompleteVersionFromBytes(b []byte) (*FDBRecordVersion, error)

CompleteVersionFromBytes parses a complete 12-byte version. Rejects versions with all-0xFF global bytes (incomplete marker). Matches Java's FDBRecordVersion.complete(byte[], boolean) constructor validation.

func FirstInDBVersion

func FirstInDBVersion(dbVersion int64) *FDBRecordVersion

FirstInDBVersion returns the first version with the given DB commit version. The batch and local portions are set to 0. Matches Java's FDBRecordVersion.firstInDBVersion().

func FirstInGlobalVersion

func FirstInGlobalVersion(globalVersion []byte) (*FDBRecordVersion, error)

FirstInGlobalVersion returns the first version with the given 10-byte global version. The local version is set to 0. Matches Java's FDBRecordVersion.firstInGlobalVersion().

func FromVersionstamp

func FromVersionstamp(vs tuple.Versionstamp) *FDBRecordVersion

FromVersionstamp creates a complete FDBRecordVersion from an FDB tuple.Versionstamp. Matches Java's FDBRecordVersion.fromVersionstamp(Versionstamp).

func IncompleteVersion

func IncompleteVersion(localVersion int) (*FDBRecordVersion, error)

IncompleteVersion creates an incomplete version with the given local version. The global version is set to all-0xFF (will be filled in at commit time).

func LastInDBVersion

func LastInDBVersion(dbVersion int64) *FDBRecordVersion

LastInDBVersion returns the last version with the given DB commit version. The batch portion is set to 0xFFFF and local to 0xFFFF. Matches Java's FDBRecordVersion.lastInDBVersion().

func LastInGlobalVersion

func LastInGlobalVersion(globalVersion []byte) (*FDBRecordVersion, error)

LastInGlobalVersion returns the last version with the given 10-byte global version. The local version is set to 0xFFFF. Matches Java's FDBRecordVersion.lastInGlobalVersion().

func MaxVersion

func MaxVersion() *FDBRecordVersion

MaxVersion returns the maximum possible complete version. Matches Java's FDBRecordVersion.MAX_VERSION:

global bytes 0-8: 0xFF, byte 9: 0xFE (0xFF...FF would mean incomplete)
local bytes 10-11: 0xFF

func MinVersion

func MinVersion() *FDBRecordVersion

MinVersion returns the minimum possible complete version (all zeros). Matches Java's FDBRecordVersion.MIN_VERSION.

func NewCompleteVersion

func NewCompleteVersion(globalVersion []byte, localVersion int) (*FDBRecordVersion, error)

NewCompleteVersion creates a complete version from 10-byte global version and local version.

func (*FDBRecordVersion) Equal

func (v *FDBRecordVersion) Equal(other *FDBRecordVersion) bool

Equal returns true if two versions are equal. Complete versions compare all 12 bytes; incomplete versions compare local version only. Matches Java's FDBRecordVersion.equals().

func (*FDBRecordVersion) GetDBVersion

func (v *FDBRecordVersion) GetDBVersion() (int64, error)

GetDBVersion returns the 8-byte FDB commit version (big-endian). Returns an error if the version is incomplete.

func (*FDBRecordVersion) GetGlobalVersion

func (v *FDBRecordVersion) GetGlobalVersion() ([]byte, error)

GetGlobalVersion returns the 10-byte global versionstamp. Returns an error if the version is incomplete.

func (*FDBRecordVersion) GetLocalVersion

func (v *FDBRecordVersion) GetLocalVersion() int

GetLocalVersion returns the 2-byte local version counter.

func (*FDBRecordVersion) IsComplete

func (v *FDBRecordVersion) IsComplete() bool

IsComplete returns true if the global version is known.

func (*FDBRecordVersion) Less

func (v *FDBRecordVersion) Less(other *FDBRecordVersion) bool

Less returns true if v sorts before other. Complete versions sort before incomplete. Among versions of the same completeness, lexicographic byte comparison of the full 12-byte raw representation. Matches Java's FDBRecordVersion.compareTo().

func (*FDBRecordVersion) Next

func (v *FDBRecordVersion) Next() (*FDBRecordVersion, error)

Next returns the version immediately after this one. For complete versions: treats all 12 bytes as big-endian unsigned integer, +1 with carry. For incomplete versions: only increments local version (last 2 bytes). Matches Java's FDBRecordVersion.next().

func (*FDBRecordVersion) Prev

func (v *FDBRecordVersion) Prev() (*FDBRecordVersion, error)

Prev returns the version immediately before this one. For complete versions: treats all 12 bytes as big-endian unsigned integer, -1 with borrow. For incomplete versions: only decrements local version (last 2 bytes). Matches Java's FDBRecordVersion.prev().

func (*FDBRecordVersion) String

func (v *FDBRecordVersion) String() string

String returns a human-readable representation matching Java's toString(). Format: "FDBRecordVersion(complete=<bool>, raw=<hex>)"

func (*FDBRecordVersion) ToBytes

func (v *FDBRecordVersion) ToBytes() []byte

ToBytes serializes the version to 12 bytes.

func (*FDBRecordVersion) ToVersionstamp

func (v *FDBRecordVersion) ToVersionstamp() (tuple.Versionstamp, error)

ToVersionstamp converts this complete version to an FDB tuple.Versionstamp. Returns an error if the version is incomplete. Matches Java's FDBRecordVersion.toVersionstamp().

func (*FDBRecordVersion) WithCommittedVersion

func (v *FDBRecordVersion) WithCommittedVersion(committedVersion []byte) (*FDBRecordVersion, error)

WithCommittedVersion completes an incomplete version using the committed versionstamp. This is called after transaction commit when the real versionstamp is known. Returns an error if the version is already complete. Matches Java's FDBRecordVersion.withCommittedVersion().

type FDBStoredRecord

type FDBStoredRecord[M proto.Message] struct {
	// PrimaryKey is the record's primary key
	PrimaryKey tuple.Tuple

	// RecordType is the type of this record
	RecordType *RecordType

	// Record is the actual record data
	Record M

	// Version is the record's version, if loaded.
	// Matches Java's FDBStoredRecord.getVersion().
	Version *FDBRecordVersion

	// Store is the record store this record belongs to.
	// Used by FunctionKeyExpression (e.g. get_versionstamp_incarnation) to access store state.
	// Matches Java's FDBRecord.getStore().
	Store *FDBRecordStore

	// Storage size information
	KeyCount  int
	KeySize   int
	ValueSize int

	// Whether the record is split across multiple keys
	Split bool
}

FDBStoredRecord represents a record that has been stored in or loaded from FDB This is generic to match Java's FDBStoredRecord<M extends Message>

func (*FDBStoredRecord[M]) HasVersion

func (r *FDBStoredRecord[M]) HasVersion() bool

HasVersion returns whether this stored record has a version. Matches Java's FDBStoredRecord.hasVersion().

type FanType

type FanType int

FanType controls how repeated (list) proto fields are handled in key expressions. Matches Java's com.apple.foundationdb.record.metadata.expressions.KeyExpression.FanType.

const (
	// FanTypeNone means the field must not be repeated. This is the default.
	FanTypeNone FanType = iota
	// FanTypeFanOut produces one tuple per repeated value — used for multi-entry indexes.
	FanTypeFanOut
	// FanTypeConcatenate puts all repeated values into a single tuple element.
	FanTypeConcatenate
)

type FieldKeyExpression

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

FieldKeyExpression extracts a single field value from a record for use as a key component. For repeated fields, use FanOut() or Concatenate() to control multi-value handling.

func (*FieldKeyExpression) ColumnSize

func (f *FieldKeyExpression) ColumnSize() int

ColumnSize returns 1 — a field expression produces a single tuple element.

func (*FieldKeyExpression) Evaluate

func (f *FieldKeyExpression) Evaluate(_ *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate extracts the field value(s) from the message. For non-repeated fields, returns one tuple with the single value. For repeated fields with FanOut, returns one tuple per value. For repeated fields with Concatenate, returns one tuple containing a nested tuple of all values.

func (*FieldKeyExpression) EvaluateFlat

func (f *FieldKeyExpression) EvaluateFlat(record *FDBStoredRecord[proto.Message], msg proto.Message) ([]any, error)

EvaluateFlat returns the scalar value directly as a single-element []any.

func (*FieldKeyExpression) EvaluateInt64

func (f *FieldKeyExpression) EvaluateInt64(record *FDBStoredRecord[proto.Message], msg proto.Message) (int64, bool, error)

EvaluateInt64 returns the field value as int64 without boxing. Returns (0, false, nil) for nil/unset fields, (val, true, nil) for integer fields.

func (*FieldKeyExpression) EvaluateScalar

func (f *FieldKeyExpression) EvaluateScalar(record *FDBStoredRecord[proto.Message], msg proto.Message) (any, error)

EvaluateScalar returns the single scalar value directly — zero allocs for the return.

func (*FieldKeyExpression) FieldNames

func (f *FieldKeyExpression) FieldNames() []string

FieldNames returns the field name accessed by this expression

func (*FieldKeyExpression) PackDirect

PackDirect encodes the field value directly into a Packer without boxing into any. Returns false if the field is nil/unset or not a directly-packable type.

func (*FieldKeyExpression) ToKeyExpression

func (f *FieldKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a FieldKeyExpression to proto. Matches Java's FieldKeyExpression.toKeyExpression(): the nullInterpretation field is written EXPLICITLY even at its NOT_UNIQUE default — the stored bytes carry it (byte-golden-pinned).

type FlatEvaluator

type FlatEvaluator interface {
	EvaluateFlat(record *FDBStoredRecord[proto.Message], msg proto.Message) ([]any, error)
}

FlatEvaluator is an optional interface for KeyExpressions that can return a single tuple directly without the [][]any wrapper. Avoids 1 alloc per call.

type FormerIndex

type FormerIndex struct {
	SubspaceKey    any
	AddedVersion   int
	RemovedVersion int
	FormerName     string
}

FormerIndex tracks a deleted index for schema evolution safety. Prevents accidental reuse of an index's subspace key after deletion. Matches Java's com.apple.foundationdb.record.metadata.FormerIndex.

type FoundSplitOutOfOrderError

type FoundSplitOutOfOrderError struct {
	Expected int64       // LogMessageKeys.SPLIT_EXPECTED
	Found    int64       // LogMessageKeys.SPLIT_FOUND
	KeyTuple tuple.Tuple // LogMessageKeys.KEY_TUPLE — the key whose suffix broke the sequence
}

FoundSplitOutOfOrderError is raised when a split record's segments are present but not in sequence — segment N+1 was expected and something else was found.

Ports Java's SplitHelper.FoundSplitOutOfOrderException (SplitHelper.java:1225-1231), which extends RecordCoreStorageException and carries LogMessageKeys.SPLIT_EXPECTED / SPLIT_FOUND. Java additionally attaches KEY_TUPLE and SUBSPACE at the throw site (SplitHelper.java:826-828); KeyTuple carries the first of those, and the subspace is implicit in it here.

It is a distinct type from FoundSplitWithoutStartError on purpose, because the two describe different damage: this one says the record's pieces are all there but mis-sequenced, which points at a writer that interleaved or a range that spans two records. A caller that wants to tell "corrupt but complete" from "truncated" cannot do it from a formatted string.

func (*FoundSplitOutOfOrderError) Error

func (e *FoundSplitOutOfOrderError) Error() string

func (*FoundSplitOutOfOrderError) Unwrap

func (e *FoundSplitOutOfOrderError) Unwrap() error

Unwrap reports this as storage corruption. Java's exception IS a RecordCoreStorageException; Go spells that relationship out so a caller matching the general storage-corruption type keeps working, the way `catch (RecordCoreStorageException)` does.

type FoundSplitWithoutStartError

type FoundSplitWithoutStartError struct {
	NextIndex int64       // LogMessageKeys.SPLIT_NEXT_INDEX — the segment found instead of the start
	Reverse   bool        // LogMessageKeys.SPLIT_REVERSE — direction of the scan that found it
	KeyTuple  tuple.Tuple // LogMessageKeys.KEY_TUPLE
}

FoundSplitWithoutStartError is raised when a split record's continuation segments are found with no start segment — the record is truncated at the front, not merely mis-ordered.

Ports Java's SplitHelper.FoundSplitWithoutStartException (SplitHelper.java:1213-1219), carrying LogMessageKeys.SPLIT_NEXT_INDEX and SPLIT_REVERSE, plus KEY_TUPLE from the throw sites.

Java chooses between this and FoundSplitOutOfOrderException by whether any start-or-later segment has already been seen — `lastIndex >= START_SPLIT_RECORD` picks out-of-order, otherwise without-start (SplitHelper.java:824-835). Go raised one untyped error for both, so a truncated record and a scrambled one were indistinguishable to every caller and to every log.

Reverse matters for reading the report: under a reverse scan the segments arrive highest-first, so "no start yet" is the expected intermediate state rather than evidence of damage, and Java records which direction produced the judgement.

func (*FoundSplitWithoutStartError) Error

type FunctionEvaluator

type FunctionEvaluator func(record *FDBStoredRecord[proto.Message], msg proto.Message, arguments [][]any) ([][]any, error)

FunctionEvaluator evaluates a named function on a record. Arguments are the pre-evaluated argument tuples from the arguments expression.

type FunctionKeyExpression

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

FunctionKeyExpression evaluates a named function on records to produce index key values. The function is resolved from the global registry by name. Matches Java's FunctionKeyExpression abstract class.

func FunctionExpr

func FunctionExpr(name string, arguments KeyExpression) *FunctionKeyExpression

FunctionExpr creates a FunctionKeyExpression with the given name and arguments. The function must be registered in the global registry before evaluation. Matches Java's FunctionKeyExpression.create(name, arguments).

func OrderFuncExpr

func OrderFuncExpr(direction string, arguments KeyExpression) *FunctionKeyExpression

OrderFuncExpr creates a FunctionKeyExpression for ordering with the given direction name and arguments. Convenience wrapper matching Java's OrderFunctionKeyExpression constructor.

func (*FunctionKeyExpression) Arguments

func (f *FunctionKeyExpression) Arguments() KeyExpression

Arguments returns the arguments key expression.

func (*FunctionKeyExpression) ColumnSize

func (f *FunctionKeyExpression) ColumnSize() int

ColumnSize returns 1 — a function expression produces a single tuple element.

func (*FunctionKeyExpression) Evaluate

func (f *FunctionKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate resolves the named function from the registry, evaluates arguments, and applies the function. Matches Java's FunctionKeyExpression.evaluateMessage().

func (*FunctionKeyExpression) FieldNames

func (f *FunctionKeyExpression) FieldNames() []string

FieldNames returns field names from the arguments expression.

func (*FunctionKeyExpression) Name

func (f *FunctionKeyExpression) Name() string

Name returns the function name.

func (*FunctionKeyExpression) ToKeyExpression

func (f *FunctionKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a FunctionKeyExpression to proto. Matches Java's FunctionKeyExpression.toKeyExpression().

type GroupingKeyExpression

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

GroupingKeyExpression wraps a key expression and divides its columns into "grouping" (leading) and "grouped" (trailing) parts. The grouping columns form the GROUP BY key, and the grouped columns are the aggregated values. Matches Java's com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression.

func GroupAll

func GroupAll(expr KeyExpression) *GroupingKeyExpression

GroupAll creates a GroupingKeyExpression where all columns are "grouping" and there are no aggregated columns. Used for COUNT indexes where the entire expression forms the GROUP BY key. Example: GroupAll(Field("price")) → count grouped by price. Matches Java's new GroupingKeyExpression(expr, 0).

func GroupBy

func GroupBy(grouped KeyExpression, groupBy ...KeyExpression) *GroupingKeyExpression

GroupBy creates a GroupingKeyExpression that groups by the given expressions and treats the receiver as the aggregated value. Example: Field("score").GroupBy(Field("game_id")) →

wholeKey = Concat(game_id, score), groupedCount = 1

Matches Java's KeyExpression.groupBy().

func Ungrouped

func Ungrouped(expr KeyExpression) *GroupingKeyExpression

Ungrouped creates a GroupingKeyExpression where all columns are "grouped" (aggregated) and there are no grouping columns. Used for ungrouped counts. Example: Ungrouped(EmptyKey()) → count all records with no grouping. Matches Java's KeyExpression.ungrouped().

func (*GroupingKeyExpression) ColumnSize

func (g *GroupingKeyExpression) ColumnSize() int

ColumnSize delegates to the whole key expression's column size.

func (*GroupingKeyExpression) Evaluate

func (g *GroupingKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate delegates to the whole key expression. The grouping/grouped split is metadata, not evaluation logic.

func (*GroupingKeyExpression) EvaluateFlat

func (g *GroupingKeyExpression) EvaluateFlat(record *FDBStoredRecord[proto.Message], msg proto.Message) ([]any, error)

EvaluateFlat delegates to the whole key's EvaluateFlat if available.

func (*GroupingKeyExpression) FieldNames

func (g *GroupingKeyExpression) FieldNames() []string

FieldNames returns field names from the whole key.

func (*GroupingKeyExpression) GetGroupedCount

func (g *GroupingKeyExpression) GetGroupedCount() int

GetGroupedCount returns the number of trailing "grouped" (aggregated) columns.

func (*GroupingKeyExpression) GetGroupingCount

func (g *GroupingKeyExpression) GetGroupingCount() int

GetGroupingCount returns the number of leading "grouping" (GROUP BY) columns.

func (*GroupingKeyExpression) ToKeyExpression

func (g *GroupingKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes GroupingKeyExpression to proto. Matches Java's GroupingKeyExpression.toKeyExpression().

type HNSWConfig

type HNSWConfig struct {
	NumDimensions         int
	M                     int             // Connectivity factor (default 16)
	MMax                  int             // Max connections for non-zero layers (default 16 — Java DEFAULT_M_MAX, a constant, NOT derived from M; setting M > 16 requires setting MMax too, like Java)
	MMax0                 int             // Max connections for layer 0 (default 32 — Java DEFAULT_M_MAX_0, a constant, NOT derived from M)
	EfConstruction        int             // Insertion search factor (default 200)
	Metric                VectorMetric    // Distance metric
	ExtendCandidates      bool            // Extend candidate set with 2nd-degree neighbors (default false)
	KeepPrunedConnections bool            // Retain pruned candidates to fill up to M (default false)
	EfRepair              int             // Delete-repair beam width; Java requires [m, 400] (default 64; 0 means "use default")
	UseInlining           bool            // Use inlining storage for layers > 0 (default false)
	Quantizer             VectorQuantizer // Optional quantizer (nil = raw float64 storage)

	// RaBitQ centroid-bootstrap stats (Java Config). When RaBitQ is enabled and no centroid
	// is yet established, inserts sample vectors into the SAMPLES subspace, periodically roll
	// them up, and once StatsThreshold vectors have accumulated, establish the rotated centroid.
	SampleVectorStatsProbability float64 // prob of sampling each inserted vector (default 0.5)
	MaintainStatsProbability     float64 // prob of rolling up samples on an insert (default 0.05)
	StatsThreshold               int     // accumulated count needed to establish the centroid (default 1000)

	// Concurrency limits — Java uses these to limit async pipeline parallelism.
	// Go's synchronous FDB model doesn't use these for concurrency control, but
	// we store and round-trip them so Java-written configs are preserved.
	// Matches Java's Config.maxNumConcurrentNodeFetches etc.
	MaxNumConcurrentNodeFetches         int // (0, 64], default 16 — node fetch parallelism in Java
	MaxNumConcurrentNeighborhoodFetches int // (0, 20], default 10 — neighborhood fetch parallelism in Java
	MaxNumConcurrentDeleteFromLayer     int // (0, 10], default 2  — layer deletion parallelism in Java
}

HNSWConfig configures an HNSW graph. Matches Java's com.apple.foundationdb.async.hnsw.Config.

func DefaultHNSWConfig

func DefaultHNSWConfig(numDimensions int) HNSWConfig

DefaultHNSWConfig returns a default HNSW configuration.

type HNSWStats

type HNSWStats struct {
	FDBGets       atomic.Int64 // individual Get() calls (point reads)
	FDBBatchGets  atomic.Int64 // batched Get() calls (pipelined, count as 1 RT)
	FDBRangeReads atomic.Int64 // GetRange() calls
	CacheHits     atomic.Int64 // reads served from in-tx cache
}

HNSWStats tracks I/O counters for a single HNSW operation. Attach to a context via WithHNSWStats and retrieve via GetHNSWStats. Zero cost when not attached — all tracking is behind a nil check.

func GetHNSWStats

func GetHNSWStats(ctx context.Context) *HNSWStats

GetHNSWStats retrieves HNSWStats from a context, or nil if not tracking.

func WithHNSWStats

func WithHNSWStats(ctx context.Context) (context.Context, *HNSWStats)

WithHNSWStats attaches an HNSWStats to the context for I/O tracking.

type IncompleteVersionstampError

type IncompleteVersionstampError struct {
	// Context names the encoder that refused, e.g. the index whose entry was
	// being written.
	Context string
}

IncompleteVersionstampError is returned when a tuple carrying an INCOMPLETE versionstamp reaches an encoder that can only write a complete one.

The Java counterpart is the IllegalArgumentException thrown by TupleOrdering.packNullsLast (TupleOrdering.java:178) and by Tuple.pack, whose message this reproduces verbatim. Java THROWS; the Go port used to reach the same condition through tuple.Pack, which PANICS — a library-boundary crash where Java hands the caller a recoverable failure. Every site that can put a versionstamp in front of a vanilla pack returns this instead.

Reachable from ordinary DDL: an index whose version column lands in the VALUE portion of a KeyWithValue split, or one that wraps the version in an ordering function. Both metadata shapes are what Java's own index generator emits (see IndexTest.java createVersionIndexWithVersionInValue), so the shapes cannot be rejected at DDL without diverging on index metadata — the failure belongs at the write, exactly where Java puts it.

func (*IncompleteVersionstampError) Error

type Index

type Index struct {
	Name           string
	Type           string
	RootExpression KeyExpression

	Options             map[string]string
	AddedVersion        int
	LastModifiedVersion int

	// Predicate filters which records are included in this index.
	// If nil, all records are indexed. If set, only records where
	// Predicate returns true are indexed (sparse/filtered index).
	Predicate IndexPredicate
	// contains filtered or unexported fields
}

func NewBitmapValueIndex

func NewBitmapValueIndex(name string, rootExpression KeyExpression) *Index

NewBitmapValueIndex creates a BITMAP_VALUE index with the given name and root key expression. BITMAP_VALUE indexes store one bit per record in fixed-size bitmaps, using atomic BIT_OR/BIT_AND operations for set/clear. The position field is the last grouped column. Matches Java's new Index(name, rootExpression, IndexTypes.BITMAP_VALUE).

func NewCountIndex

func NewCountIndex(name string, rootExpression KeyExpression) *Index

NewCountIndex creates a COUNT index with the given name and root key expression. COUNT indexes use FDB atomic ADD to maintain counts per grouping key. Matches Java's new Index(name, rootExpression, IndexTypes.COUNT).

func NewCountNotNullIndex

func NewCountNotNullIndex(name string, rootExpression KeyExpression) *Index

NewCountNotNullIndex creates a COUNT_NOT_NULL index with the given name and root key expression. Like COUNT, but skips entries where the key contains a null value (nil element). Matches Java's new Index(name, rootExpression, IndexTypes.COUNT_NOT_NULL).

func NewCountUpdatesIndex

func NewCountUpdatesIndex(name string, rootExpression KeyExpression) *Index

NewCountUpdatesIndex creates a COUNT_UPDATES index with the given name and root key expression. Like COUNT, but deletes are no-ops (count never decrements) and updates always re-count (skipUpdateForUnchangedKeys = false). Tracks total insert+update events. Matches Java's new Index(name, rootExpression, IndexTypes.COUNT_UPDATES).

func NewGroupCountCompanion

func NewGroupCountCompanion(idx *Index) (*Index, bool)

NewGroupCountCompanion builds the companion COUNT(*) index for a grouped SUM or COUNT(col) index: same record type, same grouping key, same predicate, GroupAll (zero grouped columns) so its stored value is the group's row count.

Carrying the owner's predicate is a divergence from the RFC text, which defines the companion by grouping key alone. Without it, a sparse owner's companion counts rows the owner never indexed, and the merge would report groups that hold no qualifying row — an invented group, which is the very defect the companion exists to remove.

Returns false when idx needs no companion or its grouping key cannot be split.

func NewIndex

func NewIndex(name string, rootExpression KeyExpression) *Index

NewIndex creates a VALUE index with the given name and root key expression. Matches Java's new Index(name, rootExpression) which defaults to IndexTypes.VALUE.

func NewMaxEverLongIndex

func NewMaxEverLongIndex(name string, rootExpression KeyExpression) *Index

NewMaxEverLongIndex creates a MAX_EVER_LONG index with the given name and root key expression. MAX_EVER_LONG indexes use FDB atomic MAX to track the maximum value seen per grouping key. Values must be non-negative (unsigned comparison). Deletes are no-ops (_EVER = irreversible). Matches Java's new Index(name, rootExpression, IndexTypes.MAX_EVER_LONG).

func NewMaxEverTupleIndex

func NewMaxEverTupleIndex(name string, rootExpression KeyExpression) *Index

NewMaxEverTupleIndex creates a MAX_EVER_TUPLE index with the given name and root key expression. MAX_EVER_TUPLE indexes use FDB atomic BYTE_MAX to track the maximum tuple-packed value per grouping key. Unlike MAX_EVER_LONG, accepts any tuple-encodable type and compares via byte ordering. Deletes are no-ops (_EVER = irreversible). Idempotent. Matches Java's new Index(name, rootExpression, IndexTypes.MAX_EVER_TUPLE).

func NewMaxEverVersionIndex

func NewMaxEverVersionIndex(name string, rootExpression KeyExpression) *Index

NewMaxEverVersionIndex creates a MAX_EVER_VERSION index that tracks the maximum version ever written per grouping key. The root expression must be a GroupingKeyExpression with exactly 1 VersionKeyExpression in the grouped (aggregated) portion. Matches Java's new Index(name, rootExpression, IndexTypes.MAX_EVER_VERSION).

func NewMinEverLongIndex

func NewMinEverLongIndex(name string, rootExpression KeyExpression) *Index

NewMinEverLongIndex creates a MIN_EVER_LONG index with the given name and root key expression. MIN_EVER_LONG indexes use FDB atomic MIN to track the minimum value seen per grouping key. Values must be non-negative (unsigned comparison). Deletes are no-ops (_EVER = irreversible). Matches Java's new Index(name, rootExpression, IndexTypes.MIN_EVER_LONG).

func NewMinEverTupleIndex

func NewMinEverTupleIndex(name string, rootExpression KeyExpression) *Index

NewMinEverTupleIndex creates a MIN_EVER_TUPLE index with the given name and root key expression. MIN_EVER_TUPLE indexes use FDB atomic BYTE_MIN to track the minimum tuple-packed value per grouping key. Unlike MIN_EVER_LONG, accepts any tuple-encodable type and compares via byte ordering. Deletes are no-ops (_EVER = irreversible). Idempotent. Matches Java's new Index(name, rootExpression, IndexTypes.MIN_EVER_TUPLE).

func NewMultidimensionalIndex

func NewMultidimensionalIndex(name string, rootExpression KeyExpression) *Index

NewMultidimensionalIndex creates a MULTIDIMENSIONAL index backed by a Hilbert R-tree. The root expression must be a DimensionsKeyExpression that specifies prefix, dimensions, and suffix. Matches Java's new Index(name, rootExpression, IndexTypes.MULTIDIMENSIONAL).

func NewPermutedMaxIndex

func NewPermutedMaxIndex(name string, rootExpression KeyExpression, permutedSize int) *Index

NewPermutedMaxIndex creates a PERMUTED_MAX index with the given name, root key expression, and permuted size. The permuted size specifies how many trailing grouping columns are permuted to after the value in the secondary subspace, enabling value-ordered scans. Matches Java's new Index(name, rootExpression, IndexTypes.PERMUTED_MAX) with PERMUTED_SIZE_OPTION.

func NewPermutedMinIndex

func NewPermutedMinIndex(name string, rootExpression KeyExpression, permutedSize int) *Index

NewPermutedMinIndex creates a PERMUTED_MIN index with the given name, root key expression, and permuted size. The permuted size specifies how many trailing grouping columns are permuted to after the value in the secondary subspace, enabling value-ordered scans. Matches Java's new Index(name, rootExpression, IndexTypes.PERMUTED_MIN) with PERMUTED_SIZE_OPTION.

func NewRankIndex

func NewRankIndex(name string, rootExpression KeyExpression) *Index

NewRankIndex creates a RANK index with the given name and root key expression. RANK indexes maintain a B-tree (like VALUE) plus a skip-list ranked set per group for O(log n) rank/select queries. Matches Java's new Index(name, rootExpression, IndexTypes.RANK).

func NewSumIndex

func NewSumIndex(name string, rootExpression KeyExpression) *Index

NewSumIndex creates a SUM index with the given name and root key expression. SUM indexes use FDB atomic ADD to maintain running sums per grouping key. The expression must include at least one grouped (aggregated) column. Matches Java's new Index(name, rootExpression, IndexTypes.SUM).

func NewTextIndex

func NewTextIndex(name string, rootExpression KeyExpression) *Index

NewTextIndex creates a TEXT index with the given name and root key expression. TEXT indexes tokenize string fields and store per-token position lists in a BunchedMap. Matches Java's new Index(name, rootExpression, IndexTypes.TEXT).

func NewTimeWindowLeaderboardIndex

func NewTimeWindowLeaderboardIndex(name string, rootExpression KeyExpression) *Index

NewTimeWindowLeaderboardIndex creates a TIME_WINDOW_LEADERBOARD index. TIME_WINDOW_LEADERBOARD indexes maintain multiple ranked sets, one per time window, enabling time-windowed leaderboard queries. Matches Java's new Index(name, rootExpression, IndexTypes.TIME_WINDOW_LEADERBOARD).

func NewVectorIndex

func NewVectorIndex(name string, rootExpression KeyExpression, numDimensions int) *Index

NewVectorIndex creates a VECTOR index backed by an HNSW graph. The root expression identifies the vector field to index. numDimensions specifies the vector dimensionality. Supports EUCLIDEAN, COSINE, and INNER_PRODUCT metrics. Matches Java's new Index(name, rootExpression, IndexTypes.VECTOR).

func NewVersionIndex

func NewVersionIndex(name string, rootExpression KeyExpression) *Index

NewVersionIndex creates a VERSION index with the given name and root key expression. VERSION indexes store the record's commit version in the index key for version-ordered queries. The root expression should include a VersionKeyExpression (typically via Concat with other fields). Matches Java's new Index(name, rootExpression, IndexTypes.VERSION).

func (*Index) CanonicalType

func (idx *Index) CanonicalType() string

canonicalIndexType resolves a deprecated index-type alias to the type whose behaviour it names, and returns every other type unchanged.

One function rather than two more `case` labels on each switch, because Java only ever answers this question ONCE. There, the type string picks a maintainer out of the factory registry and every later question — is it idempotent, does it validate grouping, can it serve this aggregate — is a method call on the maintainer that was already chosen. Go flattened that into several independent switches over `idx.Type`, so an alias handled at the dispatch and nowhere else would produce an index that builds with the right maintainer and is then mis-judged by the idempotency check, the grouping validator and the aggregate matcher. Routing every one of those switches through here keeps the alias in a single place, which is the property Java gets for free from its registry. CanonicalType is canonicalIndexType for callers outside this package — the chaos model and its verifier, which re-derive an index's behaviour from its type exactly as the five in-package switches do.

They are the same hazard with a worse failure mode. A model that does not resolve the alias simply DROPS the index: it tracks no min/max for it, the verifier's own switch skips it too, and the pair agrees perfectly about an index neither one looked at. That is a safety net reporting green on the thing it was not checking, which is worse than a wrong answer.

func (*Index) CreatesDuplicates

func (i *Index) CreatesDuplicates() bool

Index represents a secondary index definition. Matches Java's com.apple.foundationdb.record.metadata.Index. CreatesDuplicates reports whether the index's root key expression fans out — a single record can produce multiple index entries (e.g. an index over a repeated/collection field). Ports Java's index.getRootExpression().createsDuplicates(); such an index does NOT produce distinct records (DistinctRecordsProperty). Returns false when the root expression is unset. FAILS CLOSED (conservatively duplicating) for an UNRECOGNIZED root type so the M4 DistinctRecords signal never mis-reports an unknown-fan-out index as distinct (which would elide a required DISTINCT).

func (*Index) GetBooleanOption

func (idx *Index) GetBooleanOption(key string, defaultVal bool) bool

GetBooleanOption returns the boolean value of an index option. Returns the default value if the option is not set. Matches Java's Index.getBooleanOption(String, boolean).

func (*Index) GetPredicateProto

func (idx *Index) GetPredicateProto() *gen.Predicate

GetPredicateProto returns the proto representation of the predicate, if any. Returns nil for programmatic Go predicates set via SetPredicate(). The returned message is a defensive clone: callers cannot mutate the evaluator's serialized authority through this accessor.

func (*Index) GetReplacedByIndexNames

func (idx *Index) GetReplacedByIndexNames() []string

GetReplacedByIndexNames returns the names of indexes that replace this one. Options with keys starting with "replacedBy" (e.g. "replacedBy", "replacedBy_0") have their values as replacement index names. Matches Java's Index.getReplacedByIndexNames().

func (*Index) HasExplicitSubspaceKey

func (idx *Index) HasExplicitSubspaceKey() bool

HasExplicitSubspaceKey reports whether the subspace key was chosen rather than defaulted from the index name. Matches Java's Index.hasExplicitSubspaceKey().

func (*Index) HasFilteringPredicate

func (idx *Index) HasFilteringPredicate() bool

HasFilteringPredicate reports whether this index's predicate can actually reject a record — the question anything reasoning about index COMPLETENESS has to ask, as opposed to HasPredicate's question of whether a predicate is declared at all. A predicate that is a proved tautology rejects nothing, so the index holds an entry for every record and a scan over it is not lossy; Java draws the same line at ValueIndexExpansionVisitor.java:141, where a tautological index predicate is simply not attached to the match candidate and the index stays a full value index.

Only the serialized representation can be reasoned about. A programmatic Go predicate is an opaque closure — `func(proto.Message) bool { return true }` is a tautology no one can prove — so it counts as filtering and fails closed.

func (*Index) HasPredicate

func (idx *Index) HasPredicate() bool

HasPredicate reports whether this is a sparse/filtered index. Check both representations: programmatic Go predicates have only Predicate, while serialized metadata carries predicateProto (and normally its compiled evaluator too). Keeping one authority prevents planner adapters from accidentally admitting one representation but not the other.

func (*Index) HasPrimaryKeyComponentPositions

func (idx *Index) HasPrimaryKeyComponentPositions() bool

HasPrimaryKeyComponentPositions returns true if the index has computed PK component positions.

NOT SEMANTICALLY IDENTICAL to Java's Index.hasPrimaryKeyComponentPositions(), which is `!= null && IntStream.of(...).anyMatch(i -> i >= 0)` -- this drops the second conjunct, so the two disagree for a non-nil ALL-NEGATIVE array. They agree on everything Build produces, because buildPrimaryKeyComponentPositions returns nil rather than an all-negative array (in both engines), so the divergent state is unreachable from the builder in production. It IS constructible by hand, and metadata_evolution_validator_test.go does exactly that -- so "matches Java" is true of the reachable states and false of the predicate, which is why this says which.

func (*Index) HasRowNumberWindowPredicate

func (idx *Index) HasRowNumberWindowPredicate() bool

HasRowNumberWindowPredicate reports whether this index declares a row-number window anywhere on a conjunctive path in its stored predicate. Answers only from the SERIALIZED predicate: a programmatic Go predicate is an opaque closure and cannot declare a window. Matches the predicate half of Java's SlidingWindowIndexMaintainerFactory.isSlidingWindowIndex().

func (*Index) IsAtomicMutationIndex

func (idx *Index) IsAtomicMutationIndex() bool

IsAtomicMutationIndex reports whether this index is maintained via FDB atomic mutations that store aggregated or running-extremum entries rather than per-record values. Such indexes (COUNT/SUM totals, MAX_EVER/MIN_EVER running extrema, BITMAP_VALUE bitsets) must never be offered as an ordinary VALUE-index scan source: their entries are not the underlying record values, so an ordered per-record scan (e.g. a StreamingAgg over the index) reads stale/mislaid data.

Mirrors Java, where AtomicMutationIndexMaintainerFactory (count, count_not_null, count_updates, sum, max_ever_*, min_ever_*, max_ever_version) and BitmapValueIndexMaintainerFactory never call expandValueIndexMatchCandidate — these types only ever reach the aggregate expansion path (or none), and their maintainers reject a BY_VALUE scan at runtime. VALUE, VERSION, RANK, and the PERMUTED_MIN/MAX types are deliberately excluded here: they are genuinely value-scannable (their Java maintainers support BY_VALUE).

func (*Index) IsClearWhenZero

func (idx *Index) IsClearWhenZero() bool

IsClearWhenZero returns whether this index should clear entries when values reach zero. When true, atomic ADD mutations are followed by CompareAndClear(zero) to remove stale zero-value entries. Applies to COUNT, COUNT_NOT_NULL, and SUM indexes. Matches Java's IndexOptions.CLEAR_WHEN_ZERO.

func (*Index) IsUnique

func (idx *Index) IsUnique() bool

IsUnique returns whether this index enforces a uniqueness constraint. Matches Java's Index.isUnique() which checks IndexOptions.UNIQUE_OPTION.

func (*Index) PrimaryKeyComponentPositions

func (idx *Index) PrimaryKeyComponentPositions() []int

PrimaryKeyComponentPositions returns the overlap mapping between index key and primary key. nil means no overlap was computed. Matches Java's Index.getPrimaryKeyComponentPositions().

func (*Index) RowNumberWindowSpec

func (idx *Index) RowNumberWindowSpec() (*RowNumberWindowSpec, error)

RowNumberWindowSpec returns the window declaration the MAINTAINER will use, or an error when the index carries no usable one. Uses Java's narrower maintainer-side lookup — see qualifyRowNumberWindowPredicateProto.

func (*Index) SetClearWhenZero

func (idx *Index) SetClearWhenZero(clear bool) *Index

SetClearWhenZero enables or disables the clear-when-zero behavior. Matches Java's IndexOptions.CLEAR_WHEN_ZERO.

func (*Index) SetPredicate

func (idx *Index) SetPredicate(p IndexPredicate) *Index

SetPredicate sets a filter predicate for sparse/filtered indexes. Only records where the predicate returns true will have index entries. Note: programmatic Go predicates cannot be serialized to proto. Use SetPredicateProto for predicates that must survive metadata round-tripping.

func (*Index) SetPredicateProto

func (idx *Index) SetPredicateProto(p *gen.Predicate) error

SetPredicateProto sets a predicate from a proto message. This both stores the proto for round-tripping and builds an evaluator function.

func (*Index) SetSubspaceKey

func (idx *Index) SetSubspaceKey(key any) *Index

SetSubspaceKey overrides the default subspace key (index name). Matches Java's Index.setSubspaceKey(), including its side effect of MARKING the key explicit — see useExplicitSubspaceKey.

func (*Index) SetUnique

func (idx *Index) SetUnique() *Index

SetUnique marks this index as enforcing uniqueness.

func (*Index) SubspaceTupleKey

func (idx *Index) SubspaceTupleKey() any

SubspaceTupleKey returns the key used to identify this index's subspace within the IndexKey (2) subspace. Defaults to the index name. Matches Java's Index.getSubspaceTupleKey().

func (*Index) TrimPrimaryKey

func (idx *Index) TrimPrimaryKey(primaryKey tuple.Tuple) (tuple.Tuple, error)

trimPrimaryKey removes PK components that already appear in the index key. Returns the remaining PK components that need to be appended to the index entry. Returns an error if primaryKeyComponentPositions references an index beyond the primary key length. Matches Java's Index.TrimPrimaryKey().

type IndexAggregateFunction

type IndexAggregateFunction struct {
	Name    string        // Function name (e.g. "count", "sum", "min_ever")
	Operand KeyExpression // The operand (typically a GroupingKeyExpression)
	Index   string        // Optional: explicit index name. Empty = auto-select.
}

IndexAggregateFunction specifies an aggregate computation to evaluate via an index. Matches Java's com.apple.foundationdb.record.metadata.IndexAggregateFunction.

func NewCountAggregateFunction

func NewCountAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewCountAggregateFunction creates a COUNT aggregate function. Matches Java's IndexAggregateFunction(FunctionNames.COUNT, operand).

func NewMaxAggregateFunction

func NewMaxAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewMaxAggregateFunction creates a MAX aggregate function (via VALUE index).

func NewMaxEverAggregateFunction

func NewMaxEverAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewMaxEverAggregateFunction creates a MAX_EVER aggregate function.

func NewMinAggregateFunction

func NewMinAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewMinAggregateFunction creates a MIN aggregate function (via VALUE index).

func NewMinEverAggregateFunction

func NewMinEverAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewMinEverAggregateFunction creates a MIN_EVER aggregate function.

func NewSumAggregateFunction

func NewSumAggregateFunction(operand KeyExpression) *IndexAggregateFunction

NewSumAggregateFunction creates a SUM aggregate function.

type IndexBuildState

type IndexBuildState struct {
	State          IndexState
	RecordsScanned *int64 // nil if not WRITE_ONLY or not tracked
	RecordsInTotal *int64 // nil if no COUNT index available
}

IndexBuildState reports the current state of an index build. Matches Java's com.apple.foundationdb.record.provider.foundationdb.IndexBuildState.

func LoadIndexBuildState

func LoadIndexBuildState(store *FDBRecordStore, index *Index) (*IndexBuildState, error)

LoadIndexBuildState loads the build state for an index within an open store. Matches Java's IndexBuildState.loadIndexBuildStateAsync().

type IndexEntry

type IndexEntry struct {
	Index *Index
	Key   tuple.Tuple // Full key (indexed values + primary key components)
	Value tuple.Tuple // Value tuple (empty for VALUE indexes)
	// contains filtered or unexported fields
}

IndexEntry represents a single entry from an index scan. Matches Java's com.apple.foundationdb.record.IndexEntry.

func (*IndexEntry) IndexValues

func (e *IndexEntry) IndexValues() tuple.Tuple

IndexValues extracts the indexed values portion from the entry key. Returns the prefix of Key up to the index expression's column count.

func (*IndexEntry) PrimaryKey

func (e *IndexEntry) PrimaryKey() tuple.Tuple

PrimaryKey extracts the primary key portion from the index entry key. When the index has primaryKeyComponentPositions, some PK components are pulled from the index key portion (deduplicated) and the rest from the appended tail. Matches Java's IndexEntry.getPrimaryKey() → Index.getEntryPrimaryKey().

type IndexKeySizeError

type IndexKeySizeError struct {
	IndexName  string
	PrimaryKey tuple.Tuple
	KeySize    int
	Limit      int
}

IndexKeySizeError indicates an index entry key exceeds the FDB key size limit. Matches Java's FDBExceptions.FDBStoreKeySizeException.

func (*IndexKeySizeError) Error

func (e *IndexKeySizeError) Error() string

type IndexMaintainer

type IndexMaintainer interface {
	// Update updates the index for a record change.
	// oldRecord is nil for inserts, newRecord is nil for deletes.
	Update(oldRecord, newRecord *FDBStoredRecord[proto.Message]) error

	// UpdateWhileWriteOnly updates the index during WRITE_ONLY state (index being built).
	// For idempotent indexes (VALUE), this is a pass-through to Update().
	// For non-idempotent indexes, checks if the record's PK is in the already-built range.
	// Matches Java's standardIndexMaintainer.updateWhileWriteOnly().
	UpdateWhileWriteOnly(oldRecord, newRecord *FDBStoredRecord[proto.Message]) error

	// Scan scans the index within the given tuple range.
	// Matches Java's IndexMaintainer.scan().
	Scan(scanRange TupleRange, continuation []byte, scanProperties ScanProperties) RecordCursor[*IndexEntry]

	// DeleteWhere clears all index entries whose key starts with the given prefix.
	// Uses FDB range clears — no scanning. Called by DeleteRecordsWhere.
	// Matches Java's IndexMaintainer.deleteWhere().
	DeleteWhere(prefix tuple.Tuple) error

	// CanDeleteWhere reports whether this maintainer can clear exactly the
	// entries the given prefix names, returning the reason it cannot.
	// Matches Java's IndexMaintainer.canDeleteWhere.
	//
	// It is part of the interface — not an optional capability a maintainer may
	// leave out — for the same reason Java declares it ABSTRACT: a new index
	// type must not be able to reach the DeleteRecordsWhere path without having
	// decided what its physical keys are prefixed by. That is not hypothetical:
	// while the check was an optional interface here, the bound was absent on
	// one type after another — RANK's ranked sets, PERMUTED's permuted
	// secondary, the aggregates' grouping keys, TEXT's bunched tokens — because
	// an optional check reads "does not implement it" and "can clear any prefix"
	// as the same answer, and the first is by far the more common. Closing that
	// one index type at a time converges only as fast as someone thinks to look;
	// a compile error converges immediately.
	//
	// standardIndexMaintainer supplies the default, so a type embedding it gets
	// the bound its root expression implies and overrides only to NARROW it.
	//
	// WHERE the question is asked is the other half, and it cannot be left to
	// DeleteWhere. DeleteRecordsWhere queues the record, version and count
	// clears BEFORE calling any maintainer, so a refusal raised inside
	// DeleteWhere arrives after the records are already gone from the
	// transaction: a caller that logs the error and commits anyway loses the
	// records while the index keeps describing them. Every refusal therefore
	// belongs in the store's preflight, and the matching check inside
	// DeleteWhere stays only as a backstop for direct callers — which is exactly
	// the split Java has between canDeleteWhere and the Verify.verify inside
	// deleteWhere.
	CanDeleteWhere(prefix tuple.Tuple) error
}

IndexMaintainer handles index updates and scanning. Matches Java's com.apple.foundationdb.record.provider.foundationdb.IndexMaintainer.

type IndexNotBuiltError

type IndexNotBuiltError struct {
	IndexName string
}

IndexNotBuiltError is returned when trying to mark an index as readable but it has unbuilt ranges remaining in its range set.

func (*IndexNotBuiltError) Error

func (e *IndexNotBuiltError) Error() string

type IndexNotFoundError

type IndexNotFoundError struct {
	IndexName string
}

IndexNotFoundError is returned when an index name is not found in the metadata. Matches Java's MetaDataException for missing indexes.

func (*IndexNotFoundError) Error

func (e *IndexNotFoundError) Error() string

type IndexNotReadableError

type IndexNotReadableError struct {
	IndexName    string
	CurrentState IndexState
}

IndexNotReadableError is returned when trying to scan an index that is not in a readable state. Matches Java's com.apple.foundationdb.record.ScanNonReadableIndexException.

func (*IndexNotReadableError) Error

func (e *IndexNotReadableError) Error() string

type IndexPredicate

type IndexPredicate func(msg proto.Message) bool

IndexPredicate is a function that determines whether a record should be indexed. Return true to include the record in the index, false to skip it. Matches Java's Index predicate concept for sparse/filtered indexes.

type IndexRebuildPolicy

type IndexRebuildPolicy func(index *Index, recordCount int64, indexOnNewRecordTypes bool) IndexState

IndexRebuildPolicy determines what state a new/changed index should be put in when the store is opened with updated metadata. Matches Java's FDBRecordStoreBase.UserVersionChecker.needRebuildIndex().

type IndexRecordFunction

type IndexRecordFunction struct {
	Name    string        // Function name (e.g. "rank")
	Operand KeyExpression // The operand (typically a GroupingKeyExpression)
	Index   string        // Optional: explicit index name. Empty = auto-select.

	// TimeWindow specifies the leaderboard time window for TIME_WINDOW_RANK and
	// TIME_WINDOW_RANK_AND_ENTRY functions. Nil for plain RANK (uses all-time).
	// Matches Java's TimeWindowRecordFunction.timeWindow (TimeWindowForFunction).
	TimeWindow *TimeWindowForFunction
}

IndexRecordFunction specifies a function to evaluate on a record using an index. Matches Java's com.apple.foundationdb.record.metadata.IndexRecordFunction.

type IndexScanType

type IndexScanType string

IndexScanType identifies the type of index scan. Matches Java's com.apple.foundationdb.record.IndexScanType.

const (
	// IndexScanByValue scans a VALUE index by its indexed values.
	IndexScanByValue IndexScanType = "BY_VALUE"
	// IndexScanByRank scans a RANK index by rank position.
	// The range bounds contain [group..., rank] where rank is an int64.
	// Matches Java's IndexScanType.BY_RANK.
	IndexScanByRank IndexScanType = "BY_RANK"

	// IndexScanByTextToken scans a TEXT index by text token.
	// Matches Java's IndexScanType.BY_TEXT_TOKEN.
	IndexScanByTextToken IndexScanType = "BY_TEXT_TOKEN"

	// IndexScanByGroup scans a PERMUTED_MIN/MAX index by group in the
	// secondary (permuted) subspace. Returns one entry per group with the
	// extremum value, ordered by [groupPrefix, value, groupSuffix].
	// Matches Java's IndexScanType.BY_GROUP.
	IndexScanByGroup IndexScanType = "BY_GROUP"

	// IndexScanByTimeWindow scans a TIME_WINDOW_LEADERBOARD index within a
	// specific time window. The scan range contains score bounds; the
	// leaderboard type and timestamp are provided via TimeWindowScanRange.
	// Matches Java's IndexScanType.BY_TIME_WINDOW.
	IndexScanByTimeWindow IndexScanType = "BY_TIME_WINDOW"

	// IndexScanByDistance scans a VECTOR index by distance to a query vector.
	// Returns results sorted by distance (closest first) as a kNN search.
	// Must be used with ScanVectorIndex which provides VectorScanBounds.
	// Matches Java's IndexScanType.BY_DISTANCE.
	IndexScanByDistance IndexScanType = "BY_DISTANCE"

	// IndexScanByDistanceOrderedStream is the RFC-156 Phase C VBASE distance-
	// ordered STREAMING scan: a demand-driven cursor that widens its scanned
	// horizon in batches as the consumer (Filter → Limit) pulls, bounded by a
	// budget cap (honest ScanLimitReached truncation, never a silent < k). Go-only
	// read-side extension; no wire-format change.
	IndexScanByDistanceOrderedStream IndexScanType = "BY_DISTANCE_ORDERED_STREAM"
)

type IndexState

type IndexState int

IndexState represents the state of a secondary index. Matches Java's com.apple.foundationdb.record.IndexState.

const (
	// IndexStateReadable means the index is fully built and can be read and written.
	IndexStateReadable IndexState = 0
	// IndexStateWriteOnly means the index is being built. Written to on record changes
	// but not yet safe for queries.
	IndexStateWriteOnly IndexState = 1
	// IndexStateDisabled means the index is not maintained or readable.
	IndexStateDisabled IndexState = 2
	// IndexStateReadableUniquePending means the unique index is fully indexed but may
	// have duplicate entries. Safe to query if uniqueness is not assumed.
	IndexStateReadableUniquePending IndexState = 3
)

func AlwaysRebuildPolicy

func AlwaysRebuildPolicy(_ *Index, _ int64, _ bool) IndexState

AlwaysRebuildPolicy always rebuilds indexes inline. Matches Java's ALWAYS_READABLE_CHECKER behavior.

func DefaultIndexRebuildPolicy

func DefaultIndexRebuildPolicy(index *Index, recordCount int64, indexOnNewRecordTypes bool) IndexState

DefaultIndexRebuildPolicy matches Java's default behavior: inline rebuild (READABLE) for stores with ≤200 records or indexes on new record types, DISABLED otherwise (requires OnlineIndexer). Java constant: FDBRecordStoreBase.MAX_RECORDS_FOR_REBUILD = 200.

func WriteOnlyIfTooLargePolicy

func WriteOnlyIfTooLargePolicy(index *Index, recordCount int64, indexOnNewRecordTypes bool) IndexState

WriteOnlyIfTooLargePolicy returns READABLE for small stores (inline rebuild) and WRITE_ONLY for larger stores. WRITE_ONLY is the production-safe choice: new writes maintain the index immediately, and the operator invokes OnlineIndexer to backfill historical data. This avoids both:

  • READABLE: times out on large stores (single-transaction rebuild)
  • DISABLED: index is completely ignored, new writes don't maintain it

Threshold matches Java's MAX_RECORDS_FOR_REBUILD = 200.

func (IndexState) IsDisabled

func (s IndexState) IsDisabled() bool

IsDisabled returns true if this index is in DISABLED state.

func (IndexState) IsScannable

func (s IndexState) IsScannable() bool

IsScannable returns true if this state allows index scans. Matches Java's IndexState.isScannable() — READABLE or READABLE_UNIQUE_PENDING.

func (IndexState) IsWriteOnly

func (s IndexState) IsWriteOnly() bool

IsWriteOnly returns true if this index is in WRITE_ONLY state.

func (IndexState) String

func (s IndexState) String() string

type IndexValidationEntry

type IndexValidationEntry struct {
	IndexKey   tuple.Tuple
	PrimaryKey tuple.Tuple
}

IndexValidationEntry represents a single missing or orphaned index entry.

type IndexValidationResult

type IndexValidationResult struct {
	// MissingEntries are index entries that should exist (based on records) but don't.
	MissingEntries []IndexValidationEntry
	// OrphanedEntries are index entries that exist but have no corresponding record.
	OrphanedEntries []IndexValidationEntry
	// TotalRecordsScanned is the number of records checked.
	TotalRecordsScanned int
	// TotalEntriesScanned is the number of index entries checked.
	TotalEntriesScanned int
}

IndexValidationResult contains the results of an index validation.

func (*IndexValidationResult) IsValid

func (r *IndexValidationResult) IsValid() bool

IsValid returns true if no discrepancies were found.

type IndexValueSizeError

type IndexValueSizeError struct {
	IndexName  string
	PrimaryKey tuple.Tuple
	ValueSize  int
	Limit      int
}

IndexValueSizeError indicates an index entry value exceeds the FDB value size limit. Matches Java's FDBExceptions.FDBStoreValueSizeException.

func (*IndexValueSizeError) Error

func (e *IndexValueSizeError) Error() string

type IndexVersionKind

type IndexVersionKind string

IndexVersionKind names which of an index's two version stamps a IndexVersionTooNewError refers to.

const (
	// IndexVersionAdded is the version at which the index was introduced.
	IndexVersionAdded IndexVersionKind = "added"
	// IndexVersionLastModified is the version at which the index definition last changed.
	IndexVersionLastModified IndexVersionKind = "last modified"
)

type IndexVersionTooNewError

type IndexVersionTooNewError struct {
	IndexName           string
	Kind                IndexVersionKind
	AddedVersion        int
	LastModifiedVersion int
	MetaDataVersion     int
}

IndexVersionTooNewError is returned when an index declares a version that is newer than the metadata version carrying it.

Such metadata is not merely untidy: an index whose lastModifiedVersion exceeds the metadata version is permanently "new since" every store header version, so every subsequent version bump re-runs the rebuild decision for it and can clear an index a background build already populated.

Matches Java's MetaDataValidator.validateIndex(), which throws MetaDataException for both the added-version and last-modified-version cases (MetaDataValidator.java:124-133).

func (*IndexVersionTooNewError) Error

func (e *IndexVersionTooNewError) Error() string

func (*IndexVersionTooNewError) Unwrap

func (e *IndexVersionTooNewError) Unwrap() error

Unwrap reports this as a metadata validation failure so that callers matching on *MetaDataError keep working, mirroring Java where this is a MetaDataException.

type IndexingHeartbeat

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

IndexingHeartbeat manages liveness heartbeats for concurrent index building. Each indexer process writes a heartbeat at [9, indexSubspaceKey, 7, uuid] so that other processes can detect stale/crashed builders.

In non-mutual (exclusive) mode, an active heartbeat from another process prevents this indexer from starting. In mutual mode, heartbeats are written but not checked (concurrent building is allowed).

Matches Java's IndexingHeartbeat.

func NewIndexingHeartbeat

func NewIndexingHeartbeat(info string, leaseLengthMs int64, allowMutual bool, env *dst.Env) *IndexingHeartbeat

NewIndexingHeartbeat creates a heartbeat manager for an indexer. leaseLengthMs is how long a heartbeat remains valid before being considered stale. env is the DST environment (from the indexer's database, oi.db.Env()); a nil env means production — the UUID and timestamps then come from crypto/rand and the wall clock, byte-identical to the pre-seam behavior.

func (*IndexingHeartbeat) CheckAndUpdate

func (h *IndexingHeartbeat) CheckAndUpdate(tx fdb.WritableTransaction, storeSubspace subspace.Subspace, index *Index) error

CheckAndUpdate checks for conflicting heartbeats and updates this indexer's heartbeat.

In non-mutual mode: scans all heartbeats for this index. If any active (non-stale) heartbeat from a different indexer exists, returns a SynchronizedSessionLockedError. Stale heartbeats (older than leaseLengthMs) are ignored — the process is presumed dead.

In mutual mode: skips the check entirely and just updates.

Called once per buildRange transaction to maintain liveness. Matches Java's IndexingHeartbeat.checkAndUpdateHeartbeat().

func (*IndexingHeartbeat) Cleanup

func (h *IndexingHeartbeat) Cleanup(tx fdb.WritableTransaction, storeSubspace subspace.Subspace, index *Index)

Cleanup removes this indexer's heartbeat. Called when the build completes or is explicitly cancelled. Matches Java's IndexingHeartbeat.removeHeartbeat().

type IndexingPolicy

type IndexingPolicy struct {
	// ForceStampOverwrite forces writing the stamp without conflict checks on
	// fresh (non-continued) builds, or allows overwriting on continued builds
	// when no records have been scanned. Used internally during rebuild.
	ForceStampOverwrite bool

	// AllowUnblock permits unblocking a blocked stamp during resume.
	AllowUnblock bool

	// AllowUnblockID restricts unblocking to stamps with a matching blockID.
	// Empty string means any blockID is accepted (when AllowUnblock is true).
	AllowUnblockID string

	// AllowedTakeovers is the set of allowed build method conversions.
	AllowedTakeovers map[TakeoverType]bool
}

IndexingPolicy configures how the OnlineIndexer handles stamp conflicts, blocked stamps, and build method conversions. Matches Java's OnlineIndexer.IndexingPolicy.

func (*IndexingPolicy) ShouldAllowTypeConversionContinue

func (p *IndexingPolicy) ShouldAllowTypeConversionContinue(newStamp, savedStamp *gen.IndexBuildIndexingStamp) bool

ShouldAllowTypeConversionContinue returns true if switching from savedStamp's method to newStamp's method is permitted by the policy. Matches Java's IndexingPolicy.shouldAllowTypeConversionContinue().

func (*IndexingPolicy) ShouldAllowUnblock

func (p *IndexingPolicy) ShouldAllowUnblock(stampBlockID string) bool

ShouldAllowUnblock returns true if the policy allows unblocking a stamp with the given blockID. Matches Java's IndexingPolicy.shouldAllowUnblock().

type IndexingRangeSet

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

IndexingRangeSet wraps a RangeSet scoped to a specific index's build progress. Lives at store subspace [IndexRangeSpaceKey (6), indexSubspaceKey]. Wire-compatible with Java's IndexingRangeSet.

func NewIndexingRangeSet

func NewIndexingRangeSet(storeSubspace subspace.Subspace, index *Index) *IndexingRangeSet

NewIndexingRangeSet creates an IndexingRangeSet for the given index within the store's subspace. Matches Java's IndexingRangeSet.forIndexBuild(store, index).

func (*IndexingRangeSet) Clear

func (irs *IndexingRangeSet) Clear(tr fdb.WritableTransaction)

Clear removes all range tracking data.

func (*IndexingRangeSet) ContainsKey

func (irs *IndexingRangeSet) ContainsKey(tr fdb.WritableTransaction, key []byte) (bool, error)

ContainsKey checks if a primary key (tuple-packed bytes) is in the built range.

func (*IndexingRangeSet) FirstMissingRange

func (irs *IndexingRangeSet) FirstMissingRange(tr fdb.WritableTransaction) (*RangeSetRange, error)

FirstMissingRange returns the first gap in the range set, or nil if complete.

func (*IndexingRangeSet) InsertRange

func (irs *IndexingRangeSet) InsertRange(tr fdb.WritableTransaction, begin, end []byte, requireEmpty bool) (bool, error)

InsertRange marks a range as built. Returns true if the database was modified.

func (*IndexingRangeSet) IsComplete

func (irs *IndexingRangeSet) IsComplete(tr fdb.WritableTransaction) (bool, error)

IsComplete returns true if all ranges have been built.

func (*IndexingRangeSet) ListMissingRanges

func (irs *IndexingRangeSet) ListMissingRanges(tr fdb.WritableTransaction) ([]RangeSetRange, error)

ListMissingRanges returns all gaps in the range set.

func (*IndexingRangeSet) ListMissingRangesInBytes

func (irs *IndexingRangeSet) ListMissingRangesInBytes(tr fdb.WritableTransaction, begin, end []byte) ([]RangeSetRange, error)

ListMissingRangesInBytes returns missing ranges within the given byte boundaries. Used by mutual indexing to check missing ranges within a fragment.

type Int64Evaluator

type Int64Evaluator interface {
	EvaluateInt64(record *FDBStoredRecord[proto.Message], msg proto.Message) (int64, bool, error)
}

Int64Evaluator extracts a single int64 value without boxing to any. Go's convT64 allocates for every non-zero int64→any conversion. This interface bypasses that by returning int64 directly.

type IntersectionChildResume

type IntersectionChildResume struct {
	Continuation []byte
	Started      bool
}

IntersectionChildResume is one child's decoded resume state, the output of decodeIntersectionContinuation. Started + (empty) Continuation classify the child as START (!Started → begin fresh), MID (Started + bytes → resume from bytes), or END (Started + empty → exhausted).

func DecodeIntersectionContinuation

func DecodeIntersectionContinuation(data []byte, n int) ([]IntersectionChildResume, error)

DecodeIntersectionContinuation is the exact inverse of buildIntersectionContinuation: it splits a parent IntersectionContinuation proto into n per-child resume states (RFC-071). A nil/empty continuation yields n all-fresh children (START). A proto that fails to parse, or whose child count does not match n, is a hard error (mirroring Java IntersectionCursorContinuation's RecordCoreArgumentException) — never a silent truncate. Consumed executor-side by executeIntersection / executeMultiIntersection.

type InvalidNameError

type InvalidNameError = protoname.InvalidNameError

InvalidNameError mirrors Java's ProtoUtils.InvalidNameException.

type IsolationLevel

type IsolationLevel int

IsolationLevel represents the transaction isolation level. Matches Java's IsolationLevel enum.

Java Reference: com.apple.foundationdb.record.IsolationLevel Location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/IsolationLevel.java

func (IsolationLevel) IsSnapshot

func (level IsolationLevel) IsSnapshot() bool

IsSnapshot returns true if this isolation level uses snapshot reads.

Java equivalent: IsolationLevel.isSnapshot()

func (IsolationLevel) String

func (level IsolationLevel) String() string

String returns a human-readable representation of the isolation level.

type ItemSlot

type ItemSlot struct {
	HilbertValue *big.Int
	Point        Point
	KeySuffix    tuple.Tuple
	Value        tuple.Tuple
}

ItemSlot is a leaf node slot containing a data item.

func (ItemSlot) GetMBR

func (s ItemSlot) GetMBR() MBR

GetMBR returns the MBR for this item (a degenerate point MBR).

func (ItemSlot) ItemKey

func (s ItemSlot) ItemKey() tuple.Tuple

ItemKey returns the combined item key (point coordinates + suffix).

type KVCallback

type KVCallback func(keyLen, valueLen int)

KVCallback is called for each raw FDB key-value pair read during iteration. Used for byte scan limiting — the callback receives the raw key and value lengths before deserialization. Matches Java's Consumer<KeyValue> callback passed to BunchedMapMultiIterator.

type KeyExpression

type KeyExpression interface {
	// Evaluate extracts key tuples from a record.
	// Returns a list of key tuples (each tuple is a []any).
	// Single-valued expressions return one tuple; fan-out expressions
	// (e.g. repeated fields) return multiple tuples.
	//
	// record is the top-level stored record context (provides version, PK, etc.).
	// msg is the current message being evaluated (changes during nesting into sub-messages).
	// Either or both may be nil.
	//
	// Matches Java's KeyExpression.evaluateMessage(FDBRecord, Message) -> List<Key.Evaluated>.
	Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

	// FieldNames returns the field names this expression accesses
	FieldNames() []string

	// ColumnSize returns the number of tuple elements this expression produces.
	// Matches Java's KeyExpression.getColumnSize().
	ColumnSize() int

	// ToKeyExpression serializes this expression to its protobuf representation.
	// Matches Java's KeyExpression.toKeyExpression().
	ToKeyExpression() *gen.KeyExpression
}

KeyExpression represents an expression that extracts key components from a record. Matches Java's KeyExpression interface which returns List<Key.Evaluated>.

func Concat

func Concat(exprs ...KeyExpression) KeyExpression

Concat creates a composite key from multiple expressions

func EmptyKey

func EmptyKey() KeyExpression

EmptyKey creates a key expression that produces an empty tuple.

func FanOut

func FanOut(name string) KeyExpression

FanOut creates a key expression for a repeated field that produces one index entry per repeated value. Matches Java's Key.Expressions.field("name", FanType.FanOut).

func Field

func Field(name string) KeyExpression

Field creates a key expression that extracts a single (non-repeated) field.

func FieldConcatenate

func FieldConcatenate(name string) KeyExpression

FieldConcatenate creates a key expression for a repeated field that collects all repeated values into a single tuple element. Matches Java's Key.Expressions.field("name", FanType.Concatenate). This is the argument shape a CARDINALITY() index uses: the whole array is materialised into one Key.Evaluated so its element count can be taken.

func GetNestedExpression

func GetNestedExpression(expr KeyExpression) KeyExpression

GetNestedExpression returns the nested expression of a RecordTypeKeyExpression

func GroupingKeyExpressionOf

func GroupingKeyExpressionOf(g *GroupingKeyExpression) (KeyExpression, bool)

GroupingKeyExpressionOf returns the GROUPING half of a grouping key expression: the leading columns the index groups by, with the trailing aggregated columns removed. For a COUNT(*) index (no aggregated columns) that is the whole key.

Returns false when the split cannot be made exactly — no grouping columns at all (an ungrouped aggregate), or a child expression that straddles the grouping/grouped boundary. Declining is the only safe answer: a companion matched on a mis-split key would drive the merge from the wrong group set.

func KeyExpressionFromProto

func KeyExpressionFromProto(expr *gen.KeyExpression) (KeyExpression, error)

KeyExpressionFromProto deserializes a protobuf KeyExpression to a Go KeyExpression. Exactly one field must be set. Matches Java's KeyExpression.fromProto().

func Nest

func Nest(parentField string, child KeyExpression) KeyExpression

Nest creates a nesting expression: navigate into a message field and evaluate the child expression on the sub-message. Matches Java's field("parent").nest("child").

func NestFanOut

func NestFanOut(parentField string, child KeyExpression) KeyExpression

NestFanOut creates a nesting expression for a repeated message field. Each element of the repeated field is evaluated with the child expression, producing fan-out. Matches Java's field("parent", FanType.FanOut).nest(child).

type KeyExpressionError

type KeyExpressionError struct {
	Message string
}

KeyExpressionError is returned when a key expression evaluation fails. Matches Java's com.apple.foundationdb.record.metadata.expressions.KeyExpression.InvalidExpressionException.

func (*KeyExpressionError) Error

func (e *KeyExpressionError) Error() string

type KeyWithValueExpression

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

KeyWithValueExpression wraps an inner key expression and splits its evaluated columns into a "key" portion and a "value" portion. The key portion (columns 0..splitPoint-1) is stored in the FDB key, and the value portion (columns splitPoint..end) is stored in the FDB value. This enables "covering indexes" where additional query-relevant columns can be read without fetching the record. Matches Java's com.apple.foundationdb.record.metadata.expressions.KeyWithValueExpression.

func KeyWithValue

func KeyWithValue(inner KeyExpression, splitPoint int) *KeyWithValueExpression

KeyWithValue creates a KeyWithValueExpression. splitPoint is the number of leading columns that go into the FDB key; remaining columns go into the value. Matches Java's Key.Expressions.keyWithValue(inner, splitPoint).

func (*KeyWithValueExpression) ColumnSize

func (k *KeyWithValueExpression) ColumnSize() int

ColumnSize returns the split point — only key columns count, not value columns. Matches Java's KeyWithValueExpression.getColumnSize().

func (*KeyWithValueExpression) Evaluate

func (k *KeyWithValueExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate delegates to the inner key expression. The key/value split is only applied when writing/reading index entries, not during evaluation.

func (*KeyWithValueExpression) FieldNames

func (k *KeyWithValueExpression) FieldNames() []string

FieldNames delegates to the inner key expression.

func (*KeyWithValueExpression) InnerKey

func (k *KeyWithValueExpression) InnerKey() KeyExpression

InnerKey returns the wrapped key expression.

func (*KeyWithValueExpression) SplitEvaluatedKey

func (k *KeyWithValueExpression) SplitEvaluatedKey(fullKey []any) (keyPart []any, valuePart []any)

SplitEvaluatedKey splits a full evaluated result into key and value portions. key = columns[0:splitPoint], value = columns[splitPoint:]. Matches Java's KeyWithValueExpression.getKey() / getValue().

func (*KeyWithValueExpression) SplitPoint

func (k *KeyWithValueExpression) SplitPoint() int

SplitPoint returns the number of leading columns that form the key portion.

func (*KeyWithValueExpression) ToKeyExpression

func (k *KeyWithValueExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes KeyWithValueExpression to proto. Matches Java's KeyWithValueExpression.toKeyExpression().

type Kind

type Kind uint8

Kind classifies an event the way Java's StoreTimer taxonomy does with interfaces (StoreTimer.java:203-402). Java splits the classification across the type system — a bare `Event` is timed, `Count extends Event` adds `isSize()` — and reads it back with `instanceof` in getKeysAndValues (StoreTimer.java:747-780). Go has no sealed interface hierarchy, so the same three-way distinction is carried as data on the one Event struct and read back with a switch. The distinction is not cosmetic: it decides which field of the Counter carries meaning, and therefore how the event may be exported.

const (
	// KindUnspecified is the zero value, and no declared event may carry it.
	// Making the zero value invalid is deliberate: every Event literal in this
	// package is unkeyed, so adding Kind to the struct made the compiler
	// demand a classification at each declaration rather than silently
	// defaulting a new event into whichever kind happened to be zero.
	KindUnspecified Kind = iota

	// KindTimed is Java's bare StoreTimer.Event: recorded with Record /
	// RecordSince, cumulative value is nanoseconds. Both Count() (occurrences)
	// and CumulativeValue() (total nanos) are meaningful.
	KindTimed

	// KindCount is Java's StoreTimer.Count with isSize()==false: recorded with
	// Increment / IncrementBy, ONLY Count() is meaningful. CumulativeValue()
	// stays 0, exactly as in Java (Counter.increment touches count alone,
	// StoreTimer.java:484-487).
	KindCount

	// KindSize is Java's StoreTimer.Count with isSize()==true — e.g.
	// Counts.SAVE_RECORD_KEY_BYTES (FDBStoreTimer.java:524). Recorded and read
	// exactly like KindCount: the BYTE TOTAL lands in Count(), not in
	// CumulativeValue(). Kept a distinct kind because it changes what the
	// number means to a consumer (bytes, not occurrences) even though the
	// storage is identical.
	KindSize

	// KindSizeDistribution is Java's StoreTimer.SizeEvent
	// (StoreTimer.java:401-402), which is a DIFFERENT thing from the Count above
	// despite the shared word "size", and the difference is not cosmetic:
	//
	//   - a size Count answers "how many bytes in total", so one number says
	//     everything and Java increments it;
	//   - a SizeEvent answers "how big was it each time" — Java records it with
	//     recordSize (StoreTimer.java:569-571), which adds the MAGNITUDE to
	//     cumulativeValue and the OBSERVATION to count, and getKeysAndValues
	//     then emits both `_count` and `_size` (StoreTimer.java:756-757).
	//
	// Collapsing the second onto the first loses the observation count and
	// reports a sum of magnitudes under a key that reads as a count — for the
	// sliding window's SW_WINDOW_COUNT that would render "the window was size 2,
	// then 2, then 1" as the number 5.
	KindSizeDistribution
)

func (Kind) String

func (k Kind) String() string

String renders the kind for diagnostics.

type ListKeyExpression

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

ListKeyExpression combines multiple child expressions, producing nested tuples for each child's result (unlike Then/Concat which flattens). Each child's evaluation result is wrapped as a nested tuple element. Matches Java's com.apple.foundationdb.record.metadata.expressions.ListKeyExpression.

func ListExpr

func ListExpr(children ...KeyExpression) *ListKeyExpression

ListExpr creates a ListKeyExpression from the given children.

func (*ListKeyExpression) ColumnSize

func (l *ListKeyExpression) ColumnSize() int

ColumnSize returns the number of children — each child contributes one column (as a nested tuple element). Matches Java's ListKeyExpression.getColumnSize().

func (*ListKeyExpression) Evaluate

func (l *ListKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate computes the cross-product of all child expression results, wrapping each child's result as a nested tuple.Tuple. Each child's Key.Evaluated values become a single nested tuple element in the result.

This matches Java's ListKeyExpression.evaluateMessage() + combine(), where childValue.toTupleAppropriateList() is added as a single List<Object> element that becomes a nested Tuple when serialized.

IMPORTANT: Child values must be wrapped as tuple.Tuple (named type), NOT bare []any. The FDB tuple packer's type switch uses `case Tuple:` which only matches the named type. Bare []any causes a runtime panic during Pack().

func (*ListKeyExpression) FieldNames

func (l *ListKeyExpression) FieldNames() []string

FieldNames returns all field names from child expressions.

func (*ListKeyExpression) GetChildren

func (l *ListKeyExpression) GetChildren() []KeyExpression

GetChildren returns the child expressions.

func (*ListKeyExpression) ToKeyExpression

func (l *ListKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes ListKeyExpression to proto. Matches Java's ListKeyExpression.toKeyExpression().

type LiteralKeyExpression

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

LiteralKeyExpression represents a static constant value in a key expression. The value does not depend on the record — it evaluates to the same fixed value for every record. Primary use case: passing static arguments to function key expressions, or creating composite indexes with a fixed prefix. Matches Java's LiteralKeyExpression.

func Literal

func Literal(value any) *LiteralKeyExpression

Literal creates a key expression that always evaluates to the given constant value. Supported types: nil, int, int32, int64, float32, float64, bool, string, []byte. Matches Java's Key.Expressions.value(Object).

func (*LiteralKeyExpression) ColumnSize

func (l *LiteralKeyExpression) ColumnSize() int

ColumnSize returns 1 — a literal expression produces a single tuple element.

func (*LiteralKeyExpression) Evaluate

Evaluate returns the constant value regardless of the record. Matches Java's LiteralKeyExpression.evaluateMessage() which ignores the record parameter.

func (*LiteralKeyExpression) FieldNames

func (l *LiteralKeyExpression) FieldNames() []string

FieldNames returns an empty slice — literal expressions don't access any fields.

func (*LiteralKeyExpression) GetValue

func (l *LiteralKeyExpression) GetValue() any

GetValue returns the constant value held by this expression.

func (*LiteralKeyExpression) ToKeyExpression

func (l *LiteralKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes LiteralKeyExpression to proto. Matches Java's LiteralKeyExpression.toKeyExpression().

type MBR

type MBR struct {
	Low  []int64
	High []int64
}

MBR is a minimum bounding rectangle for N dimensions. Stored as [low0, low1, ..., lowN-1, high0, high1, ..., highN-1].

func MBRFromPoint

func MBRFromPoint(p Point) MBR

MBRFromPoint creates a degenerate MBR containing only a single point.

func MBRFromTuple

func MBRFromTuple(t tuple.Tuple, numDimensions int) (MBR, error)

MBRFromTuple deserializes an MBR from a flat tuple.

func (MBR) ContainsPoint

func (m MBR) ContainsPoint(p Point) bool

ContainsPoint returns true if the MBR contains the given point.

func (MBR) NumDimensions

func (m MBR) NumDimensions() int

NumDimensions returns the number of dimensions.

func (MBR) Overlaps

func (m MBR) Overlaps(other MBR) bool

Overlaps returns true if this MBR overlaps with another. Returns false if the MBRs have different numbers of dimensions.

func (MBR) Union

func (m MBR) Union(other MBR) MBR

Union returns the smallest MBR containing both this and other. If the MBRs have different numbers of dimensions, returns self unchanged.

type MemoryLimitExceededError

type MemoryLimitExceededError struct {
	// Used is the running memory total (in bytes) at the point the budget
	// was breached — i.e. the would-be total INCLUDING the charge that
	// tripped it.
	Used int64
	// Limit is the configured statement-wide memory byte budget.
	Limit int64
}

MemoryLimitExceededError is returned when a statement's accounted in-memory buffering exceeds the statement-wide memory byte budget (RFC-130). It is a per-statement resource-limit error in the same family as the scan/byte/time limits — the relational layer maps it to SQLSTATE 54F01 (ErrCodeExecutionLimitReached), matching Java's reuse of that SQLSTATE for resource limits (there is no memory-specific SQLSTATE in Java either).

func (*MemoryLimitExceededError) Error

func (e *MemoryLimitExceededError) Error() string

type MetaDataError

type MetaDataError struct {
	Message string
}

MetaDataError is returned for metadata validation failures. Matches Java's com.apple.foundationdb.record.metadata.MetaDataException.

func (*MetaDataError) Error

func (e *MetaDataError) Error() string

type MetaDataEvolutionError

type MetaDataEvolutionError struct {
	Message string
}

MetaDataEvolutionError describes a schema evolution violation.

func (*MetaDataEvolutionError) Error

func (e *MetaDataEvolutionError) Error() string

type MetaDataEvolutionValidator

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

MetaDataEvolutionValidator validates that schema changes between an old and new RecordMetaData are safe. Prevents accidental data corruption from incompatible schema evolution.

Matches Java's com.apple.foundationdb.record.metadata.MetaDataEvolutionValidator.

func DefaultMetaDataEvolutionValidator

func DefaultMetaDataEvolutionValidator() *MetaDataEvolutionValidator

DefaultMetaDataEvolutionValidator returns the strictest validator (all options false). Matches Java's MetaDataEvolutionValidator.getDefaultInstance().

func (*MetaDataEvolutionValidator) Validate

func (v *MetaDataEvolutionValidator) Validate(oldMetaData, newMetaData *RecordMetaData) error

Validate checks that evolving from oldMetaData to newMetaData is safe. Returns nil if the evolution is valid, or an error describing the violation. Matches Java's MetaDataEvolutionValidator.validate().

type MetaDataEvolutionValidatorBuilder

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

MetaDataEvolutionValidatorBuilder builds a MetaDataEvolutionValidator with custom options.

func NewMetaDataEvolutionValidator

func NewMetaDataEvolutionValidator() *MetaDataEvolutionValidatorBuilder

NewMetaDataEvolutionValidator returns a builder for configuring the validator.

func (*MetaDataEvolutionValidatorBuilder) Build

func (*MetaDataEvolutionValidatorBuilder) SetAllowDeprecatedFieldRenames

func (b *MetaDataEvolutionValidatorBuilder) SetAllowDeprecatedFieldRenames(v bool) *MetaDataEvolutionValidatorBuilder

SetAllowDeprecatedFieldRenames permits renaming a field when the old or new field is deprecated (a narrower allowance than SetAllowFieldRenames). Matches Java's setAllowDeprecatedFieldRenames.

func (*MetaDataEvolutionValidatorBuilder) SetAllowFieldRenames

SetAllowFieldRenames permits any field to be renamed (same field number, new name) across a metadata evolution. Matches Java's setAllowFieldRenames.

func (*MetaDataEvolutionValidatorBuilder) SetAllowIndexRebuilds

func (*MetaDataEvolutionValidatorBuilder) SetAllowMissingFormerIndexNames

func (b *MetaDataEvolutionValidatorBuilder) SetAllowMissingFormerIndexNames(v bool) *MetaDataEvolutionValidatorBuilder

func (*MetaDataEvolutionValidatorBuilder) SetAllowNoSinceVersion

func (*MetaDataEvolutionValidatorBuilder) SetAllowNoVersionChange

func (*MetaDataEvolutionValidatorBuilder) SetAllowOlderFormerIndexAddedVersion

func (b *MetaDataEvolutionValidatorBuilder) SetAllowOlderFormerIndexAddedVersion(v bool) *MetaDataEvolutionValidatorBuilder

func (*MetaDataEvolutionValidatorBuilder) SetAllowUndeprecatingFields

SetAllowUndeprecatingFields permits a field that was deprecated to become non-deprecated. Matches Java's setAllowUndeprecatingFields.

func (*MetaDataEvolutionValidatorBuilder) SetAllowUnsplitToSplit

func (*MetaDataEvolutionValidatorBuilder) SetDisallowTypeRenames

type MetaDataVersionMustIncreaseError

type MetaDataVersionMustIncreaseError struct {
	OldVersion int32
	NewVersion int32
}

MetaDataVersionMustIncreaseError is returned by SaveRecordMetaData when the new metadata's version is not strictly greater than the stored one. This check is unconditional — it does not consult the evolution validator's allowNoVersionChange knob, exactly like Java, where saveAndSetCurrent throws before the validator ever runs. Matches Java's MetaDataException("meta-data version must increase") with log keys OLD / NEW.

func (*MetaDataVersionMustIncreaseError) Error

type MetaDataVersionStampStoreStateCache

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

MetaDataVersionStampStoreStateCache caches store state and validates entries using FDB's metadata version stamp (a system key that changes on every mutation). Matches Java's MetaDataVersionStampStoreStateCache.

func NewMetaDataVersionStampStoreStateCache

func NewMetaDataVersionStampStoreStateCache(opts ...MetaDataVersionStampStoreStateCacheOption) *MetaDataVersionStampStoreStateCache

NewMetaDataVersionStampStoreStateCache creates a new cache with optional configuration.

func (*MetaDataVersionStampStoreStateCache) Clear

Clear removes all cached entries.

func (*MetaDataVersionStampStoreStateCache) Get

Get retrieves store state, using the cache when the metadata version stamp matches. Matches Java's MetaDataVersionStampStoreStateCache.get().

type MetaDataVersionStampStoreStateCacheOption

type MetaDataVersionStampStoreStateCacheOption func(*MetaDataVersionStampStoreStateCache)

MetaDataVersionStampStoreStateCacheOption configures the cache.

func WithExpireAfterAccess

WithExpireAfterAccess sets the TTL for cache entries since last access. Default: 1 minute (matches Java's default).

func WithMaxSize

WithMaxSize sets the maximum number of entries in the cache. Default: 500 (matches Java's default).

type NestingKeyExpression

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

NestingKeyExpression navigates into a nested protobuf message and evaluates a child expression against it. Matches Java's NestingKeyExpression.

Example: Nest("flower", Field("type")) indexes the "type" field inside a nested "flower" message.

For repeated message fields, use NestFanOut to fan out over each sub-message.

func (*NestingKeyExpression) ColumnSize

func (n *NestingKeyExpression) ColumnSize() int

ColumnSize returns the child's column size — the parent message field doesn't contribute a tuple element.

func (*NestingKeyExpression) Evaluate

func (n *NestingKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate navigates into the parent message field and evaluates the child expression on the sub-message(s).

func (*NestingKeyExpression) FieldNames

func (n *NestingKeyExpression) FieldNames() []string

FieldNames returns the parent field name plus child field names.

func (*NestingKeyExpression) ToKeyExpression

func (n *NestingKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a NestingKeyExpression to proto. Matches Java's NestingKeyExpression.toKeyExpression().

type NoNextReason

type NoNextReason int

NoNextReason indicates why a cursor stopped producing records

const (
	// SourceExhausted means the cursor reached the end of its data
	SourceExhausted NoNextReason = iota
	// ReturnLimitReached means the cursor hit its row limit
	ReturnLimitReached
	// ByteLimitReached means the cursor hit its byte scan limit
	ByteLimitReached
	// TimeLimitReached means the cursor hit its time limit
	TimeLimitReached
	// ScanLimitReached means the cursor hit its key-value scan limit
	ScanLimitReached
)

func (NoNextReason) IsLimitReached

func (r NoNextReason) IsLimitReached() bool

IsLimitReached returns true if the cursor stopped due to any kind of limit

func (NoNextReason) IsOutOfBand

func (r NoNextReason) IsOutOfBand() bool

IsOutOfBand returns true if this reason represents an out-of-band completion (not solely dependent on the records returned)

func (NoNextReason) IsSourceExhausted

func (r NoNextReason) IsSourceExhausted() bool

IsSourceExhausted returns true if there is no more data available

type NodeKind

type NodeKind byte

NodeKind identifies a node as leaf or intermediate.

const (
	NodeKindLeaf         NodeKind = 0x00
	NodeKindIntermediate NodeKind = 0x01
)

type OnlineIndexer

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

OnlineIndexer builds indexes on existing data across multiple transactions. Each transaction processes a chunk of records, tracks progress via IndexingRangeSet, and the build resumes from where it left off if interrupted.

Supports four modes:

  • BY_RECORDS (default, single target): scans all records in primary key order
  • BY_INDEX (single target): scans an existing readable VALUE index to find records
  • MULTI_TARGET_BY_RECORDS: scans records once, builds multiple indexes simultaneously
  • MUTUAL_BY_RECORDS: concurrent multi-process building with fragment-based work division

Matches Java's OnlineIndexer with IndexingByRecords, IndexingByIndex, IndexingMultiTargetByRecords, and IndexingMutuallyByRecords.

func (*OnlineIndexer) BlockIndex

func (oi *OnlineIndexer) BlockIndex(ctx context.Context, blockID string, ttl time.Duration) error

BlockIndex sets the block flag on the indexing stamp for all target indexes. A blocked stamp prevents other indexers from continuing the build. blockID is an optional identifier for the block reason. ttl is the time-to-live for the block; zero means permanent. Matches Java's IndexingBase.performIndexingStampOperation(BLOCK, ...).

func (*OnlineIndexer) BuildIndex

func (oi *OnlineIndexer) BuildIndex(ctx context.Context) (int64, error)

BuildIndex runs the full index build: marks WRITE_ONLY, builds all records, then marks READABLE. Returns the number of records indexed. Matches Java's OnlineIndexer.buildIndex().

func (*OnlineIndexer) MarkReadableIfBuilt

func (oi *OnlineIndexer) MarkReadableIfBuilt(ctx context.Context) (bool, error)

MarkReadableIfBuilt checks each target index and marks it READABLE if fully built. Returns true if all target indexes are now READABLE. This is an idempotent operation — safe to call repeatedly. Matches Java's IndexingBase.markReadableIfBuilt().

func (*OnlineIndexer) QueryIndexingStamps

func (oi *OnlineIndexer) QueryIndexingStamps(ctx context.Context) (map[string]*gen.IndexBuildIndexingStamp, error)

QueryIndexingStamps returns the indexing stamps for all target indexes. Returns a map of index name → stamp. Nil stamps are returned as a NONE method stamp. Matches Java's IndexingBase.performIndexingStampOperation(QUERY, ...).

func (*OnlineIndexer) UnblockIndex

func (oi *OnlineIndexer) UnblockIndex(ctx context.Context, blockID string) error

UnblockIndex clears the block flag on the indexing stamp for all target indexes. If blockID is non-empty, only unblocks stamps with a matching blockID. Matches Java's IndexingBase.performIndexingStampOperation(UNBLOCK, ...).

type OnlineIndexerBuilder

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

OnlineIndexerBuilder constructs an OnlineIndexer.

func NewOnlineIndexerBuilder

func NewOnlineIndexerBuilder() *OnlineIndexerBuilder

NewOnlineIndexerBuilder creates a new builder.

func (*OnlineIndexerBuilder) AddTargetIndex

func (b *OnlineIndexerBuilder) AddTargetIndex(index *Index) *OnlineIndexerBuilder

AddTargetIndex adds a target index for multi-target building. Mutually exclusive with SetIndex. Matches Java's OnlineIndexer.Builder.addTargetIndex().

func (*OnlineIndexerBuilder) Build

func (b *OnlineIndexerBuilder) Build() (*OnlineIndexer, error)

Build creates the OnlineIndexer. Matches Java's OnlineIndexer.Builder.build(): validates, deduplicates, and sorts target indexes by name (the alphabetically-first index becomes the "primary" that drives range tracking).

func (*OnlineIndexerBuilder) SetAllowUniquePendingState

func (b *OnlineIndexerBuilder) SetAllowUniquePendingState(allow bool) *OnlineIndexerBuilder

SetAllowUniquePendingState opts in to leaving a unique index in READABLE_UNIQUE_PENDING when the build finds duplicates, instead of failing the build with a RecordIndexUniquenessViolationError.

Java OnlineIndexer.IndexingPolicy.Builder.allowUniquePendingState(boolean) (OnlineIndexer.java:1354). Defaults FALSE here as it does there, and even when set it is honoured only on a store at format version >= READABLE_UNIQUE_PENDING — see OnlineIndexer.shouldAllowUniquePendingState.

Java's javadoc carries the warning this option deserves: "This state might not be compatible with older code. Please verify that all instances are up-to-date before allowing it."

func (*OnlineIndexerBuilder) SetDatabase

SetDatabase sets the FDB database.

func (*OnlineIndexerBuilder) SetEnforcedPostTransactionDelay

func (b *OnlineIndexerBuilder) SetEnforcedPostTransactionDelay(ms int) *OnlineIndexerBuilder

SetEnforcedPostTransactionDelay sets a fixed delay in milliseconds applied after each build transaction. When positive, it is used INSTEAD of the records-per-second throttle. Default 0 disables it (records-per-second behaviour). Matches Java's OnlineIndexer.Builder.setEnforcedPostTransactionDelay().

func (*OnlineIndexerBuilder) SetFormatVersion

func (b *OnlineIndexerBuilder) SetFormatVersion(version int32) *OnlineIndexerBuilder

SetFormatVersion pins the format version the indexer opens its stores at, mirroring StoreBuilder.SetFormatVersion. Java gets this for free by reusing the caller's record-store builder (IndexingCommon.getRecordStoreBuilder).

func (*OnlineIndexerBuilder) SetIndex

func (b *OnlineIndexerBuilder) SetIndex(index *Index) *OnlineIndexerBuilder

SetIndex sets a single target index to build. Mutually exclusive with AddTargetIndex/SetTargetIndexes.

func (*OnlineIndexerBuilder) SetLeaseLengthMs

func (b *OnlineIndexerBuilder) SetLeaseLengthMs(ms int64) *OnlineIndexerBuilder

SetLeaseLengthMs sets the heartbeat lease duration in milliseconds. If a heartbeat is not updated within this duration, the process is presumed dead. Default is 30000 (30 seconds). Only relevant for non-mutual mode; in mutual mode heartbeats are written but not checked. Matches Java's OnlineIndexer.IndexingPolicy.setLeaseLengthMillis().

func (*OnlineIndexerBuilder) SetLimit

func (b *OnlineIndexerBuilder) SetLimit(limit int) *OnlineIndexerBuilder

SetLimit sets the maximum number of records to process per transaction.

func (*OnlineIndexerBuilder) SetLogger

func (b *OnlineIndexerBuilder) SetLogger(logger *slog.Logger) *OnlineIndexerBuilder

SetLogger sets the slog.Logger that receives the indexer's operational progress events. A nil logger (the default) falls back to slog.Default() at log time, mirroring the client's WithLogger convention (RFC-097).

func (*OnlineIndexerBuilder) SetMarkReadable

func (b *OnlineIndexerBuilder) SetMarkReadable(v bool) *OnlineIndexerBuilder

SetMarkReadable controls whether BuildIndex marks the index readable when the build completes (default true). With false, the build populates the index but leaves it WRITE_ONLY with its build stamp and progress counter intact — matching Java's OnlineIndexer.buildIndex(boolean markReadable).

func (*OnlineIndexerBuilder) SetMaxRetries

func (b *OnlineIndexerBuilder) SetMaxRetries(maxRetries int) *OnlineIndexerBuilder

SetMaxRetries sets the maximum number of retries per range on transient FDB errors. When a range build fails with a transient error, the indexer retries with a halved limit. Default is 0 (no retries — errors propagate immediately). Matches Java's OnlineIndexer.Builder.setMaxRetries() (default 100 in Java).

func (*OnlineIndexerBuilder) SetMetaData

SetMetaData sets the record metadata.

func (*OnlineIndexerBuilder) SetMutualIndexing

func (b *OnlineIndexerBuilder) SetMutualIndexing() *OnlineIndexerBuilder

SetMutualIndexing enables concurrent/mutual index building mode. Multiple indexer processes can run simultaneously, each building different fragments of the key space. Fragments are determined by FDB shard boundaries. Matches Java's OnlineIndexer.Builder.setMutualIndexing().

func (*OnlineIndexerBuilder) SetMutualIndexingBoundaries

func (b *OnlineIndexerBuilder) SetMutualIndexingBoundaries(boundaries [][]byte) *OnlineIndexerBuilder

SetMutualIndexingBoundaries enables mutual mode with pre-set fragment boundaries. Each boundary is a packed tuple representing a primary key at which to split the key space. If nil/empty, the entire key space is treated as a single fragment. Matches Java's OnlineIndexer.Builder.setMutualIndexingBoundaries().

func (*OnlineIndexerBuilder) SetPolicy

SetPolicy sets the indexing policy for stamp conflict resolution. Matches Java's OnlineIndexer.Builder.setIndexingPolicy().

func (*OnlineIndexerBuilder) SetProgressLogIntervalMillis

func (b *OnlineIndexerBuilder) SetProgressLogIntervalMillis(ms int64) *OnlineIndexerBuilder

SetProgressLogIntervalMillis sets the throttle for the per-range "Indexer: Built Range" progress log. Matches Java OnlineIndexer.Builder.setProgressLogIntervalMillis():

  • a negative value (the default, -1) disables progress logging;
  • 0 logs after every range;
  • a positive value logs at most once per that many milliseconds.

Events go to the logger set via SetLogger (or slog.Default()) at INFO.

func (*OnlineIndexerBuilder) SetRecordTypes

func (b *OnlineIndexerBuilder) SetRecordTypes(types ...string) *OnlineIndexerBuilder

SetRecordTypes sets which record types to index. If empty, indexes all types that have this index defined. Not allowed with multi-target building.

func (*OnlineIndexerBuilder) SetRecordsPerSecond

func (b *OnlineIndexerBuilder) SetRecordsPerSecond(rps int) *OnlineIndexerBuilder

SetRecordsPerSecond sets the inter-transaction rate limit. The indexer will sleep between transactions to maintain approximately this rate. Default is 10,000 records/second (matching Java). Set to 0 for unlimited. Matches Java's OnlineIndexer.Builder.setRecordsPerSecond().

func (*OnlineIndexerBuilder) SetSourceIndex

func (b *OnlineIndexerBuilder) SetSourceIndex(index *Index) *OnlineIndexerBuilder

SetSourceIndex sets the source index for the BY_INDEX strategy. The source index must be a READABLE VALUE index whose root expression does not create duplicates. Both source and target must apply to exactly one record type, and the target's type must be a superset of the source's. Not allowed with multi-target building. Matches Java's OnlineIndexer.Builder.setSourceIndex().

func (*OnlineIndexerBuilder) SetSubspace

SetSubspace sets the store subspace.

func (*OnlineIndexerBuilder) SetTargetIndexes

func (b *OnlineIndexerBuilder) SetTargetIndexes(indexes []*Index) *OnlineIndexerBuilder

SetTargetIndexes sets multiple target indexes for multi-target building. Mutually exclusive with SetIndex. Matches Java's OnlineIndexer.Builder.setTargetIndexes().

func (*OnlineIndexerBuilder) SetTimeLimit

SetTimeLimit sets the overall time limit for the entire build. If the build exceeds this duration, BuildIndex returns a TimeLimitExceededError after the current range completes. Zero means unlimited (default). Matches Java's OnlineIndexer.Builder.setTimeLimitMilliseconds().

type OrderDirection

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

OrderDirection specifies how tuple values are encoded for ordered storage. Matches Java's TupleOrdering.Direction enum.

type OrdinaryIndexScanFunc

type OrdinaryIndexScanFunc func(scanRange TupleRange, props ScanProperties) RecordCursor[*IndexEntry]

OrdinaryIndexScanFunc scans the ORDINARY (value) subspace of an index. The repair is expressed against this function rather than against a store because its two callers reach that subspace differently: the query executor goes through FDBRecordStore.ScanIndexByType, while the index maintainer already holds a standard-maintainer handle on the same subspace and has no store.

func OrdinaryIndexScanner

func OrdinaryIndexScanner(store *FDBRecordStore, index *Index) OrdinaryIndexScanFunc

OrdinaryIndexScanner returns the scan function the repair needs for an index reached through a record store — the query executor's route.

type PartlyBuiltError

type PartlyBuiltError struct {
	IndexName     string
	SavedStamp    string // string representation of the saved stamp
	ExpectedStamp string // string representation of the expected stamp
	Message       string
}

PartlyBuiltError is returned when an OnlineIndexer encounters an index that was partly built by another method or is blocked from continuing. Matches Java's com.apple.foundationdb.record.provider.foundationdb.IndexingBase.PartlyBuiltException.

func (*PartlyBuiltError) Error

func (e *PartlyBuiltError) Error() string

type PassThroughRecordStoreStateCache

type PassThroughRecordStoreStateCache struct{}

PassThroughRecordStoreStateCache is a no-op cache that always loads from FDB. This is the default behavior. Matches Java's PassThroughRecordStoreStateCache.

func (*PassThroughRecordStoreStateCache) Clear

Clear is a no-op.

func (*PassThroughRecordStoreStateCache) Get

Get always loads fresh state from FDB. Skips the metadata version stamp read since PassThrough has no cache to validate against.

type Point

type Point struct {
	Coordinates tuple.Tuple
}

Point represents an N-dimensional point with int64 coordinates. Null coordinates are represented as nil in the tuple.

func (Point) Coordinate

func (p Point) Coordinate(dim int) int64

Coordinate returns the coordinate at the given dimension index, or MinInt64 if nil.

func (Point) NumDimensions

func (p Point) NumDimensions() int

NumDimensions returns the number of dimensions.

type PostCommitFunc

type PostCommitFunc func()

PostCommitFunc is a callback that runs after a successful commit. Matches Java's PostCommit interface.

type RTree

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

RTree is a Hilbert R-tree backed by FDB. Matches Java's com.apple.foundationdb.async.rtree.RTree.

func NewRTree

func NewRTree(storage *rtreeStorage, config RTreeConfig) (*RTree, error)

NewRTree creates a new R-tree. Returns an error if config is invalid.

func (*RTree) Clear

func (rt *RTree) Clear(tx fdb.WritableTransaction) error

Clear removes all data from the R-tree.

func (*RTree) Delete

func (rt *RTree) Delete(tx fdb.WritableTransaction, point Point, keySuffix tuple.Tuple) error

Delete removes an item from the R-tree. Matches Java's RTree.delete().

func (*RTree) InsertOrUpdate

func (rt *RTree) InsertOrUpdate(tx fdb.WritableTransaction, point Point, keySuffix tuple.Tuple, value tuple.Tuple) error

InsertOrUpdate inserts a new item or updates an existing one. Matches Java's RTree.insertOrUpdate().

func (*RTree) Scan

func (rt *RTree) Scan(tx fdb.ReadTransaction, lastHV *big.Int, lastKey tuple.Tuple, mbrPredicate func(MBR) bool) ([]ItemSlot, error)

Scan returns all items matching the MBR predicate, starting after (lastHV, lastKey). Items are returned in Hilbert value order. Convenience wrapper around ScanIterator.

func (*RTree) ScanIterator

func (rt *RTree) ScanIterator(tx fdb.ReadTransaction, lastHV *big.Int, lastKey tuple.Tuple, mbrPredicate func(MBR) bool) *RTreeIterator

ScanIterator returns a lazy iterator over items matching the MBR predicate, starting after (lastHV, lastKey). Items are yielded one at a time in Hilbert value order. Only fetches leaf nodes from FDB on demand.

type RTreeConfig

type RTreeConfig struct {
	MinM               int  // Min slots per non-root node (default 16)
	MaxM               int  // Max slots per node (default 32)
	SplitS             int  // Siblings involved in split/fuse (default 2)
	StoreHilbertValues bool // Store HV in leaf slots (default true)
	NumDimensions      int  // Number of spatial dimensions
}

RTreeConfig configures the R-tree behavior. Matches Java's RTree.Config.

func DefaultRTreeConfig

func DefaultRTreeConfig(numDimensions int) RTreeConfig

DefaultRTreeConfig returns the default R-tree configuration.

type RTreeIterator

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

RTreeIterator lazily walks the R-tree depth-first in Hilbert order, fetching one leaf node at a time from FDB.

func (*RTreeIterator) Next

func (it *RTreeIterator) Next() (ItemSlot, bool, error)

Next returns the next item from the iterator. Returns (item, true, nil) on success, (zero, false, nil) when exhausted, or (zero, false, err) on error.

type RangeSet

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

RangeSet tracks ranges of completed work in an FDB subspace. Wire-compatible with Java's com.apple.foundationdb.async.RangeSet.

func NewRangeSet

func NewRangeSet(ss subspace.Subspace) *RangeSet

NewRangeSet creates a RangeSet that stores data in the given subspace.

func (*RangeSet) Clear

func (rs *RangeSet) Clear(tr fdb.WritableTransaction)

Clear removes all ranges from the set.

func (*RangeSet) Contains

func (rs *RangeSet) Contains(tr fdb.WritableTransaction, key []byte) (bool, error)

Contains checks if key is in any range in the set. Adds a read conflict on the key for proper isolation.

func (*RangeSet) InsertRange

func (rs *RangeSet) InsertRange(tr fdb.WritableTransaction, begin, end []byte, requireEmpty bool) (bool, error)

InsertRange adds range [begin, end) to the set. If begin is nil, uses FIRST_KEY. If end is nil, uses FINAL_KEY. If requireEmpty is true, only inserts if the range is currently empty (atomic test-and-set). Returns true if the database was modified.

func (*RangeSet) IsEmpty

func (rs *RangeSet) IsEmpty(tr fdb.WritableTransaction) (bool, error)

IsEmpty returns true if no ranges have been inserted into this set.

func (*RangeSet) MissingRanges

func (rs *RangeSet) MissingRanges(tr fdb.WritableTransaction, begin, end []byte, limit int) ([]RangeSetRange, error)

MissingRanges returns the gaps (ranges not in the set) within [begin, end). If begin is nil, uses FIRST_KEY. If end is nil, uses FINAL_KEY. If limit <= 0, returns all gaps.

type RangeSetEmptyKeyError

type RangeSetEmptyKeyError struct{}

RangeSetEmptyKeyError is returned when a RangeSet operation receives an empty key.

func (*RangeSetEmptyKeyError) Error

func (e *RangeSetEmptyKeyError) Error() string

type RangeSetInvertedRangeError

type RangeSetInvertedRangeError struct {
	Begin []byte
	End   []byte
}

RangeSetInvertedRangeError is returned when begin > end in a range operation.

func (*RangeSetInvertedRangeError) Error

type RangeSetKeyTooLargeError

type RangeSetKeyTooLargeError struct {
	Key []byte
}

RangeSetKeyTooLargeError is returned when a key is >= \xff.

func (*RangeSetKeyTooLargeError) Error

func (e *RangeSetKeyTooLargeError) Error() string

type RangeSetRange

type RangeSetRange struct {
	Begin []byte
	End   []byte
}

RangeSetRange represents a gap (missing range) as [Begin, End).

type RankQuerier

type RankQuerier interface {
	RankForScore(score tuple.Tuple, nullsAreLow bool) (*int64, error)
	ScoreForRank(rank tuple.Tuple) (tuple.Tuple, error)
}

RankQuerier is the public interface for rank-based queries on a RANK index maintainer.

type RecordAlreadyExistsError

type RecordAlreadyExistsError struct {
	Message    string
	PrimaryKey any // tuple.Tuple, but using any to avoid import cycle
}

RecordAlreadyExistsError is returned when attempting to insert a record that already exists. Includes structured context matching Java's RecordAlreadyExistsException.

Java equivalent: com.apple.foundationdb.record.provider.foundationdb.RecordAlreadyExistsException Location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/RecordAlreadyExistsException.java Java includes: LogMessageKeys.PRIMARY_KEY

func (*RecordAlreadyExistsError) Error

func (e *RecordAlreadyExistsError) Error() string

type RecordContextConfig

type RecordContextConfig struct {
	// TransactionTimeout is the timeout for the FDB transaction.
	// Zero means use FDB's default.
	TransactionTimeout time.Duration

	// Priority sets the transaction priority.
	Priority TransactionPriority

	// TransactionID is an optional identifier for tracing/debugging.
	TransactionID string

	// TransactionSizeWarnBytes triggers a warning when the approximate
	// transaction size exceeds this threshold. Zero disables the check.
	// FDB's hard limit is 10MB; a typical warning threshold is 8MB.
	TransactionSizeWarnBytes int64

	// TransactionSizeErrorBytes causes operations to return
	// TransactionSizeExceededError when the approximate transaction
	// size exceeds this threshold. Zero disables the check.
	// Setting this below FDB's 10MB limit lets callers commit early.
	TransactionSizeErrorBytes int64

	// Tags are transaction tags used for tag-based throttling, applied to every
	// transaction this config creates. Matches Java's FDBRecordContextConfig
	// tags (FDBRecordContextConfig.java:60), which are applied one at a time via
	// transaction.options().setTag (FDBRecordContext.java:205-207) — the plain
	// TAG option, never AUTO_THROTTLE_TAG.
	//
	// Java models this as a Set<String>, so duplicates collapse and iteration
	// order is arbitrary. ValidateTags dedups; SetTags sorts, because the commit
	// request's TagSet is a length-prefixed concatenation in insertion order and
	// an arbitrary order would make the commit bytes vary run to run for the
	// same config. Order is not a wire contract on either side.
	Tags []string

	// Timer is the StoreTimer every context this config creates records into.
	// Matches Java's FDBRecordContextConfig.timer (FDBRecordContextConfig.java:42,
	// Builder.setTimer :357-368), which likewise defaults to null/nil.
	//
	// Nil does NOT mean uninstrumented: it means "inherit the database's timer",
	// which is the same relationship Java has between FDBDatabaseRunner.setTimer and
	// the context config it delegates to (FDBDatabaseRunner.java:105-119). Set it only
	// to give one runner's transactions a timer separate from the rest of the process
	// — an index build measured on its own, say.
	Timer *StoreTimer
}

RecordContextConfig holds configuration for creating FDBRecordContext instances. Matches Java's FDBRecordContextConfig.

func (*RecordContextConfig) SetTags

func (c *RecordContextConfig) SetTags(tags []string) error

SetTags validates and stores the tag set, returning an error rather than panicking where Java throws. Tags are sorted so the resulting commit bytes are deterministic for a given config.

type RecordCoreStorageError

type RecordCoreStorageError struct {
	Message    string      // Java's RecordCoreStorageException message
	IndexName  string      // LogMessageKeys.INDEX_NAME
	PrimaryKey tuple.Tuple // LogMessageKeys.PRIMARY_KEY (nil if the entry yielded no PK)
	IndexKey   tuple.Tuple // LogMessageKeys.INDEX_KEY
}

RecordCoreStorageError signals storage-level corruption detected while resolving an index entry to its base record. Matches Java's com.apple.foundationdb.record.RecordCoreStorageException.

When a record is fetched from an index entry with IndexOrphanBehavior.ERROR (FDBRecordStoreBase.loadIndexEntryRecord) and the referenced record is missing, Java throws this exception rather than dropping the row, attaching LogMessageKeys.INDEX_NAME / PRIMARY_KEY / INDEX_KEY. An index entry with no base record means the index and the records disagree — index corruption, an out-of-band delete, or a maintainer bug — and both query execution (RecordQueryIndexPlan → FetchIndexRecords.PRIMARY_KEY) and the index-from-index rebuild (scanIndexRecords defaults to ERROR) use this loud path. Silently skipping would convert detectable corruption into quietly-fewer rows.

func (*RecordCoreStorageError) Error

func (e *RecordCoreStorageError) Error() string

type RecordCountKeySizeMismatchError

type RecordCountKeySizeMismatchError struct {
	KeyColumnSize int
	ValueSize     int
}

RecordCountKeySizeMismatchError is Java's recordCoreException("key and value are not the same size") thrown by getSnapshotRecordCount (FDBRecordStore.java:2295-2297) when the evaluated value has a different number of columns than the key expression it is supposed to be a value of.

It is a typed error rather than a plain message because the two sizes are the whole diagnosis: the caller either passed a value for a different count key or truncated one, and the alternative to failing is answering from a slot nothing ever writes — a confident, wrong 0.

func (*RecordCountKeySizeMismatchError) Error

type RecordCursor

type RecordCursor[T any] interface {
	// OnNext asynchronously returns the next result from this cursor
	OnNext(ctx context.Context) (RecordCursorResult[T], error)

	// Close releases any resources held by this cursor
	Close() error

	// IsClosed returns true if the cursor has been closed
	IsClosed() bool
}

RecordCursor is a generic async iterator over records

func Chained

func Chained[T any](
	generator func(prev *T) (*T, error),
	encode func(T) []byte,
	decode func([]byte) (T, bool),
	continuation []byte,
) RecordCursor[T]

Chained creates a cursor that produces values from a generator function. generator receives the previous value (nil for the first call) and returns the next value or nil to signal exhaustion. encode/decode serialize/deserialize values for continuations. Matches Java's ChainedCursor.

func ConcatCursors

func ConcatCursors[T any](first, second CursorFactory[T], continuation []byte) RecordCursor[T]

ConcatCursors concatenates two cursor factories: results from first, then second. Continuation tokens are wire-compatible with Java's ConcatCursor.

func Dedup

func Dedup[T any](
	innerFactory CursorFactory[T],
	equal func(a, b T) bool,
	pack func(T) []byte,
	unpack func([]byte) (T, bool),
	continuation []byte,
) RecordCursor[T]

Dedup creates a cursor that removes adjacent duplicate elements. equal compares two elements; pack/unpack serialize the last value for continuations. If pack is nil, continuation will not include lastValue. Matches Java's DedupCursor.

func Empty

func Empty[T any]() RecordCursor[T]

Empty returns a cursor that produces no results (source exhausted immediately).

func Fallback

func Fallback[T any](
	inner RecordCursor[T],
	fallbackFactory func(lastResult *RecordCursorResult[T]) RecordCursor[T],
) RecordCursor[T]

Fallback creates a cursor that falls back to an alternative on error. fallbackFactory receives the last successful result (nil if the inner cursor failed on its first call) and should return a cursor that resumes from there. Matches Java's FallbackCursor — one-shot fallback (fails permanently if the fallback cursor also errors).

func FlatMapPipelined

func FlatMapPipelined[T, V any](
	outerFactory CursorFactory[T],
	innerFactory func(T, []byte) RecordCursor[V],
	continuation []byte,
	pipelineSize int,
) RecordCursor[V]

FlatMapPipelined creates a cursor that flat-maps an outer cursor through an inner cursor factory. Matches Java's RecordCursor.flatMapPipelined().

Parameters:

  • outerFactory: creates the outer cursor from a continuation
  • innerFactory: given an outer value and optional inner continuation, creates an inner cursor
  • continuation: serialized FlatMapContinuation for resumption (nil to start fresh)
  • pipelineSize: accepted for API compat (Go processes sequentially)

func FlatMapPipelinedWithCheck

func FlatMapPipelinedWithCheck[T, V any](
	outerFactory CursorFactory[T],
	innerFactory func(T, []byte) RecordCursor[V],
	checkValueFunc func(T) []byte,
	continuation []byte,
	pipelineSize int,
) RecordCursor[V]

FlatMapPipelinedWithCheck is like FlatMapPipelined but with an optional check value function. The check value validates that the outer record hasn't changed between transactions when resuming from a continuation mid-inner-cursor.

func FromList

func FromList[T any](items []T) RecordCursor[T]

FromList creates a cursor from a slice. Matches Java's RecordCursor.fromList().

func FromListWithContinuation

func FromListWithContinuation[T any](items []T, continuation []byte) RecordCursor[T]

FromListWithContinuation creates a cursor from a slice, starting from a continuation. Matches Java's RecordCursor.fromList(list, continuation). Continuation format: 4-byte big-endian position (nil/empty = start from beginning). Java uses ByteBuffer.wrap(continuation).getInt() which reads first 4 bytes and throws BufferUnderflowException for continuations shorter than 4 bytes. Go returns an error from OnNext for invalid continuation lengths to match Java's fail-fast behavior.

func InstrumentIndexScanCursor

func InstrumentIndexScanCursor(timer *StoreTimer, inner RecordCursor[*IndexEntry]) RecordCursor[*IndexEntry]

InstrumentIndexScanCursor times each entry an index-scan cursor produces into EventScanIndex. It is the exported form of the wrapper the store's own scan methods apply, for the one caller that cannot use them: the query executor builds its index cursor from the maintainer directly (it needs the per-range factory that FDBRecordStore.ScanIndex does not expose), so without this the engine's primary index-scan path produces no scan event at all.

Decorating the maintainer instead would be the tidier-looking fix and is wrong: IndexMaintainer implementations are type-asserted for capability interfaces (RankQuerier among them), and a struct decorator silently fails those assertions.

A nil timer returns the cursor unchanged, so an uninstrumented store pays nothing per entry.

func Intersection

func Intersection[T any](
	cursors []RecordCursor[T],
	compKeyFunc ComparisonKeyFunc[T],
	reverse bool,
) RecordCursor[T]

Intersection creates a merge-intersection cursor that returns only elements present in ALL child cursors (by comparison key). All cursors must be ordered by the same key. Returns the element from the first cursor when all match. Matches Java's IntersectionCursor.create().

func IntersectionMulti

func IntersectionMulti[T any](
	cursors []RecordCursor[T],
	compKeyFunc ComparisonKeyFunc[T],
	reverse bool,
) RecordCursor[[]T]

IntersectionMulti creates a merge-intersection cursor that returns, for each set of elements present in ALL child cursors (by comparison key), the list of those elements (index i is the element from child i). All cursors must be ordered by the same key. Matches Java's IntersectionMultiCursor.create().

func IntersectionMultiResume

func IntersectionMultiResume[T any](
	cursors []RecordCursor[T],
	compKeyFunc ComparisonKeyFunc[T],
	reverse bool,
	resume []IntersectionChildResume,
) RecordCursor[[]T]

IntersectionMultiResume is IntersectionMulti with per-child resume state (see IntersectionResume). resume may be nil (all children fresh).

func IntersectionResume

func IntersectionResume[T any](
	cursors []RecordCursor[T],
	compKeyFunc ComparisonKeyFunc[T],
	reverse bool,
	resume []IntersectionChildResume,
) RecordCursor[T]

IntersectionResume is Intersection with per-child resume state from DecodeIntersectionContinuation. Each child's cached continuation is seeded from resume[i] (START → fresh, MID → its bytes, END → end), so a resumed child re-encodes correctly on the next checkpoint (RFC-071). resume may be nil (all children fresh, == Intersection).

func LimitRowsCursor

func LimitRowsCursor[T any](cursor RecordCursor[T], n int) RecordCursor[T]

LimitRowsCursor wraps a cursor and limits to at most n elements. Matches Java's RecordCursor.limitRowsTo() exactly: 0 (and MaxInt) mean UNLIMITED — the cursor is returned unchanged — and a negative limit is an error (Java throws RecordCoreException("Invalid row limit"); Go's no-panic rule surfaces it as an error cursor). The previous n<=0→Empty reading silently inverted Java's 0-is-unlimited convention.

func MapCursor

func MapCursor[T, R any](cursor RecordCursor[T], fn func(T) R) RecordCursor[R]

MapCursor creates a cursor that transforms each value using the given function. Continuations from the inner cursor are passed through transparently.

func MapErrCursor

func MapErrCursor[T, R any](cursor RecordCursor[T], fn func(T) (R, error)) RecordCursor[R]

MapErrCursor creates a cursor that transforms each value using a function that can return an error. If the transform function returns an error, iteration stops with that error. Continuations from the inner cursor are passed through. Matches Java's MapResultCursor with checked exceptions.

func NewAutoContinuingCursor

func NewAutoContinuingCursor[T any](
	runner *FDBDatabaseRunner,
	generator func(*FDBRecordContext, []byte) RecordCursor[T],
	maxRetries int,
) RecordCursor[T]

NewAutoContinuingCursor creates a cursor that automatically creates new transactions when the inner cursor stops before exhaustion (e.g., time limit, scan limit). Matches Java's AutoContinuingCursor.

Parameters:

  • runner: provides transaction creation and context configuration
  • generator: creates a cursor within a transaction context from a continuation
  • maxRetries: maximum retries on transient FDB errors (0 = no retries)

func OrElse

func OrElse[T any](primary RecordCursor[T], alternative func() RecordCursor[T]) RecordCursor[T]

OrElse returns the primary cursor if it has results, otherwise falls back to the alternative cursor. Matches Java's RecordCursor.orElse().

func OrElseWithContinuation

func OrElseWithContinuation[T any](
	primaryFactory CursorFactory[T],
	alternativeFactory CursorFactory[T],
	continuation []byte,
) RecordCursor[T]

OrElseWithContinuation creates an OrElse cursor with continuation support. Matches Java's OrElseCursor with OrElseContinuation proto serialization. Both primary and alternative are cursor factories that accept continuation bytes for cross-transaction resume.

func OuterMergeMultiResume

func OuterMergeMultiResume[T any](
	cursors []RecordCursor[T],
	compKeyFunc ComparisonKeyFunc[T],
	reverse bool,
	resume []IntersectionChildResume,
	driving int,
	absent func(childIndex int) T,
) RecordCursor[[]T]

OuterMergeMultiResume creates the driven outer merge. driving indexes the stream that decides the group set; absent supplies a filler value for a non-driving stream that has no row for the current key.

Every cursor must be ordered by the same comparison key, ascending unless reverse. resume carries per-child positions exactly as IntersectionMultiResume does.

func SkipCursor

func SkipCursor[T any](cursor RecordCursor[T], n int) RecordCursor[T]

SkipCursor wraps a cursor and skips the first n elements. Matches Java's RecordCursor.skip().

func SkipThenLimit

func SkipThenLimit[T any](cursor RecordCursor[T], skip, limit int) RecordCursor[T]

SkipThenLimit is a convenience that skips n elements then limits to m. Matches Java's RecordCursor.skipThenLimit().

func Stopped

func Stopped[T any](reason NoNextReason, continuation RecordCursorContinuation) RecordCursor[T]

Stopped returns a cursor that produces no value and reports the given NoNextReason and continuation. reason must be a genuine stop reason and, per RecordCursor's contract (RecordCursor.java:212-215), anything other than SourceExhausted must carry a NON-end continuation so the caller can resume.

The precondition is checked HERE and not left to NewResultNoNext at pull time. Java validates in RecordCursorResult.withoutNextValue (RecordCursorResult.java:252-258), where deciding the stop and reporting it are the same instant. A cursor splits those apart: a violation built here would otherwise surface as a panic from whichever parent happened to pull it, naming a stack that does not contain the mistake. A misbuilt stop is a programming error either way — this only decides whether the failure names its author.

type RecordCursorContinuation

type RecordCursorContinuation interface {
	// ToBytes serializes this continuation to a byte array.
	// Returns (nil, nil) if this is an end continuation.
	ToBytes() ([]byte, error)

	// IsEnd returns true if this represents the end of iteration
	IsEnd() bool
}

RecordCursorContinuation represents the position of a cursor for resumption

type RecordCursorResult

type RecordCursorResult[T any] struct {
	// contains filtered or unexported fields
}

RecordCursorResult represents the result of a cursor's OnNext() call.

Invariants (matching Java's RecordCursorResult):

  • A result WITH a value (HasNext=true) must NOT have an EndContinuation
  • A result with SourceExhausted MUST have an EndContinuation
  • A result with any other NoNextReason must NOT have an EndContinuation

The value is held BY VALUE, not behind a pointer, and hasNext is what distinguishes "has a value" — the pointer carried no information the flag does not. Boxing it cost one heap allocation per emitted row PER CURSOR LEVEL, and a plan is a stack of cursors: on a 20k-row wide scan whose plan is a projection over a scan, the boxes were the largest single allocator in the row path. GetValue still refuses to answer without a value, so the zero T a no-next result now carries is unreachable.

func MapResult

func MapResult[T, R any](result RecordCursorResult[T], fn func(T) R) RecordCursorResult[R]

MapResult transforms a result's value using the given function. If the result has no value (HasNext() is false), returns a no-next result with the same reason and continuation. Go generics require this as a standalone function rather than a method. Matches Java's RecordCursorResult.map().

func NewResultNoNext

func NewResultNoNext[T any](reason NoNextReason, continuation RecordCursorContinuation) RecordCursorResult[T]

NewResultNoNext creates a result indicating no more records. Enforces invariants matching Java's RecordCursorResult.withoutNextValue():

  • EndContinuation is only valid with SourceExhausted
  • SourceExhausted requires an EndContinuation

func NewResultWithValue

func NewResultWithValue[T any](value T, continuation RecordCursorContinuation) RecordCursorResult[T]

NewResultWithValue creates a result with a value. Panics if continuation is an EndContinuation OR nil — a value result must always carry a RESUMABLE continuation (Java's RecordCursorResult.withNextValue has no null arm). A nil here would make every parent snapshot the child as START and silently replay the emitted rows on resume.

func (RecordCursorResult[T]) GetContinuation

func (r RecordCursorResult[T]) GetContinuation() RecordCursorContinuation

GetContinuation returns the continuation for resuming the cursor

func (RecordCursorResult[T]) GetNoNextReason

func (r RecordCursorResult[T]) GetNoNextReason() NoNextReason

GetNoNextReason returns the reason why there's no next record (valid when HasNext is false)

func (RecordCursorResult[T]) GetValue

func (r RecordCursorResult[T]) GetValue() T

GetValue returns the value. Panics if HasNext() is false — callers must check HasNext() first. This matches Java's behavior of throwing IllegalResultValueAccessException.

func (RecordCursorResult[T]) HasNext

func (r RecordCursorResult[T]) HasNext() bool

HasNext returns true if this result contains a value

func (RecordCursorResult[T]) HasStoppedBeforeEnd

func (r RecordCursorResult[T]) HasStoppedBeforeEnd() bool

HasStoppedBeforeEnd returns true if the cursor stopped before exhausting all records. This means the cursor can be resumed with the continuation to get more results. Matches Java's RecordCursorResult.hasStoppedBeforeEnd() which checks the continuation, not the reason — with the EndContinuation↔SourceExhausted invariant, these are equivalent.

func (RecordCursorResult[T]) WithContinuation

func (r RecordCursorResult[T]) WithContinuation(continuation RecordCursorContinuation) RecordCursorResult[T]

WithContinuation returns a copy of this result with a different continuation. Matches Java's RecordCursorResult.withContinuation().

type RecordDeserializationError

type RecordDeserializationError struct {
	PrimaryKey any // tuple.Tuple, but using any to avoid import cycle
	Cause      error
}

RecordDeserializationError is returned when a record fails to deserialize (unmarshal) from protobuf. Matches Java's com.apple.foundationdb.record.provider.foundationdb.RecordDeserializationException.

func (*RecordDeserializationError) Error

func (*RecordDeserializationError) Unwrap

func (e *RecordDeserializationError) Unwrap() error

type RecordDoesNotExistError

type RecordDoesNotExistError struct {
	Message    string
	PrimaryKey any // tuple.Tuple, but using any to avoid import cycle
}

RecordDoesNotExistError is returned when attempting to update a record that does not exist. Includes structured context matching Java's RecordDoesNotExistException.

Java equivalent: com.apple.foundationdb.record.provider.foundationdb.RecordDoesNotExistException Location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/RecordDoesNotExistException.java Java includes: LogMessageKeys.PRIMARY_KEY

func (*RecordDoesNotExistError) Error

func (e *RecordDoesNotExistError) Error() string

type RecordExistenceCheck

type RecordExistenceCheck int

RecordExistenceCheck controls record existence validation during save operations. Matches Java's FDBRecordStoreBase.RecordExistenceCheck enum.

Java Reference: com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase.RecordExistenceCheck Location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java:394

const (
	// RecordExistenceCheckNone performs no existence validation.
	// This is the default behavior for SaveRecord.
	//
	// Java equivalent: NONE
	RecordExistenceCheckNone RecordExistenceCheck = iota

	// RecordExistenceCheckErrorIfExists throws an error if the record already exists.
	// This corresponds to InsertRecord behavior.
	//
	// Returns: RecordAlreadyExistsError if record exists
	// Java equivalent: ERROR_IF_EXISTS
	RecordExistenceCheckErrorIfExists

	// RecordExistenceCheckErrorIfNotExists throws an error if the record does not exist.
	// Use this for update-only operations where the record must pre-exist.
	//
	// Returns: RecordDoesNotExistError if record doesn't exist
	// Java equivalent: ERROR_IF_NOT_EXISTS
	RecordExistenceCheckErrorIfNotExists

	// RecordExistenceCheckErrorIfTypeChanged throws an error if an existing record has a different type.
	// Use this to prevent accidentally overwriting a record of a different type with the same primary key.
	//
	// Returns: RecordTypeChangedError if existing record has different type
	// Java equivalent: ERROR_IF_RECORD_TYPE_CHANGED
	RecordExistenceCheckErrorIfTypeChanged

	// RecordExistenceCheckErrorIfNotExistsOrTypeChanged combines ERROR_IF_NOT_EXISTS and ERROR_IF_RECORD_TYPE_CHANGED.
	// This corresponds to UpdateRecord behavior - the record must exist and must have the same type.
	//
	// Returns: RecordDoesNotExistError if record doesn't exist
	// Returns: RecordTypeChangedError if existing record has different type
	// Java equivalent: ERROR_IF_NOT_EXISTS_OR_RECORD_TYPE_CHANGED
	RecordExistenceCheckErrorIfNotExistsOrTypeChanged
)

func (RecordExistenceCheck) ErrorIfExists

func (c RecordExistenceCheck) ErrorIfExists() bool

ErrorIfExists returns true if this check should error when a record already exists.

Java equivalent: RecordExistenceCheck.errorIfExists()

func (RecordExistenceCheck) ErrorIfNotExists

func (c RecordExistenceCheck) ErrorIfNotExists() bool

ErrorIfNotExists returns true if this check should error when a record does not exist.

Java equivalent: RecordExistenceCheck.errorIfNotExists()

func (RecordExistenceCheck) ErrorIfTypeChanged

func (c RecordExistenceCheck) ErrorIfTypeChanged() bool

ErrorIfTypeChanged returns true if this check should error when a record's type changes.

Java equivalent: RecordExistenceCheck.errorIfTypeChanged()

func (RecordExistenceCheck) String

func (c RecordExistenceCheck) String() string

String returns a human-readable representation of the existence check.

type RecordIndexUniquenessViolationError

type RecordIndexUniquenessViolationError struct {
	IndexName   string
	IndexKey    tuple.Tuple
	PrimaryKey  tuple.Tuple
	ExistingKey tuple.Tuple
}

RecordIndexUniquenessViolationError indicates a unique index constraint violation. Matches Java's RecordIndexUniquenessViolation.

func (*RecordIndexUniquenessViolationError) Error

type RecordMetaData

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

RecordMetaData describes the schema for records stored in a record store. This is a simplified version for our MVP - just enough to define record types and their primary keys.

func RecordMetaDataFromProto

func RecordMetaDataFromProto(md *gen.MetaData) (*RecordMetaData, error)

RecordMetaDataFromProto deserializes a MetaData proto back into a RecordMetaData. Matches Java's RecordMetaDataBuilder.loadFromProto().

func (*RecordMetaData) AmbiguousDeclaredNames

func (m *RecordMetaData) AmbiguousDeclaredNames() ([]string, bool)

AmbiguousDeclaredNames reports a pair of declared record types whose names collide across the SQL and storage namespaces, in USER identifiers, or ok=false when none collide.

Descriptor names are ESCAPED (protoname.ToProtoBufCompliantName) and the escaping is NOT injective across the two namespaces: MY$TABLE is stored as MY__1TABLE, while a table whose SQL name IS MY__1TABLE is stored as MY__01TABLE. Any lookup that tries a name as given and then its escaped form resolves the first of those to the wrong entry, and no ordering fixes it — either order prices one of the two wrong.

It lives here, beside DeclaresSyntheticRecordTypes, because it is the same KIND of fact: a property of the declarations that decides whether a caller computing over "all record types" can trust the answer. Both the statistics reader and both collection entry points need it, and a shared property with three consumers does not belong in any one of them.

CASE-SENSITIVE, and every gate that cites this function inherits that. The collision test is a map lookup on the escaped name, so a schema declaring quoted "MY$TABLE" (stored MY__1TABLE) alongside quoted "my__1table" (stored my__01table) is NOT reported: the spellings differ only in case. That gap does not reach a destructive path, because GetRecordType is case-sensitive too and never resolves one to the other -- but a renderer that lowercases before comparing would re-open it. Tracked in TODO.md.

Returns USER identifiers because the operator has to act on the SQL names; the collision itself is detected in storage space, where it lives.

func (*RecordMetaData) CommonPrimaryKey

func (m *RecordMetaData) CommonPrimaryKey() KeyExpression

CommonPrimaryKey returns the primary key expression if all record types share the same one, or nil if they differ. Uses structural comparison via keyExpressionEquals. Matches Java's RecordMetaData.commonPrimaryKey().

func (*RecordMetaData) CommonPrimaryKeyLength

func (m *RecordMetaData) CommonPrimaryKeyLength() int

CommonPrimaryKeyLength returns the number of columns in the primary key if all record types have the same PK length, or -1 if they differ. Matches Java's RecordMetaData.commonPrimaryKeyLength().

func (*RecordMetaData) DeclaresSyntheticRecordTypes

func (m *RecordMetaData) DeclaresSyntheticRecordTypes() bool

DeclaresSyntheticRecordTypes reports whether the metadata DECLARES joined or unnested record types — types this port carries verbatim (see preservedMetaDataFields) but does not model.

It exists so that "Go does not model synthetic types" cannot be silently mistaken for "this metadata has none". Those are different claims, and only the second one licenses treating the record-type set as complete. A caller that computes over "all record types" — a scan range, a coverage decision, a count — is computing over a set that omits the synthetic ones, and must refuse rather than answer from the partial set.

func (*RecordMetaData) FileDescriptor

func (m *RecordMetaData) FileDescriptor() protoreflect.FileDescriptor

FileDescriptor returns the schema's protobuf file descriptor.

func (*RecordMetaData) GetAllIndexes

func (m *RecordMetaData) GetAllIndexes() map[string]*Index

GetAllIndexes returns all indexes by name.

func (*RecordMetaData) GetFormerIndexes

func (m *RecordMetaData) GetFormerIndexes() []*FormerIndex

GetFormerIndexes returns all former (deleted) indexes. Matches Java's RecordMetaData.getFormerIndexes().

func (*RecordMetaData) GetFormerIndexesSince

func (m *RecordMetaData) GetFormerIndexesSince(version int) []*FormerIndex

GetFormerIndexesSince returns former indexes removed since the given version. Matches Java's RecordMetaData.getFormerIndexesSince(int).

func (*RecordMetaData) GetIndex

func (m *RecordMetaData) GetIndex(name string) *Index

GetIndex returns the index with the given name, or nil if not found. Matches Java's RecordMetaData.getIndex(String).

func (*RecordMetaData) GetIndexFromSubspaceKey

func (m *RecordMetaData) GetIndexFromSubspaceKey(key any) *Index

GetIndexFromSubspaceKey returns the index with the given subspace key, or nil. Matches Java's RecordMetaData.getIndexFromSubspaceKey().

func (*RecordMetaData) GetIndexesForRecordType

func (m *RecordMetaData) GetIndexesForRecordType(name string) []*Index

GetIndexesForRecordType returns the indexes defined for a specific record type, including both single-type and multi-type indexes. Does NOT include universal indexes — use GetUniversalIndexes() for those. Matches Java's RecordType.getAllIndexes().

Resolves through GetRecordType rather than indexing the map, so a caller holding the SQL identifier for a type stored under its escaped protobuf name gets that type's indexes instead of nil. A raw lookup here returns "this type has no indexes", which is a silent wrong answer wherever the result feeds an index-selection decision.

func (*RecordMetaData) GetIndexesToBuildSince

func (m *RecordMetaData) GetIndexesToBuildSince(version int) []*Index

GetIndexesToBuildSince returns indexes that were added or modified since the given metadata version. Used by CreateOrOpen to detect new indexes that need to be built when opening an existing store with updated metadata. Matches Java's RecordMetaData.getIndexesToBuildSince(int).

func (*RecordMetaData) GetRecordCountKey

func (m *RecordMetaData) GetRecordCountKey() KeyExpression

GetRecordCountKey returns the key expression used for record counting. Returns nil if counting is disabled.

func (*RecordMetaData) GetRecordType

func (m *RecordMetaData) GetRecordType(name string) *RecordType

GetRecordType returns the record type for the given name.

Record types are keyed by their STORAGE name — the ProtoUtils-escaped descriptor name, which is what the wire carries. A caller holding a USER identifier (SQL text: a table declared `"foo$table"` is stored as FOO__1TABLE) resolves through the escape on a miss.

Java does not need this because its relational layer keeps its OWN table map keyed by the user name (RecordLayerSchemaTemplate.getTable) and never addresses RecordMetaData with a user identifier; Go's relational layer uses RecordMetaData directly as its table catalog, so the translation lives here — ONE boundary that every SQL path already funnels through — rather than being sprinkled over the 25 call sites.

The fallback fires only when the direct key misses, so it never SHADOWS a stored type and ToProtoBufCompliantName is deterministic. It is not, however, unambiguous: the escaping is not injective across the two namespaces, so a schema declaring both MY__1TABLE (the storage form of SQL MY$TABLE) and MY__01TABLE (the storage form of SQL MY__1TABLE) resolves a lookup for the SQL name MY__1TABLE to the FIRST of those — the wrong entry. No ordering fixes it; either order prices one of the pair wrong. See AmbiguousDeclaredNames, which is how a caller computing over all record types detects that case, and TestGetRecordTypeMisResolvesAnAmbiguousPair, which pins the behaviour so a reordering "fix" fails loudly.

func (*RecordMetaData) GetRecordTypeFromRecordTypeKey

func (m *RecordMetaData) GetRecordTypeFromRecordTypeKey(key any) *RecordType

GetRecordTypeFromRecordTypeKey returns the record type with the given type key. Returns nil if no record type matches. Matches Java's RecordMetaData.getRecordTypeFromRecordTypeKey(), which scans the record types comparing getRecordTypeKey().equals(recordTypeKey).

Comparison goes through recordTypeKeyIdentity — the same identity the duplicate check uses — so a lookup can never answer a question Build did not already settle. Comparing through the general subspace-key normalizer folded a []byte key into the string of the same bytes, and Build admits that pair as two distinct types, so the lookup returned whichever of them the map happened to yield first.

func (*RecordMetaData) GetSubspaceKeyCounter

func (m *RecordMetaData) GetSubspaceKeyCounter() int64

GetSubspaceKeyCounter returns the counter-based subspace-key counter value. Matches Java's RecordMetaData.getSubspaceKeyCounter().

func (*RecordMetaData) GetUnionDescriptor

func (m *RecordMetaData) GetUnionDescriptor() protoreflect.MessageDescriptor

GetUnionDescriptor returns the protobuf message descriptor for UnionDescriptor. Returns nil if the schema has no union (single-type schema). Matches Java's RecordMetaData.getUnionDescriptor().

func (*RecordMetaData) GetUnionFieldForRecordType

func (m *RecordMetaData) GetUnionFieldForRecordType(rt *RecordType) protoreflect.FieldDescriptor

GetUnionFieldForRecordType returns the union field descriptor for a record type. Returns nil if the record type has no union field (single-type schema). Matches Java's RecordMetaData.getUnionFieldForRecordType().

func (*RecordMetaData) GetUniversalIndexes

func (m *RecordMetaData) GetUniversalIndexes() []*Index

GetUniversalIndexes returns indexes that apply to all record types.

func (*RecordMetaData) HasIndexes

func (m *RecordMetaData) HasIndexes() bool

HasIndexes returns true if any indexes are defined.

func (*RecordMetaData) IsSplitLongRecords

func (m *RecordMetaData) IsSplitLongRecords() bool

IsSplitLongRecords returns whether records >100KB are split across multiple KV pairs.

func (*RecordMetaData) IsStoreRecordVersions

func (m *RecordMetaData) IsStoreRecordVersions() bool

IsStoreRecordVersions returns whether record versioning is enabled.

func (*RecordMetaData) PrimaryKeyHasRecordTypePrefix

func (m *RecordMetaData) PrimaryKeyHasRecordTypePrefix() bool

PrimaryKeyHasRecordTypePrefix returns true if all record types have a primary key that starts with a RecordTypeKeyExpression. Matches Java's RecordMetaData.primaryKeyHasRecordTypePrefix().

func (*RecordMetaData) RecordTypes

func (m *RecordMetaData) RecordTypes() map[string]*RecordType

RecordTypes returns all record types

NOTE: this returns the LIVE map, not a copy, and the map IS mutated after Build.

The census below is over changes to its KEY SET, because that is all the derived field depends on: computeAmbiguousDeclaredNames ranges the keys and probes declared[escaped], and never reads a *RecordType value. Mutating a value already in the map is irrelevant here.

ENUMERATED per site rather than counted by regex, and by SHAPE rather than by one shape mistaken for all of them. Both mistakes were made here in succession: first a count that included the sentence stating it, then an assignment census presented as a mutation census -- which survived a round of review, because the two read alike and the missing shape was delete().

Insertions, subscript form -- 8:
  2 on the BUILDER, before any RecordMetaData exists, both in this file:
    one in setRecordsWithUnionName, one in setRecordsWithoutUnion
  6 post-Build, every one in a test:
    1 in record_type_key_identity_test.go
    5 in metadata_evolution_validator_test.go

Removals, delete() form -- 4, ALL post-Build, every one in a test:
    1 in record_type_key_identity_test.go
    3 in metadata_evolution_validator_test.go
  Two of those three are bare removals with no paired insertion (the
  "rejects removed type" cases); the third is half of a rename. A removal
  invalidates the derived field exactly as an insertion does.

clear(), the maps.* helpers, and whole-map assignment: none AFTER
  construction. online_indexer.go does assign a whole recordTypes, but that
  is a []string on a different struct -- it was once miscounted into this
  census.

CONSTRUCTION is a separate route with the same fail-open, and is not a
mutation of this map at all, which is why it sits outside the counts above:
metadata_evolution_validator_test.go and online_indexer_preset_test.go each
build &RecordMetaData{recordTypes: ...} directly, so Build never runs and
ambiguousFound stays false -- reported as "no collision" over a set nobody
derived.

Twelve sites, ten of them after Build.

This matters because Build DERIVES ambiguousNames from this map, so any post-Build key-set change leaves the derived field describing a declared set that no longer exists. Latent today, and measured so: none of the THREE files named above calls AmbiguousDeclaredNames, so nothing reads the stale value. A test that starts doing both re-arms it.

Copying the map here would NOT close that. All ten post-Build sites touch the private field directly rather than through this accessor, so a copy prevents none of them while adding an O(types) allocation to every caller -- including computeAmbiguousDeclaredNames itself.

The staleness is silent either way, but it is at least deterministic: the field always means "what Build saw", never "whatever the set was the first time somebody happened to ask", which is what the sync.Once it replaced meant.

func (*RecordMetaData) RecordTypesForIndex

func (m *RecordMetaData) RecordTypesForIndex(idx *Index) []*RecordType

RecordTypesForIndex returns the record types that the given index covers. Universal indexes cover all record types. Type-specific indexes cover only the record types they are associated with. Matches Java's RecordMetaData.recordTypesForIndex(Index).

func (*RecordMetaData) SyntheticRecordTypeNames

func (m *RecordMetaData) SyntheticRecordTypeNames() []string

SyntheticRecordTypeNames returns the declared joined/unnested type names, for diagnostics. The port does not model these types; this reads their names off the carried protos so an error can say which ones it refused for.

func (*RecordMetaData) ToProto

func (m *RecordMetaData) ToProto() (*gen.MetaData, error)

ToProto serializes RecordMetaData to its protobuf representation. Matches Java's RecordMetaData.toProto().

EVERY field of the MetaData message is accounted for here, in one of two ways, and the distinction is the point of this comment — the previous version said only "Matches Java's RecordMetaData.toProto()", which read as a completeness claim it did not have. Six fields were being dropped silently.

MODELLED — parsed into Go structures and re-serialized from them:

1  records                    9  dependencies
2  indexes                   10  subspace_key_counter
3  record_types              11  uses_subspace_key_counter
4  split_long_records
5  version
6  former_indexes
7  record_count_key
8  store_record_versions

CARRIED VERBATIM — held as opaque protos and re-emitted unchanged, never interpreted (see preservedMetaDataFields):

12  joined_record_types      14  user_defined_functions
13  unnested_record_types    15  views

Carried is not supported. The port does not model synthetic record types, user-defined functions or views; it declines to DELETE them from metadata it did not author. Anything whose behaviour would depend on interpreting them must refuse rather than proceed on the partial view — see RecordMetaData.DeclaresSyntheticRecordTypes.

Extension ranges (1000-2000) are genuine unknown fields, and they are carried here too — as preservedMetaDataFields.unknown. Protobuf's own unknown-field preservation does NOT cover them across this round trip: it keeps unknown bytes attached to the message they were parsed into, and ToProto builds a FRESH MetaData, so nothing carries them over unless this does.

Unknown-field preservation is also the mechanism fields 12-15 were mistakenly assumed to be covered by, where it never applied for the opposite reason — they are declared fields of a message this code parses, so they were never unknown in the first place. Both halves of the message therefore need explicit carrying, and neither gets it for free.

func (*RecordMetaData) UsesSubspaceKeyCounter

func (m *RecordMetaData) UsesSubspaceKeyCounter() bool

UsesSubspaceKeyCounter reports whether index subspace keys are assigned from a counter rather than from index names. Matches Java's RecordMetaData.usesSubspaceKeyCounter().

func (*RecordMetaData) Version

func (m *RecordMetaData) Version() int

Version returns the metadata version

type RecordMetaDataBuilder

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

RecordMetaDataBuilder provides a builder pattern for creating RecordMetaData This matches the Java RecordMetaDataBuilder pattern

func NewRecordMetaDataBuilder

func NewRecordMetaDataBuilder() *RecordMetaDataBuilder

NewRecordMetaDataBuilder creates a new builder

func (*RecordMetaDataBuilder) AddIndex

func (b *RecordMetaDataBuilder) AddIndex(recordTypeName string, index *Index) *RecordMetaDataBuilder

AddIndex adds a secondary index for a specific record type. Matches Java's RecordMetaDataBuilder.addIndex(String recordType, Index index).

func (*RecordMetaDataBuilder) AddMultiTypeIndex

func (b *RecordMetaDataBuilder) AddMultiTypeIndex(recordTypeNames []string, index *Index) *RecordMetaDataBuilder

AddMultiTypeIndex adds an index spanning multiple record types. If recordTypeNames is nil or empty, treats as universal index. If only one name, adds as single-type index. Matches Java's RecordMetaDataBuilder.addMultiTypeIndex().

func (*RecordMetaDataBuilder) AddUniversalIndex

func (b *RecordMetaDataBuilder) AddUniversalIndex(index *Index) *RecordMetaDataBuilder

AddUniversalIndex adds an index that applies to all record types. Matches Java's RecordMetaDataBuilder.addUniversalIndex(Index index).

func (*RecordMetaDataBuilder) Build

Build creates the final RecordMetaData.

Returns an error if any record type has no primary key set, and refuses any metadata whose index registry and record-type associations do not agree in both directions -- see the bijection check below.

WHAT BUILD WRITES INTO THE BUILDER'S OWN OBJECTS -- first, because two successive revisions of this comment claimed it wrote nothing, and the revision that fixed THAT over-claimed in the other direction by listing three writes where there is one. Exactly one:

  • `idx.primaryKeyComponentPositions`, on every SINGLE-TYPE index, because `indexes[k] = v` shares the pointer. That is the point: Java sets positions on the objects the caller registered and the scan call sites read them off those same objects. (Multi-type and universal indexes are deliberately excluded -- see the loop.)

`rt.explicitRecordTypeKey` and `fi.SubspaceKey` are NOT writes, which is easy to misread because the assignments look like ones: both sites open with a struct copy (`rt := *v`, `f := *fi`) and then assign the COPY. The builder's RecordType and FormerIndex objects come out of Build unchanged. They belong to the copied set below.

WHAT STAYS SHARED with the builder after Build returns: the `*Index` objects themselves, and therefore everything reachable through them -- `Options` (a map, with the exported in-place mutators SetUnique and SetClearWhenZero), `primaryKeyComponentPositions`, `Predicate`, `predicateProto`, `subspaceKey`, and the KeyExpression graphs behind `RootExpression`. Also shared, both descriptors: `fileDescriptor` and `unionDescriptor`; and the KeyExpression graphs behind each record type's `PrimaryKey` and behind `recordCountKey`.

WHAT IS COPIED: every container Build owns directly -- the index registry map, the universal and former index slices, each record type's index slices, the RecordType and FormerIndex structs themselves (with their `[]byte` and subspace-key fields deep-copied), the `preserved` struct and all FIVE of its slices (`unknown` included), and `recordsSourceProto` (a proto.Clone; note this is the RETAINED verbatim descriptor, not the shared `fileDescriptor` named above).

WHAT IS DERIVED HERE and shared with nothing: `fieldNumberToRecordType` and `ambiguousNames`, both computed from the metadata this Build assembled.

Copying the containers keeps a later builder mutation from rewriting what Build returned; sharing the objects is what callers depend on when they hand a pre-Build *Index to ScanIndex, RebuildIndex or SetIndex, and what keeps OnlineIndexer's containment check an identity check rather than a name check. The objects therefore stay mutable through their exported fields and through those two setters, which is a divergence from Java's `private final` plus `ImmutableMap.copyOf`: DIVERGENCES.md has the analysis and the fix.

func (*RecordMetaDataBuilder) EnableCounterBasedSubspaceKeys

func (b *RecordMetaDataBuilder) EnableCounterBasedSubspaceKeys() *RecordMetaDataBuilder

EnableCounterBasedSubspaceKeys switches index subspace keys from name-based (string) to counter-based (int64). Each index added after this call gets an auto-incrementing integer subspace key instead of the index name. Matches Java's RecordMetaDataBuilder.enableCounterBasedSubspaceKeys().

func (*RecordMetaDataBuilder) GetFormerIndexes

func (b *RecordMetaDataBuilder) GetFormerIndexes() []*FormerIndex

GetFormerIndexes returns the builder's former indexes (for testing/inspection).

func (*RecordMetaDataBuilder) GetRecordCountKey

func (b *RecordMetaDataBuilder) GetRecordCountKey() KeyExpression

GetRecordCountKey returns the record count key expression on the builder.

func (*RecordMetaDataBuilder) GetRecordType

func (b *RecordMetaDataBuilder) GetRecordType(name string) *RecordTypeBuilder

GetRecordType returns the record type builder for setting primary keys, etc. Panics with MetaDataError if the record type does not exist, matching Java's RecordMetaDataBuilder.getRecordType() which throws MetaDataException.

func (*RecordMetaDataBuilder) GetRecordTypes

func (b *RecordMetaDataBuilder) GetRecordTypes() map[string]*RecordType

GetRecordTypes returns the record types map on the builder.

func (*RecordMetaDataBuilder) GetSubspaceKeyCounter

func (b *RecordMetaDataBuilder) GetSubspaceKeyCounter() int64

GetSubspaceKeyCounter returns the current counter value; 0 when the scheme is not enabled. Matches Java's RecordMetaDataBuilder.getSubspaceKeyCounter().

func (*RecordMetaDataBuilder) GetVersion

func (b *RecordMetaDataBuilder) GetVersion() int

GetVersion returns the current metadata version on the builder. Matches Java's RecordMetaDataBuilder.getVersion().

func (*RecordMetaDataBuilder) IsSplitLongRecords

func (b *RecordMetaDataBuilder) IsSplitLongRecords() bool

IsSplitLongRecords returns whether split long records is enabled on the builder. Matches Java's RecordMetaDataBuilder.isSplitLongRecords().

func (*RecordMetaDataBuilder) IsStoreRecordVersions

func (b *RecordMetaDataBuilder) IsStoreRecordVersions() bool

IsStoreRecordVersions returns whether record versioning is enabled on the builder. Matches Java's RecordMetaDataBuilder.isStoreRecordVersions().

func (*RecordMetaDataBuilder) RemoveIndex

func (b *RecordMetaDataBuilder) RemoveIndex(indexName string) *RecordMetaDataBuilder

RemoveIndex removes an index by name and records it as a FormerIndex to prevent subspace key reuse. Matches Java's RecordMetaDataBuilder.removeIndex(String).

func (*RecordMetaDataBuilder) SetRecordCountKey

func (b *RecordMetaDataBuilder) SetRecordCountKey(key KeyExpression) *RecordMetaDataBuilder

SetRecordCountKey sets the key expression for partitioning record counts. If set, the store will maintain record counts using FDB atomic ADD mutations. If nil (default), record counting is disabled. Java equivalent: RecordMetaDataBuilder.setRecordCountKey(KeyExpression)

func (*RecordMetaDataBuilder) SetRecords

SetRecords sets the protobuf file descriptor containing record definitions. Uses the default union message name "UnionDescriptor".

func (*RecordMetaDataBuilder) SetRecordsSourceProto

SetRecordsSourceProto retains the ORIGINAL FileDescriptorProto the file descriptor was built from, so ToProto() can emit it verbatim. Java keeps the source proto inside Descriptors.FileDescriptor and toProto() returns it unchanged; Go's protodesc.ToFileDescriptorProto instead RECONSTRUCTS the proto — absolutizing type names (".T" where Java stored "T") and materializing json_name — which changes the stored bytes. Descriptor bytes are wire (the catalog persists RecordMetaData.toProto()), so the source proto must survive to serialization untouched.

func (*RecordMetaDataBuilder) SetRecordsWithUnionName

func (b *RecordMetaDataBuilder) SetRecordsWithUnionName(fd protoreflect.FileDescriptor, unionName string) *RecordMetaDataBuilder

SetRecordsWithUnionName is SetRecords with an explicit union message name. Use this when the proto file's union message is not called "UnionDescriptor" — e.g. schemas that must coexist with another RecordMetaData in the same Go package (gen.*), where duplicate UnionDescriptor symbols would clash. Behaviour is identical to SetRecords in every other respect.

func (*RecordMetaDataBuilder) SetSplitLongRecords

func (b *RecordMetaDataBuilder) SetSplitLongRecords(split bool) *RecordMetaDataBuilder

SetSplitLongRecords enables or disables splitting records >100KB across multiple FDB key-value pairs. Matches Java's RecordMetaDataBuilder.setSplitLongRecords(boolean).

func (*RecordMetaDataBuilder) SetStoreRecordVersions

func (b *RecordMetaDataBuilder) SetStoreRecordVersions(store bool) *RecordMetaDataBuilder

SetStoreRecordVersions enables or disables automatic record versioning. When enabled, each save assigns an FDBRecordVersion to the record. Java equivalent: RecordMetaDataBuilder.setStoreRecordVersions(boolean)

func (*RecordMetaDataBuilder) SetSubspaceKeyCounter

func (b *RecordMetaDataBuilder) SetSubspaceKeyCounter(counter int64) *RecordMetaDataBuilder

SetSubspaceKeyCounter sets the counter's starting value, for callers whose indexes already carry keys that would collide with counter-based assignment. Matches Java's RecordMetaDataBuilder.setSubspaceKeyCounter(long), including both of its refusals: the scheme must already be enabled, and the counter may only move FORWARD. Moving it backwards would hand a fresh index a key some existing index already owns, which is a silent data collision rather than an error, so the guard is not a convenience.

func (*RecordMetaDataBuilder) SetVersion

func (b *RecordMetaDataBuilder) SetVersion(version int) *RecordMetaDataBuilder

SetVersion sets the metadata schema version. This should be bumped when the schema changes for evolution tracking. Matches Java's RecordMetaDataBuilder.setVersion(int).

func (*RecordMetaDataBuilder) UsesSubspaceKeyCounter

func (b *RecordMetaDataBuilder) UsesSubspaceKeyCounter() bool

UsesSubspaceKeyCounter reports whether counter-based subspace-key assignment is in effect. Matches Java's RecordMetaDataBuilder.usesSubspaceKeyCounter().

type RecordSerializationError

type RecordSerializationError struct {
	Cause error
}

RecordSerializationError is returned when a record fails to serialize (marshal) to protobuf. Matches Java's com.apple.foundationdb.record.provider.foundationdb.RecordSerializationException.

func (*RecordSerializationError) Error

func (e *RecordSerializationError) Error() string

func (*RecordSerializationError) Unwrap

func (e *RecordSerializationError) Unwrap() error

type RecordStoreAlreadyExistsError

type RecordStoreAlreadyExistsError struct{}

RecordStoreAlreadyExistsError is returned when attempting to create a store that already exists. Matches Java's com.apple.foundationdb.record.provider.foundationdb.RecordStoreAlreadyExistsException.

func (*RecordStoreAlreadyExistsError) Error

type RecordStoreDoesNotExistError

type RecordStoreDoesNotExistError struct{}

RecordStoreDoesNotExistError is returned when attempting to open a store that does not exist. Matches Java's com.apple.foundationdb.record.provider.foundationdb.RecordStoreDoesNotExistException.

func (*RecordStoreDoesNotExistError) Error

type RecordStoreNoInfoButNotEmptyError

type RecordStoreNoInfoButNotEmptyError struct {
	FirstKey []byte // First key found in the subspace (Java's LogMessageKeys.KEY)
}

RecordStoreNoInfoButNotEmptyError is returned when a store subspace has data but no valid store header (StoreInfoKey). Matches Java's com.apple.foundationdb.record.provider.foundationdb.RecordStoreNoInfoAndNotEmptyException.

func (*RecordStoreNoInfoButNotEmptyError) Error

type RecordStoreState

type RecordStoreState struct {
	StoreHeader *gen.DataStoreInfo
	IndexStates map[string]IndexState
}

RecordStoreState captures the mutable state of a record store at a point in time. Matches Java's RecordStoreState.

type RecordStoreStateNotLoadedError

type RecordStoreStateNotLoadedError struct{}

RecordStoreStateNotLoadedError is returned when store operations are called before the store state has been loaded via Create/Open/CreateOrOpen. Matches Java's com.apple.foundationdb.record.provider.foundationdb.UninitializedRecordStoreException.

func (*RecordStoreStateNotLoadedError) Error

type RecordType

type RecordType struct {
	// Name of the record type (usually the protobuf message name)
	Name string

	// Protobuf message descriptor
	Descriptor protoreflect.MessageDescriptor

	// Primary key definition
	PrimaryKey KeyExpression

	// Since version (for schema evolution)
	SinceVersion int

	// Record type index in union descriptor (for key construction)
	RecordTypeIndex int

	// Union field descriptor for reflection-based access
	UnionFieldDescriptor protoreflect.FieldDescriptor
	// contains filtered or unexported fields
}

RecordType represents a type of record that can be stored

func (*RecordType) GetAllIndexes

func (rt *RecordType) GetAllIndexes() []*Index

GetAllIndexes returns all indexes for this record type (single-type + multi-type). Does not include universal indexes. Matches Java's RecordType.getAllIndexes().

func (*RecordType) GetIndexes

func (rt *RecordType) GetIndexes() []*Index

GetIndexes returns the indexes defined for this record type (single-type only). Does not include multi-type or universal indexes. Matches Java's RecordType.getIndexes().

func (*RecordType) GetMultiTypeIndexes

func (rt *RecordType) GetMultiTypeIndexes() []*Index

GetMultiTypeIndexes returns the multi-type indexes for this record type. Matches Java's RecordType.getMultiTypeIndexes().

func (*RecordType) GetRecordTypeKey

func (rt *RecordType) GetRecordTypeKey() any

GetRecordTypeKey returns the explicit record type key if set, or falls back to the record type index. Matches Java's RecordType.getRecordTypeKey().

The stored value is ALREADY canonical — SetRecordTypeKey and the proto reader canonicalize on the way in, as Java's setter does — so this returns it verbatim. Normalizing on the way out instead would leave every other consumer of the field (duplicate validation, proto export, prefix comparison) looking at the raw value while only the evaluation path saw the canonical one, which is the split that let equal-encoding keys pass validation as distinct.

The derived arm converts the record type index because RecordTypeIndex is a declared int field, not a caller-supplied value: the conversion is exact and total, and it gives int and int64 spellings of a key one representation.

func (*RecordType) HasExplicitRecordTypeKey

func (rt *RecordType) HasExplicitRecordTypeKey() bool

HasExplicitRecordTypeKey returns true if the record type key was explicitly set. Matches Java's RecordType.hasExplicitRecordTypeKey().

func (*RecordType) IsSynthetic

func (rt *RecordType) IsSynthetic() bool

IsSynthetic reports whether this is a synthetic record type (one assembled from other records, e.g. a joined type). The Go port does not model synthetic record types — they are out of scope (see CLAUDE.md) — so this is always false. Kept as a method for 1:1 fidelity with Java's RecordType.isSynthetic() so callers (e.g. the typed-records range preset) read identically to the Java algorithm.

The constant false is SOUND ONLY BECAUSE no synthetic type can reach a *RecordType. Record types are built from the union descriptor's fields, and a joined or unnested type is declared in the metadata proto rather than in the union — so it never becomes one of these. The dangerous reading is that synthetic types are absent from the METADATA, which is not what this says and is not true once fields 12/13 are carried. Callers that need the metadata-level question must ask RecordMetaData.DeclaresSyntheticRecordTypes.

func (*RecordType) PrimaryKeyHasRecordTypePrefix

func (rt *RecordType) PrimaryKeyHasRecordTypePrefix() bool

PrimaryKeyHasRecordTypePrefix returns true if this record type's primary key starts with a RecordTypeKeyExpression — i.e. its records live in a contiguous record-type-keyed sub-range of the records space. Matches Java's RecordType.primaryKeyHasRecordTypePrefix().

type RecordTypeBuilder

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

RecordTypeBuilder provides methods to configure a specific record type

func (*RecordTypeBuilder) SetPrimaryKey

func (rtb *RecordTypeBuilder) SetPrimaryKey(keyExpr KeyExpression) *RecordTypeBuilder

SetPrimaryKey sets the primary key expression for this record type

func (*RecordTypeBuilder) SetRecordTypeKey

func (rtb *RecordTypeBuilder) SetRecordTypeKey(key any) *RecordTypeBuilder

SetRecordTypeKey overrides the auto-derived record type key for this record type. By default, the record type index (proto field number order) is used. Matches Java's RecordTypeBuilder.setRecordTypeKey(Key.Evaluated), which delegates to RecordTypeIndexesBuilder.setRecordTypeKey: reject anything that is not a primitive, then store TupleTypeUtil.toTupleEquivalentValue(key).

Canonicalizing HERE, not at every read, is the whole point. The key is read by duplicate-key validation, by proto serialization, by DeleteRecordsWhere's prefix comparison and by the tuple packer, and each of those asks a different question of it — equality, encodability, wire bytes. A value normalized only on the evaluation path leaves the others looking at a different representation of the same key, which is how int64(7) and uint(7) came to be two "distinct" keys that encode to identical bytes.

A rejected key is reported from Build() rather than panicking: Java throws MetaDataException from the setter, but a Go builder setter returns the builder for chaining and library code must not panic, so the error is deferred to the one call that already returns one.

type RecordTypeChangedError

type RecordTypeChangedError struct {
	Message      string
	PrimaryKey   any // tuple.Tuple, but using any to avoid import cycle
	ActualType   string
	ExpectedType string
}

RecordTypeChangedError is returned when attempting to update a record but its type has changed. Includes structured context matching Java's RecordTypeChangedException.

Java equivalent: com.apple.foundationdb.record.provider.foundationdb.RecordTypeChangedException Location: fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/RecordTypeChangedException.java Java includes: LogMessageKeys.PRIMARY_KEY, LogMessageKeys.ACTUAL_TYPE, LogMessageKeys.EXPECTED_TYPE

func (*RecordTypeChangedError) Error

func (e *RecordTypeChangedError) Error() string

type RecordTypeKeyExpression

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

RecordTypeKeyExpression represents the special record type key prefix. Matches Java's RecordTypeKeyExpression: evaluates to the record type key (an integer derived from the union descriptor field number). It carries NO state. Java's RecordTypeKeyExpression is a singleton with no fields whose evaluateMessage reads the key off the record being evaluated:

record != null ? Key.Evaluated.scalar(record.getRecordType().getRecordTypeKey())
               : Key.Evaluated.NULL

Go previously bound a metadata-derived map onto the expression instead. That made the expression object metadata-scoped state while the object itself is shared: RecordMetaDataBuilder does not copy key expressions, so two builds from one expression graph both wrote into the same slot, and whichever bound LAST decided the record type key for BOTH — the first metadata's stores then wrote index entries and primary keys under the second metadata's key. No amount of invalidation fixes that, because the two bindings are both live at once; the state has to go. Resolving from the record makes the key a function of the record's own resolved type, which is what it always meant.

func RecordTypeKey

func RecordTypeKey() *RecordTypeKeyExpression

RecordTypeKey creates a key expression that prefixes with the record type

func (*RecordTypeKeyExpression) ColumnSize

func (r *RecordTypeKeyExpression) ColumnSize() int

ColumnSize returns 1 for the type key itself, plus the nested expression's column size if present.

func (*RecordTypeKeyExpression) Evaluate

func (r *RecordTypeKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate returns the record type key (integer), optionally followed by nested values. Matches Java's RecordTypeKeyExpression.evaluateMessage() which returns record.getRecordType().getRecordTypeKey() — the union descriptor field number.

func (*RecordTypeKeyExpression) EvaluateFlat

func (r *RecordTypeKeyExpression) EvaluateFlat(record *FDBStoredRecord[proto.Message], msg proto.Message) ([]any, error)

EvaluateFlat returns the type key as a single-element []any, or the type key followed by the nested expression's columns when Nest() was used.

evaluateKeyFlat propagates this result directly with no fallback to Evaluate, and store.go computes every record's primary key through it, so the nested columns must be produced here rather than declined — returning only the type key collapses every record of the type onto one primary key.

func (*RecordTypeKeyExpression) EvaluateScalar

func (r *RecordTypeKeyExpression) EvaluateScalar(record *FDBStoredRecord[proto.Message], msg proto.Message) (any, error)

EvaluateScalar returns the type key directly — zero alloc.

Only the bare record-type key is a scalar. Nest() is a Go-only extension (Java's RecordTypeKeyExpression is KeyExpressionWithoutChildren with getColumnSize() == 1), and a nested expression makes ColumnSize() > 1, so there is no single value to return. Erroring routes every caller to Evaluate; returning just the type key would silently drop the nested columns and pack a short key.

func (*RecordTypeKeyExpression) FieldNames

func (r *RecordTypeKeyExpression) FieldNames() []string

FieldNames returns the field names accessed by nested expression

func (*RecordTypeKeyExpression) Nest

Nest adds a nested key expression after the record type prefix

func (*RecordTypeKeyExpression) PackDirect

PackDirect encodes the record type key directly into a Packer. Reports false when the type cannot be resolved from the record, which routes the caller to the erroring Evaluate path rather than packing a guess.

A nested expression is declined for the same reason: this packer emits one element, so accepting a Nest() would write a key short of ColumnSize().

func (*RecordTypeKeyExpression) ToKeyExpression

func (r *RecordTypeKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a RecordTypeKeyExpression to proto. A bare RecordTypeKeyExpression serializes as RecordTypeKey{}. With a nested expression, serializes as Then{RecordTypeKey{}, nested} matching Java's concat(recordTypeKey(), nested).

type RecordTypeKeyTypeError

type RecordTypeKeyTypeError struct {
	// Key is the offending value.
	Key any
	// Reason distinguishes the two ways a key is refused. Empty means the Go
	// TYPE is not one a record type key may have, which is Java's wording
	// verbatim. A non-empty reason means the type is fine but this VALUE
	// cannot be represented, where Java's message would be actively
	// misleading — a uint64 above max int64 is a primitive, and saying
	// otherwise sends the reader looking for the wrong mistake.
	Reason string
}

RecordTypeKeyTypeError reports a record type key whose Go type cannot be a record type key. Java's equivalent is the MetaDataException "Only primitive types are allowed as record type key" thrown by RecordTypeIndexesBuilder.setRecordTypeKey.

func (*RecordTypeKeyTypeError) Error

func (e *RecordTypeKeyTypeError) Error() string

type RecordTypeKeyUnresolvedError

type RecordTypeKeyUnresolvedError struct {
	// MessageType is the proto message name that was being evaluated, empty
	// when there was no message either.
	MessageType string
}

RecordTypeKeyUnresolvedError reports an evaluation of a record type key against a record whose type was never resolved. Java cannot reach this: its FDBRecord always carries a RecordType, and the save path evaluates the primary key against an FDBStoredRecordBuilder that has been given the type before any key is computed (FDBRecordStore.saveTypedRecord). Go's callers must do the same. It is an ERROR rather than a fallback to the message's type NAME because that fallback wrote a string where the integer type key belongs — bytes Java reads as a different key entirely — and did it silently.

func (*RecordTypeKeyUnresolvedError) Error

type RecordTypeStatistic

type RecordTypeStatistic struct {
	// Count is the number of records of this type observed by the scan. Exact
	// for a store at rest; see the type comment for what concurrent key moves do.
	Count int64
	// CollectedAtVersion is the read version the count was taken at.
	CollectedAtVersion int64
	// CollectedAtUnixNanos is the collector's wall clock at collection.
	// Used for expiry; see StatisticsReader.
	CollectedAtUnixNanos int64
}

RecordTypeStatistic is one collected per-record-type statistic.

Count is COUNTED, not sampled. That is the point of collecting offline: it removes the floor, the quantization and the bytes-to-rows conversion that make plan-time sampling unusable for small tables.

It is EXACT for a store that is not being mutated under the scan, and only then. Collection spans transactions -- it must, since a full scan cannot fit in one -- and each batch is a consistent snapshot of its own, not of the run. So a record whose PRIMARY KEY MOVES concurrently, from an already-scanned key to a later one, is observed by both batches and counted twice; the reverse move is observed by neither and counted zero times. Ordinary inserts and deletes are not a problem in the same way -- they are simply seen or not, which is the usual meaning of "as of collection time" -- but a key move can make the total differ from any count the store ever actually had.

This is not fixable by scanning harder. A run-wide snapshot needs one read version across the whole store, and FDB expires a read version in 5s -- the same limit that forces the batching. The honest statement is therefore the narrower one, and it is enough for the purpose: a count that is exact at rest and near-exact under churn still separates a 150-row table from a 1,000,000-row one, which is the decision this feature exists to inform, and it is a class better than the estimator it replaced.

type RowNumberWindowDirection

type RowNumberWindowDirection int

RowNumberWindowDirection is the sort direction of a sliding window's ordering field. Matches Java's IndexPredicate.RowNumberWindowPredicate.Direction.

const (
	// RowNumberWindowAscending keeps the SMALLEST ordering values — the rows
	// with the lowest row numbers in ascending order.
	RowNumberWindowAscending RowNumberWindowDirection = iota
	// RowNumberWindowDescending keeps the LARGEST ordering values.
	RowNumberWindowDescending
)

func (RowNumberWindowDirection) String

func (d RowNumberWindowDirection) String() string

type RowNumberWindowSpec

type RowNumberWindowSpec struct {
	// OrderingField is the field PATH of the ordering key, outermost first.
	OrderingField []string
	// Direction selects MIN (ASC) or MAX (DESC) extremum semantics.
	Direction RowNumberWindowDirection
	// Size is the window's N — how many records the index keeps per partition.
	Size int
	// PartitionFieldPaths are the PARTITION BY field paths, each outermost
	// first. Empty means unpartitioned.
	PartitionFieldPaths [][]string
}

RowNumberWindowSpec is the Go form of Java's IndexPredicate.RowNumberWindowPredicate: the declaration `QualifyRowNumber(orderingField, direction) <= size`, optionally `PARTITION BY partitionFieldPaths`.

It is a WINDOWING SPEC, not a row filter. Go's Index.Predicate is a `func(proto.Message) bool` that answers "does this record belong in the index"; a row-number window cannot be answered per-record, because the answer depends on every other record in the partition. So the compiled Index.Predicate for this arm accepts every record (matching Java's RowNumberWindowPredicate.shouldIndexThisRecord, IndexPredicate.java:739-742) and the actual windowing lives in the maintainer, which reads the spec back off the stored proto through RowNumberWindowPredicateOf.

func (*RowNumberWindowSpec) OrderingKey

func (s *RowNumberWindowSpec) OrderingKey() (KeyExpression, error)

OrderingKey builds the KeyExpression for the ordering field. Matches Java's RowNumberWindowPredicate.getOrderingKey().

func (*RowNumberWindowSpec) PartitionKey

func (s *RowNumberWindowSpec) PartitionKey() (KeyExpression, error)

PartitionKey builds the KeyExpression for the partition key, or nil when the window is unpartitioned. Multiple partition paths are concatenated in declaration order. Matches Java's RowNumberWindowPredicate.getPartitionKey().

func (*RowNumberWindowSpec) PartitionKeyColumnSize

func (s *RowNumberWindowSpec) PartitionKeyColumnSize() (int, error)

PartitionKeyColumnSize returns the total number of columns the partition key produces, or 0 when unpartitioned. Matches Java's RowNumberWindowPredicate.getPartitionKeyColumnSize().

func (*RowNumberWindowSpec) String

func (s *RowNumberWindowSpec) String() string

type SPFreshConfig

type SPFreshConfig struct {
	NumDimensions int
	Metric        VectorMetric
	Lmax          int     // posting split threshold (entries)
	LminRatio     int     // Lmin = Lmax / LminRatio (merge threshold)
	CellTarget    int     // fine centroids per cell, build target
	CellMax       int     // coarse-split threshold (fine centroids)
	Replication   int     // closure replication cap r
	Alpha         float64 // RNG closure threshold (> 1.0)
	Kn            int     // NPA reassignment neighborhood
	CooldownSec   int     // post-split merge cooldown
	NumExBits     int     // RaBitQ extended bits for posting residual codes
	Sidecar       bool    // fp16 sidecar for re-rank
	// BuildAssignCells is the bulk-build wave-B assignment width w_b (RFC-099):
	// nearest coarse cells whose fines are candidates per imported vector.
	BuildAssignCells int
}

SPFreshConfig is the structural configuration of an SPFresh index. Every field here is immutable for an existing index (RFC-094 §10).

func DefaultSPFreshConfig

func DefaultSPFreshConfig(numDimensions int) SPFreshConfig

DefaultSPFreshConfig returns the RFC-094 defaults for the given dimensionality.

func (SPFreshConfig) Lmin

func (c SPFreshConfig) Lmin() int

Lmin returns the merge threshold in entries.

type SPFreshIntegrityReport

type SPFreshIntegrityReport struct {
	Members                int            // total membership rows in the index
	Sampled                int            // pks actually checked
	OK                     int            // sampled pks whose every membership target holds the posting entry
	MembershipWithoutEntry int            // sampled pks with >=1 membership target missing its posting entry
	TargetStates           map[string]int // membership-target references by centroid state: active/sealed/forward/dead/absent
	BadTargets             int            // target references in forward/dead/absent state (unambiguously bad post-drain)

	// Posting-size accounting (SPANN §3.2.1 balanced-postings / SPFresh LIRE
	// split-merge bounds). The papers' defining property is length-limited
	// postings; LIRE's split exists to hold the ceiling. Post-drain the normal
	// envelope is mostly <=Lmax with some <=4*Lmax (just-split children,
	// unsampled Lmax..2Lmax postings, closure overcount), and NEVER >4*Lmax —
	// the chunked-drain hard guarantee. A split that fails to drain grows a
	// posting unboundedly past the envelope, which OversizedHard catches.
	ActiveFines   int // ACTIVE fine centroids scanned
	Oversized     int // ACTIVE postings with > Lmax entries (soft: includes the legitimate <=4Lmax band)
	OversizedHard int // ACTIVE postings with > 4*Lmax entries (the LIRE hard-envelope violation)
	MaxPostingLen int // largest ACTIVE posting observed

	Violations []SPFreshIntegrityViolation
}

SPFreshIntegrityReport is the structured result of SPFreshCheckIntegrity — the same membership⊆postings + target-state invariants SPFreshDebugIntegrity formats as a string, returned as data for programmatic assertions. The report is descriptive; the caller sets policy. After the maintenance queue is drained to quiescence the strict invariant is MembershipWithoutEntry == 0, BadTargets == 0, and OversizedHard == 0 (every membership target ACTIVE-or- SEALED with its posting entry present, no posting over the 4×Lmax envelope). SEALED targets are valid — sealed postings are still searched mid-split; only FORWARD/DEAD/absent count as BadTargets.

func SPFreshCheckIntegrity

func SPFreshCheckIntegrity(rtx *FDBRecordContext, store *FDBRecordStore, indexName string, sample int) (SPFreshIntegrityReport, error)

SPFreshCheckIntegrity samples up to `sample` pks evenly from the index's own membership rows and verifies, for each, that every membership target holds the pk's posting entry (membership ⊆ postings) and classifies the target centroid's lifecycle state. It is the structured, programmatic form of SPFreshDebugIntegrity. O(index) reads (it streams the membership keyspace) — never call it on a serving path. `sample` <= 0 checks a single pk.

Violations beyond a small cap are counted but not individually recorded (BadTargets / MembershipWithoutEntry stay exact; Violations is truncated).

type SPFreshIntegrityViolation

type SPFreshIntegrityViolation struct {
	PrimaryKey tuple.Tuple
	FineID     int64
	// Kind: "missing_posting" (membership target lacks this pk's posting
	// entry), "absent_target" (target fineID is in no cell), "forward_target"
	// (target centroid split away), "dead_target" (target centroid GC'd).
	Kind string
}

SPFreshIntegrityViolation is one structural defect found by SPFreshCheckIntegrity: a membership row referencing a posting/centroid that the LIRE invariants forbid.

func (SPFreshIntegrityViolation) String

func (v SPFreshIntegrityViolation) String() string

type SPFreshMaintenanceOptions

type SPFreshMaintenanceOptions struct {
	// Tenants is the set of (store, index) pairs to maintain. The caller owns
	// discovery — store layout is application keyspace, not enumerable from
	// inside the record layer. Sharding the list across workers is safe:
	// SweepSPFreshIndexes is concurrent-safe by construction (unique lease
	// owners, task-level exclusion).
	Tenants []SPFreshTenant

	// SweepInterval is the rebalance cadence. <= 0 uses the default (10s).
	SweepInterval time.Duration
	// RefineInterval is the refinement cadence. <= 0 uses the default (5m).
	// Ignored when DisableRefine is set.
	RefineInterval time.Duration
	// DisableRefine turns off the RFC-104 refinement loop entirely (sweep
	// only) — e.g. for a read-mostly fleet where ingest drift never builds up.
	DisableRefine bool

	// Sweep / Refine carry the per-pass budgets and the StoreTimer that
	// accumulates instrumentation (per-kind action counts, lease skips,
	// refine moves/converged — see spfresh_metrics.go). Their zero values are
	// the documented defaults; set Timer to scrape metrics.
	Sweep  SPFreshSweepOptions
	Refine SPFreshRefineOptions

	// OnSweep / OnRefine, when set, are called after each pass with its result
	// and joined per-tenant error — the hook for logging / exporting metrics /
	// alerting on backlog. A pass error is never fatal to the loop (per-tenant
	// failures are already isolated inside Sweep/Refine); the loop runs until
	// ctx is canceled.
	OnSweep  func(SPFreshSweepResult, error)
	OnRefine func(SPFreshRefineResult, error)
}

SPFreshMaintenanceOptions configures the reference maintenance loop.

type SPFreshRecallReport

type SPFreshRecallReport struct {
	K               int     // neighbors per query
	CorpusSize      int     // indexed records scanned for ground truth
	QueriesRun      int     // query samples evaluated
	MeanRecall      float64 // average recall@k across queries (the headline signal)
	MinRecall       float64 // worst single-query recall@k
	PerfectFraction float64 // fraction of queries with recall@k == 1.0
}

SPFreshRecallReport summarizes a recall measurement.

func MeasureSPFreshRecall

func MeasureSPFreshRecall(ctx context.Context, store *FDBRecordStore, indexName string, k, querySamples int, seed int64) (SPFreshRecallReport, error)

MeasureSPFreshRecall measures recall@k of a readable SPFresh index against brute-force ground truth over `querySamples` query vectors drawn from the index's own records (deterministic by seed). Returns a zero-query report when the index is empty. The store must be bound to a transaction (call inside db.Run); the whole scan+queries run in that one tx, so keep the corpus under spfreshMaxRecallCorpus (see its doc for the scale limit). Mostly read-only, but it can inherit SearchSPFreshIndex's read-path split-repair write for any over-envelope posting (none on a maintained index). Distances use the index's configured metric, so the ground-truth ranking matches what the index optimizes, and recall is scored with the SPANN §4.2.1 tie correction (a result within the k-th true distance counts, so equidistant/duplicate vectors don't under-report).

Queries are drawn from the corpus itself (not a held-out set as in the SPANN SIFT benchmark), so absolute numbers run slightly optimistic — the self-match is a guaranteed hit. That is fine for the intended use as a relative drift signal: alert on MeanRecall falling below YOUR measured baseline, not on an absolute target.

type SPFreshRefineOptions

type SPFreshRefineOptions struct {
	// BudgetPerTenant bounds the vectors re-evaluated per tenant per pass.
	// 0 means the default (spfreshRefineFleetBudget).
	BudgetPerTenant int

	// Timer, when non-nil, accumulates refinement instrumentation across the pass
	// (CountSPFreshRefineMoves, CountSPFreshRefineConverged) — the observability
	// the rebalance sweeper gets via its own Timer. Nil disables recording.
	Timer *StoreTimer
}

SPFreshRefineOptions tunes one fleet refinement pass.

type SPFreshRefineResult

type SPFreshRefineResult struct {
	Moves     int // vectors moved (re-routed) across all tenants this pass
	Refined   int // tenants that completed a refine pass without error
	Converged int // tenants whose cursor wrapped a full cycle moving nothing
}

SPFreshRefineResult summarizes one fleet refinement pass.

func RefineSPFreshIndexes

func RefineSPFreshIndexes(ctx context.Context, db *FDBDatabase, tenants []SPFreshTenant, opts SPFreshRefineOptions) (SPFreshRefineResult, error)

RefineSPFreshIndexes runs ONE budgeted refinement pass over each tenant — the "refinement loop beside the rebalancer loop" (RFC-104). Unlike SweepSPFreshIndexes (task-driven: tenants with no pending lifecycle task rows are skipped), refinement is CURSOR-driven and unconditional — every tenant's persistent round-robin cursor advances by up to BudgetPerTenant vectors, re-routing each against the current (converged) topology and moving the stale ones, recovering the closure replication fast ingest never fired.

Run it on a SLOWER cadence than the rebalance sweep: refinement is recall-recovery, not correctness, and a fully converged tenant re-scans its cursor for zero moves each pass. The result's Converged count (cursor wrapped a full cycle with zero moves) lets a caller back off quiescent tenants. As in SweepSPFreshIndexes, per-tenant failures are isolated (joined, not fatal — one corrupt tenant must not halt fleet refinement) and ctx cancellation ends the pass between tenants.

type SPFreshSweepOptions

type SPFreshSweepOptions struct {
	// MaxRoundsPerTenant bounds the scan-and-act rounds per tenant per
	// pass. 0 means the default (8).
	MaxRoundsPerTenant int
	// MaxActionsPerTenant is the per-tenant fairness budget: at most this
	// many lifecycle actions per pass, ENFORCED WITHIN a round too — a
	// whale tenant whose single scan finds thousands of independent task
	// rows must not monopolize the pass. Undrained tenants
	// are reported, not errored — the next pass continues them. 0 means
	// the default (64).
	MaxActionsPerTenant int

	// Timer collects SPFresh maintenance instrumentation (per-kind action
	// counts, zombie cleanups, lease skips — see spfresh_metrics.go) across
	// the pass. Nil disables recording.
	Timer *StoreTimer
}

SPFreshSweepOptions tunes one sweep pass.

type SPFreshSweepResult

type SPFreshSweepResult struct {
	Actions   int // lifecycle actions executed across all tenants
	Worked    int // tenants that had pending maintenance
	Undrained int // tenants whose budget ran out with work remaining
}

SPFreshSweepResult summarizes one sweep pass.

func SweepSPFreshIndexes

func SweepSPFreshIndexes(ctx context.Context, db *FDBDatabase, tenants []SPFreshTenant, opts SPFreshSweepOptions) (SPFreshSweepResult, error)

SweepSPFreshIndexes makes ONE bounded pass over the tenants: a cheap pending-work probe per tenant (two snapshot reads), then a round-budgeted rebalance for the tenants that need it. Per-tenant failures are collected, not fatal — one corrupt tenant must not halt fleet maintenance; the pass continues and the joined error reports every failure. Callers loop passes on their own cadence and stop idling tenants however they like (the result says whether anything happened).

type SPFreshTenant

type SPFreshTenant struct {
	StoreBuilder func(*FDBRecordContext) (*FDBRecordStore, error)
	IndexName    string
}

SPFreshTenant names one index a sweeper drives: the store (the caller owns tenant discovery — store layout is application keyspace, not enumerable from inside the record layer) and the index within it.

type ScalarEvaluator

type ScalarEvaluator interface {
	EvaluateScalar(record *FDBStoredRecord[proto.Message], msg proto.Message) (any, error)
}

ScalarEvaluator is an optional interface for KeyExpressions that extract a single scalar value. Avoids both the outer [][]any and inner []any allocs.

type ScanLimitReachedError

type ScanLimitReachedError struct {
	// Reason is the out-of-band stop reason that triggered the failure
	// (ScanLimitReached or ByteLimitReached).
	Reason NoNextReason
}

ScanLimitReachedError is returned by a leaf cursor when it hits a scanned-records or scanned-bytes limit AND ExecuteProperties. FailOnScanLimitReached is set. Mirrors Java's ScanLimitReachedException (RecordCoreException, SQLSTATE 54F01): with setFailOnScanLimitReached(true) the scan errors instead of paginating. Default (false) is unchanged — the cursor returns a ScanLimitReached/ByteLimitReached NoNextReason and the caller paginates via the continuation.

func (*ScanLimitReachedError) Error

func (e *ScanLimitReachedError) Error() string

type ScanLimiterState

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

ScanLimiterState holds the mutable, execution-scoped counters that the scanned-records / scanned-bytes / time limits are checked against. It is the structural analog of Java's ExecuteState (ExecuteState.java:44-47, the RecordScanLimiter + ByteScanLimiter pair) plus the transaction-anchored TimeScanLimiter that CursorLimitManager mints from FDBRecordContext.getTransactionCreateTime() (CursorLimitManager.java:88-98) — NOT the Go type literally named ExecuteState (execute_state.go), which is a Go-only, statement-scoped extension (RFC-130's memory byte budget, no Java analog) held on ExecuteProperties.State. The two live on separate pointer fields with separate lifetimes for a reason: State/ExecuteState is minted ONCE per statement and survives every page/transaction retry (RFC-130 deliberately budgets memory across the whole result set); ScanState/ ScanLimiterState is minted fresh every time DefaultExecuteProperties is called — which paginatingRows.executeProps() does exactly once per page, INSIDE the retried transaction closure, so every retry attempt and every page gets its own honest scan/byte/time budget, matching Java's CursorLimitManager being rebuilt from a fresh FDBRecordContext each attempt.

That per-page lifetime is the AUTO-COMMIT story, and it is the whole story only there, because a page IS a transaction there. Inside an explicit SQL transaction the SQL layer overrides the freshly minted state with one held on the transaction (RFC-198 Decision 5), so every page of every statement charges ONE budget against FDB's single 5-second window instead of N fresh ones — which is the same thing Java gets from a transaction-scoped ExecuteState plus a transactionCreateTime anchor. AnchorAt below is what re-anchors that longer-lived state on the read-version instant; nothing on the auto-commit path calls it. Do not read this comment's resemblance to "ExecuteState" as a naming correspondence with Go's existing ExecuteState type; it names the Java class this ports, not the sibling Go type.

The pointer is carried on ExecuteProperties and copied BY REFERENCE through every value-copy (WithX helpers, ClearSkipAndLimit, per-leg innerProps) — the same reference-sharing pattern ExecuteProperties.State uses for the RFC-130 memory budget, just page-scoped instead of statement-scoped. That reference sharing is the fix for a real hang: an IN-join/IN-union with many legs previously gave every leg's leaf cursor its OWN local recordsScanned/bytesScanned/startTime, so a scan/byte/time limit configured to keep one FDB transaction under the 5s hard wall never tripped when each individual leg's own contribution stayed small — the limit was checked, just against the wrong (per-leg) denominator. A single ExecuteProperties value threaded through ExecutePlan's recursive leg calls (executeInJoin/executeInUnion: props.ClearSkipAndLimit(), never a fresh DefaultExecuteProperties()) now shares ONE counter set across every leg, so the aggregate scan actually stops (or errors, with FailOnScanLimitReached) once the total crosses the configured budget — matching Java's per-transaction CursorLimitManager sharing.

A nil *ScanLimiterState (any ExecuteProperties built as a raw struct literal rather than through DefaultExecuteProperties) is handled by each leaf cursor falling back to a private, cursor-local instance — identical to the pre-fix per-cursor counters, so every existing single-cursor caller is unaffected.

Concurrency: plain (non-atomic) fields, deliberately. Every leaf cursor that touches a ScanLimiterState is reached exclusively through the executor's single-threaded call chain (pkg/recordlayer/query/executor launches zero goroutines — the pinned invariant test package_invariant_test.go enforces this, and execute_state.go's ExecuteState relies on the same guarantee for its own counters) and none of the leaf cursors in this package (key_value_cursor.go, index_scan.go, record_key_cursor.go, count_index_maintainer.go, bitmap_value_index_maintainer.go, multidimensional_index_maintainer.go, text_cursor.go) spawn a goroutine either — including text_cursor.go's KVCallback, invoked synchronously from within textCursor.OnNext's own call to the underlying iterator, never from a separate goroutine. An earlier version of this file used sync/atomic for bytesScanned only, defensively, while leaving recordsScanned a plain field — inconsistent under both readings of the invariant (if it holds, the atomic ops are pure overhead — a per-record RMW where a `+=` used to suffice; if it doesn't, recordsScanned was ALREADY a silent race the moment anything pipelines). If the executor ever goes concurrent, the pinned test fires and every field here moves to atomic together, not just the one a defensive habit happened to cover.

func NewScanLimiterState

func NewScanLimiterState() *ScanLimiterState

NewScanLimiterState mints a fresh counter set anchored to the current instant — Java's FDBRecordContext.transactionCreateTime, set once when the transaction/execution attempt begins (FDBRecordContext.java:187) and shared by every TimeScanLimiter minted for that transaction (CursorLimitManager.java:93).

func NewScanLimiterStateIn

func NewScanLimiterStateIn(env *dst.Env) *ScanLimiterState

NewScanLimiterStateIn is NewScanLimiterState anchored on env's clock.

The time budget is not instrumentation: it decides whether a leaf cursor stops with TimeLimitReached, and therefore WHERE THE PAGE ENDS and what continuation the caller gets back. The SQL layer arms it on every single statement (paginatingRows.executeProps clamps to a per-transaction page budget so the FDB 5s wall is never crossed), so a wall-clock anchor means a simulated run pages differently depending on how fast the machine happened to be — nondeterminism in the exact layer RFC-199 exists to make reproducible.

func (*ScanLimiterState) AddBytesScanned

func (s *ScanLimiterState) AddBytesScanned(n int64)

AddBytesScanned charges n more bytes against the shared counter.

func (*ScanLimiterState) AddRecordScanned

func (s *ScanLimiterState) AddRecordScanned()

AddRecordScanned charges one more record against the shared counter.

func (*ScanLimiterState) AnchorAt

func (s *ScanLimiterState) AnchorAt(t time.Time)

AnchorAt moves the time anchor to t, keeping the records/bytes counters. It exists for the ONE state whose lifetime outlives the instant it was minted at: the transaction-scoped state of an explicit SQL transaction (RFC-198 Decision 5). That state is minted at the first statement's executeProps — before any read has happened — but the budget it enforces is FDB's 5-second MVCC window, which opens at the GRV. Minting time is the refuted proxy (a first statement need not take a read version at all: a RYW-covered read or a store-state-cache hit reads nothing), so the page preflight re-anchors on the client's read-version instant once one exists. Re-anchoring is idempotent between GRVs, and a retry's fresh GRV re-anchors automatically at the next page.

A zero t is ignored: "no instant" must never look like an epoch-zero anchor that makes every elapsed measurement astronomical.

func (*ScanLimiterState) BytesScanned

func (s *ScanLimiterState) BytesScanned() int64

BytesScanned returns the number of bytes charged against this state so far. A nil receiver reports zero.

func (*ScanLimiterState) Elapsed

func (s *ScanLimiterState) Elapsed() time.Duration

Elapsed reports how long this state has been running, on the SAME clock its anchor was minted on. Every leaf cursor's TimeLimit check goes through here.

A nil receiver reports zero, which reads as "no time has passed" and so never trips a limit — the safe direction for a state that was never configured.

func (*ScanLimiterState) RecordsScanned

func (s *ScanLimiterState) RecordsScanned() int

RecordsScanned returns the number of records charged against this state so far. A nil receiver (no shared state configured) reports zero.

func (*ScanLimiterState) StartTime

func (s *ScanLimiterState) StartTime() time.Time

StartTime returns the instant this state was minted — the anchor every leaf cursor sharing it measures TimeLimit against. A nil receiver returns the zero Time; callers must not call this on a nil state for a live time check (leaf cursors fall back to a private, non-nil state first).

Use Elapsed for a live limit check. Reading the anchor and subtracting it from some other clock is the mistake this type now makes structurally impossible.

type ScanProperties

type ScanProperties struct {
	// ExecuteProperties holds the execution-level properties
	ExecuteProperties ExecuteProperties

	// Reverse indicates if the scan should be in reverse order
	Reverse bool

	// CursorStreamingMode overrides the default streaming mode
	CursorStreamingMode CursorStreamingMode
}

ScanProperties groups properties that pertain to a single scan

func ForwardScan

func ForwardScan() ScanProperties

ForwardScan returns properties for a forward scan with default execution properties. Returns a fresh copy each time to prevent mutation of shared state.

func NewScanProperties

func NewScanProperties(executeProps ExecuteProperties) ScanProperties

NewScanProperties creates scan properties with the given execution properties

func ReverseScan

func ReverseScan() ScanProperties

ReverseScan returns properties for a reverse scan with default execution properties. Returns a fresh copy each time to prevent mutation of shared state.

func (ScanProperties) GetExecuteProperties

func (s ScanProperties) GetExecuteProperties() ExecuteProperties

GetExecuteProperties returns the execute properties

func (ScanProperties) IsReverse

func (s ScanProperties) IsReverse() bool

IsReverse returns true if this is a reverse scan

func (ScanProperties) WithExecuteProperties

func (s ScanProperties) WithExecuteProperties(props ExecuteProperties) ScanProperties

WithExecuteProperties returns a copy with the specified execute properties

func (ScanProperties) WithReverse

func (s ScanProperties) WithReverse(reverse bool) ScanProperties

WithReverse returns a copy with the reverse flag set

func (ScanProperties) WithStreamingMode

func (s ScanProperties) WithStreamingMode(mode CursorStreamingMode) ScanProperties

WithStreamingMode returns a copy with the specified streaming mode

type SlidingWindowCorruptionError

type SlidingWindowCorruptionError struct {
	IndexName string
	Message   string
}

SlidingWindowCorruptionError reports sliding-window bookkeeping that cannot be true of any consistent window — a missing boundary pointer where the count says there must be one, or a boundary entry key shorter than the window key.

Matches Java's RecordCoreException with INDEX_NAME log info; it is a distinct Go type so callers can tell corrupt bookkeeping apart from an ordinary read failure.

func (*SlidingWindowCorruptionError) Error

type SlidingWindowDeleteWhereError

type SlidingWindowDeleteWhereError struct {
	IndexName string
	Message   string
}

SlidingWindowDeleteWhereError reports a deleteRecordsWhere the sliding window cannot serve. Matches Java's Query.InvalidExpressionException ("deleteRecordsWhere not supported by index X") raised when canDeleteWhere returns false.

func (*SlidingWindowDeleteWhereError) Error

type SplitKeyExpression

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

SplitKeyExpression takes a key expression producing multiple single-element results (via FanOut) and splits them into fixed-size batches. Each batch concatenates splitSize values into one tuple. For example, 12 values with splitSize 3 produces 4 tuples of 3 elements each.

The joined expression must have column size 1 and must create duplicates (e.g. FanOut on a repeated field). The total number of values must be an even multiple of splitSize.

Matches Java's com.apple.foundationdb.record.metadata.expressions.SplitKeyExpression.

func Split

func Split(joined KeyExpression, splitSize int) *SplitKeyExpression

Split creates a SplitKeyExpression that batches the joined expression's results into groups of splitSize. splitSize must be > 0.

func (*SplitKeyExpression) ColumnSize

func (s *SplitKeyExpression) ColumnSize() int

ColumnSize returns the split size — each batch produces splitSize columns. Matches Java's SplitKeyExpression.getColumnSize().

func (*SplitKeyExpression) Evaluate

func (s *SplitKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], msg proto.Message) ([][]any, error)

Evaluate evaluates the joined expression and splits the results into batches of splitSize. Each result from the joined expression must be a single-element tuple (from FanOut). The values are flattened, then chunked.

Returns an error if the total number of values is not evenly divisible by splitSize, matching Java's RecordCoreException behavior.

func (*SplitKeyExpression) FieldNames

func (s *SplitKeyExpression) FieldNames() []string

FieldNames returns the field names accessed by the joined expression.

func (*SplitKeyExpression) GetJoined

func (s *SplitKeyExpression) GetJoined() KeyExpression

GetJoined returns the underlying joined key expression.

func (*SplitKeyExpression) GetSplitSize

func (s *SplitKeyExpression) GetSplitSize() int

GetSplitSize returns the number of elements per batch.

func (*SplitKeyExpression) ToKeyExpression

func (s *SplitKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes SplitKeyExpression to proto. Matches Java's SplitKeyExpression.toKeyExpression().

type StaleMetaDataVersionError

type StaleMetaDataVersionError struct {
	LocalVersion  int
	StoredVersion int
}

StaleMetaDataVersionError is returned when the stored metadata version is newer than the local metadata version, meaning another instance already evolved the schema. Matches Java's RecordStoreStaleMetaDataVersionException.

func (*StaleMetaDataVersionError) Error

func (e *StaleMetaDataVersionError) Error() string

type StartContinuation

type StartContinuation struct{}

StartContinuation represents the start of a cursor's iteration, or a state where no continuation is available for the current result. Unlike EndContinuation, this is NOT terminal — the cursor may have more data. Matches Java's RecordCursorStartContinuation.

func (*StartContinuation) IsEnd

func (c *StartContinuation) IsEnd() bool

IsEnd returns false — StartContinuation is never an end state.

func (*StartContinuation) ToBytes

func (c *StartContinuation) ToBytes() ([]byte, error)

ToBytes returns nil (no position information available).

type StatisticsReadRefusal

type StatisticsReadRefusal string

StatisticsReadRefusal names WHY a statistics read produced no usable set.

It exists because a bool cannot: ReadStatisticsAt has seven distinct ways to decline, and collapsing them into ok=false made the refusals INDISTINGUISHABLE TO TESTS. Two separate checks were found dead that way -- each pinned by a spec asserting ok==false, each silently superseded by a later check that refused the same fixture first, and nothing went red at the moment either was superseded. Fixtures cannot fix that: an undecodable entry has no stamps BY CONSTRUCTION, so no fixture can make the decode arm the only thing able to refuse it.

Asserting the REASON makes the mutation matrix diagonal by construction, and means the next arm added cannot inherit an older arm's test.

const (
	// StatisticsReadOK — a usable set.
	StatisticsReadOK StatisticsReadRefusal = ""
	// StatisticsReadAbsent — nothing is stored here at all: no header AND no
	// entries. The ordinary "never collected" case, and the ONLY refusal that
	// means the store is empty.
	StatisticsReadAbsent StatisticsReadRefusal = "no statistics stored"
	// StatisticsReadHeaderMissing — per-type entries survive but the header does
	// not. DISTINCT from Absent: something IS stored, and reporting it as
	// absence tells an operator the store is empty while it holds orphaned
	// entries that a collect would clear. The header is what makes a set
	// vouchable, so entries without one are torn, not missing.
	StatisticsReadHeaderMissing StatisticsReadRefusal = "entries present with no header"
	// StatisticsReadUndecodableKey — an entry key this build cannot parse.
	StatisticsReadUndecodableKey StatisticsReadRefusal = "entry key could not be decoded"
	// StatisticsReadUnknownIntegerKey — an integer key that is not THE header;
	// record-type names are strings, so this is corruption or a newer layout.
	StatisticsReadUnknownIntegerKey StatisticsReadRefusal = "unknown integer entry key"
	// StatisticsReadNonStringKey — a key element that is neither the header
	// integer nor a record-type string.
	StatisticsReadNonStringKey StatisticsReadRefusal = "entry key is neither header nor record type"
	// StatisticsReadUndecodableValue — an entry value this build cannot decode.
	StatisticsReadUndecodableValue StatisticsReadRefusal = "entry value could not be decoded"
	// StatisticsReadCountMismatch — more or fewer entries than the header says
	// the write put down.
	StatisticsReadCountMismatch StatisticsReadRefusal = "entry count disagrees with the header"
	// StatisticsReadVersionMismatch — an entry carrying a version that is not
	// the header's, so it came from a different collection run.
	StatisticsReadVersionMismatch StatisticsReadRefusal = "entry version disagrees with the header"
	// StatisticsReadTimestampMismatch — same, for the timestamp. SPLIT from the
	// version case deliberately: they are two arms of one condition, and a
	// single shared reason would leave their specs unable to tell each other
	// apart -- which is the exact confound this type exists to remove. The
	// timestamp arm shipped undriven once already because the only spec covering
	// the pair moved the version.
	StatisticsReadTimestampMismatch StatisticsReadRefusal = "entry timestamp disagrees with the header"
)

type StatisticsSubspace

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

StatisticsSubspace addresses collected statistics for record stores under one root. The root MUST be outside every record store's subspace, and for a tenant-scoped deployment must be inside the tenant.

func NewStatisticsSubspace

func NewStatisticsSubspace(root subspace.Subspace) StatisticsSubspace

NewStatisticsSubspace returns statistics addressing rooted at root.

type StoreBuilder

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

StoreBuilder builds an FDBRecordStore with configuration options. This follows the builder pattern from Java exactly.

func NewStoreBuilder

func NewStoreBuilder() *StoreBuilder

NewStoreBuilder creates a new store builder

func (*StoreBuilder) Build

func (b *StoreBuilder) Build() (*FDBRecordStore, error)

Build returns a store without checking database state (advanced use case)

func (*StoreBuilder) Create

func (b *StoreBuilder) Create() (*FDBRecordStore, error)

Create creates a new record store, fails if store already exists

func (*StoreBuilder) CreateOrOpen

func (b *StoreBuilder) CreateOrOpen() (*FDBRecordStore, error)

CreateOrOpen creates store if it doesn't exist, opens if it does (like Java). When opening an existing store whose metadata version is older than the current metadata, new indexes are automatically rebuilt inline. Matches Java's FDBRecordStore.checkPossiblyRebuild().

func (*StoreBuilder) Open

func (b *StoreBuilder) Open() (*FDBRecordStore, error)

Open opens an existing record store, fails if store doesn't exist. When the current metadata version is higher than the stored version, new indexes are automatically rebuilt inline (matching Java's checkVersion flow).

func (*StoreBuilder) SetAssumeAllIndexesReadable

func (b *StoreBuilder) SetAssumeAllIndexesReadable(assume bool) *StoreBuilder

SetAssumeAllIndexesReadable pre-populates an empty indexStates map during Build(), making ensureStoreStateLoaded() a complete no-op (zero FDB reads, zero lazy-load). Safe when CreateOrOpen ran at startup and all indexes are known to be READABLE. This is an explicit opt-in for maximum performance in the Build() path.

func (*StoreBuilder) SetBypassFullStoreLockReason

func (b *StoreBuilder) SetBypassFullStoreLockReason(reason string) *StoreBuilder

SetBypassFullStoreLockReason sets a reason string that, if it matches the stored FULL_STORE lock reason exactly, allows the store to be opened despite the lock. This is intended for recovery operations. Calling it with "" bypasses a lock whose stored reason is empty — Java's bypass is a @Nullable String compared with equals(), so the empty string is a valid bypass value, distinct from "no bypass" (the field's nil default). Matches Java's FDBRecordStore.Builder.setBypassFullStoreLockReason().

func (*StoreBuilder) SetContext

func (b *StoreBuilder) SetContext(ctx *FDBRecordContext) *StoreBuilder

SetContext sets the record context

func (*StoreBuilder) SetDatabase

func (b *StoreBuilder) SetDatabase(db *FDBDatabase) *StoreBuilder

SetDatabase sets the database for inheriting the store state cache. Matches Java's FDBRecordStore.Builder.setDatabase().

func (*StoreBuilder) SetFormatVersion

func (b *StoreBuilder) SetFormatVersion(version int32) *StoreBuilder

SetFormatVersion pins the format version this store opens at, instead of defaulting to the newest version this binary knows. An explicit 0 is an ERROR, not a request for the default -- see validateBuilder.

Port of Java's FDBRecordStoreBase.BaseBuilder.setFormatVersion (FDBRecordStoreBase.java:2245, :2266). Its javadoc gives the reason the value has to be a builder property rather than a constant: during a rolling upgrade you arrange "for setFormatVersion to be called with the OLD format version" on every instance, so no upgraded instance starts writing a layout the not-yet-upgraded ones cannot read. A store that always jumped to its binary's newest version would make a staged rollout impossible.

The version is a CEILING, never a downgrade: a store whose header is already past it keeps what it has, because the data on disk may already be in that layout. Values outside [formatVersionMinimum, formatVersionMaxSupported] are rejected when the store is opened.

func (*StoreBuilder) SetIndexRebuildPolicy

func (b *StoreBuilder) SetIndexRebuildPolicy(policy IndexRebuildPolicy) *StoreBuilder

SetIndexRebuildPolicy sets the policy for rebuilding indexes during store open when the metadata version changes. If not set, DefaultIndexRebuildPolicy is used (inline rebuild for ≤200 records, DISABLED otherwise). Matches Java's FDBRecordStore.newBuilder().setUserVersionChecker().

func (*StoreBuilder) SetMetaDataProvider

func (b *StoreBuilder) SetMetaDataProvider(metaData *RecordMetaData) *StoreBuilder

SetMetaDataProvider sets the metadata

func (*StoreBuilder) SetSkipPossiblyRebuild

func (b *StoreBuilder) SetSkipPossiblyRebuild(skip bool) *StoreBuilder

SetSkipPossiblyRebuild disables automatic index rebuild checks during Open/CreateOrOpen. When set, the store will not call checkPossiblyRebuild even if the metadata version changed. This is used by OnlineIndexer which manages index states independently. Matches Java's IndexMaintenanceFilter.NONE behavior.

func (*StoreBuilder) SetStoreStateCache

func (b *StoreBuilder) SetStoreStateCache(cache FDBRecordStoreStateCache) *StoreBuilder

SetStoreStateCache sets a per-store cache override. If not set, the database's cache is used. Matches Java's FDBRecordStore.Builder.setStoreStateCache().

func (*StoreBuilder) SetSubspace

func (b *StoreBuilder) SetSubspace(subspace subspace.Subspace) *StoreBuilder

SetSubspace sets the subspace for this store

type StoreExistenceCheck

type StoreExistenceCheck int

StoreExistenceCheck controls how store existence is validated during open. Matches Java's FDBRecordStoreBase.StoreExistenceCheck.

const (
	// ExistenceCheckNone performs no existence check.
	ExistenceCheckNone StoreExistenceCheck = iota
	// ExistenceCheckErrorIfExists fails if the store already exists.
	ExistenceCheckErrorIfExists
	// ExistenceCheckErrorIfNotExists fails if the store does not exist.
	ExistenceCheckErrorIfNotExists
)

type StoreIsFullyLockedError

type StoreIsFullyLockedError struct {
	Reason    string
	Timestamp int64
}

StoreIsFullyLockedError is returned when attempting to open a store with FULL_STORE lock state without providing the correct bypass reason. Matches Java's com.apple.foundationdb.record.StoreIsFullyLockedException.

func (*StoreIsFullyLockedError) Error

func (e *StoreIsFullyLockedError) Error() string

type StoreIsLockedForRecordUpdatesError

type StoreIsLockedForRecordUpdatesError struct {
	Reason    string
	Timestamp int64
}

StoreIsLockedForRecordUpdatesError is returned when attempting to modify records in a store with FORBID_RECORD_UPDATE lock state. Matches Java's com.apple.foundationdb.record.StoreIsLockedForRecordUpdates.

func (*StoreIsLockedForRecordUpdatesError) Error

type StoreRecordFunction

type StoreRecordFunction struct {
	Name string
}

StoreRecordFunction identifies a function to be evaluated on a record using store context (not index-backed). Matches Java's com.apple.foundationdb.record.metadata.StoreRecordFunction.

type StoreStatistics

type StoreStatistics struct {
	// PerType maps record type name to its statistic. A type ABSENT here has
	// no statistic — which is a normal outcome, never an implied zero.
	PerType map[string]RecordTypeStatistic
	// CollectedAtVersion / CollectedAtUnixNanos are the run's own stamps, from
	// the header entry.
	CollectedAtVersion   int64
	CollectedAtUnixNanos int64
}

StoreStatistics is everything collected for one record store.

func ReadStatistics

func ReadStatistics(
	ctx context.Context,
	db *FDBDatabase,
	stats StatisticsSubspace,
	storeSubspace subspace.Subspace,
) (StoreStatistics, bool, error)

ReadStatistics returns the statistics collected for one store, or ok=false if there are none. It is a SNAPSHOT read: a planner read must never add a conflict range, or planning could make a transaction retry.

Absent, malformed and unknown-format all return ok=false. There is no partial success: a caller gets a usable set or nothing.

func ReadStatisticsAt

func ReadStatisticsAt(
	ctx context.Context,
	db *FDBDatabase,
	stats StatisticsSubspace,
	storeSubspace subspace.Subspace,
	tags ...string,
) (StoreStatistics, bool, int64, error)

ReadStatisticsAt is ReadStatisticsAtWithRefusal reduced to a boolean, for callers that only need to know whether the set is usable.

Every caller that DISTINGUISHES refusals must use the WithRefusal form. A bool cannot tell apart the eight ways this read declines, and FOUR checks were found dead precisely because their specs asserted ok==false: a later check refused the same fixture first, and the earlier one stopped being exercised with nothing going red at the moment it happened.

That applies to callers that REPORT a refusal too, not only to tests. The relational gate used this wrapper and therefore told operators "not collected" for every torn set -- an earlier draft of this very comment said "every caller that ASSERTS on a refusal", and its only non-test caller was doing exactly that while not asserting, so the wording excused it.

type StoreTimer

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

StoreTimer collects instrumentation counters for Record Layer operations. All operations are goroutine-safe. A nil *StoreTimer is safe to use (all methods are no-ops on nil receiver).

func NewStoreTimer

func NewStoreTimer() *StoreTimer

NewStoreTimer creates a new StoreTimer.

func (*StoreTimer) Add

func (t *StoreTimer) Add(other *StoreTimer)

Add folds every counter of other into this timer, matching Java's StoreTimer.add (StoreTimer.java:731-740).

This is Java's mechanism for aggregating instrumentation collected by a short-lived timer into a longer-lived one. It is NOT the only one, and not the one the SQL layer uses: Java's other model shares a single timer instance across many contexts (FDBDatabaseRunner.setTimer feeds every context the retry loop opens, FDBDatabaseRunner.java:105-119), which needs no fold at all because the contexts write into the same counters. Go's FDBDatabase-level timer follows that second model. Add exists for callers doing the first — measuring one unit of work in a scratch timer and rolling it up afterwards.

Counters are read one at a time, so a timer being concurrently written to does not tear an individual counter but may contribute a mix of pre- and post-write values across counters. Java has exactly the same property.

func (*StoreTimer) GetCount

func (t *StoreTimer) GetCount(event Event) int64

GetCount returns the occurrence count for the given event (0 if never recorded).

func (*StoreTimer) GetCounter

func (t *StoreTimer) GetCounter(event Event) *Counter

GetCounter returns the counter for the given event, or nil if never recorded.

func (*StoreTimer) GetTimeNanos

func (t *StoreTimer) GetTimeNanos(event Event) int64

GetTimeNanos returns the cumulative nanoseconds for the given event (0 if never recorded).

func (*StoreTimer) Increment

func (t *StoreTimer) Increment(event Event)

Increment adds one occurrence of event and leaves its cumulative value at zero. That a count event carries no duration is load-bearing, not incidental: Java uses getTimeNanos()==0 as the proof that an event is a counter rather than a timer (RecordLayerMetricCollector.java:90), and the exporter renders any non-zero cumulative value as a duration in seconds. See Counter.Increment.

func (*StoreTimer) IncrementBy

func (t *StoreTimer) IncrementBy(event Event, amount int64)

IncrementBy adds amount occurrences of event, leaving its cumulative value at zero (see Increment). For a KindSize event the amount IS the byte total, which is why it accumulates in Count() — the same place Java keeps it.

func (*StoreTimer) KeysAndValues

func (t *StoreTimer) KeysAndValues() map[string]int64

KeysAndValues renders the timer in Java's log-key form — the map StoreTimer.getKeysAndValues produces (StoreTimer.java:747-780) and feeds to KeyValueLogMessage.

The suffix rules are Java's, verbatim: every event emits `<name>_count`; a SizeEvent additionally emits `<name>_size` carrying the summed magnitudes; and a timed event additionally emits `<name>_micros` carrying nanoseconds divided by 1000 with integer truncation. A Count — including a size Count — emits only `_count`, because its cumulative value is 0 by construction.

The SizeEvent arm comes FIRST in Java (StoreTimer.java:755-759) and so does it here: Java tests `instanceof SizeEvent` before `instanceof Count`, so a SizeEvent gets `_size` even though its cumulative value would also satisfy the timed branch's shape.

This is the LOGGING surface and is deliberately distinct from the Prometheus exposition in pkg/recordlayer/rlmetrics, which speaks that ecosystem's conventions (base units, `_total`, summaries) instead of Java's log keys. Both read the same counters; neither is a re-encoding of the other.

func (*StoreTimer) Record

func (t *StoreTimer) Record(event Event, timeNanos int64)

Record records an elapsed time in nanoseconds for the given event.

func (*StoreTimer) RecordSince

func (t *StoreTimer) RecordSince(event Event, startTime time.Time)

RecordSince records the duration elapsed since startTime for the given event.

func (*StoreTimer) RecordSize

func (t *StoreTimer) RecordSize(event Event, size int64)

RecordSize records one observation of a magnitude — Java's StoreTimer.recordSize (StoreTimer.java:569-571), which is Counter.record: the SIZE accumulates in CumulativeValue and the OBSERVATION in Count.

Deliberately not Increment: a KindSizeDistribution event is asked "how big was it each time", and folding the magnitude into Count would answer a question nobody asked with a number that looks like an answer to this one.

func (*StoreTimer) Reset

func (t *StoreTimer) Reset()

Reset clears all counters from the timer.

func (*StoreTimer) Snapshot

func (t *StoreTimer) Snapshot() map[string]*CounterSnapshot

Snapshot returns an immutable snapshot of all counters at the current instant. The returned map is keyed by Event.Name.

type SubspaceSplitter

type SubspaceSplitter interface {
	SubspaceOf(keyBytes []byte) (subspace.Subspace, error)
	SubspaceTag(ss subspace.Subspace) (tuple.Tuple, error)
}

SubspaceSplitter determines which sub-map a given FDB key belongs to. Used by BunchedMapMultiIterator to partition scans across multiple BunchedMaps. Matches Java's com.apple.foundationdb.map.SubspaceSplitter.

type SynchronizedSessionLockedError

type SynchronizedSessionLockedError struct {
	ExistingIndexerID string
	ExistingInfo      string
	HeartbeatAgeMs    int64
	LeaseLengthMs     int64
}

SynchronizedSessionLockedError is returned when a non-mutual indexer detects an active heartbeat from another indexer process. Matches Java's SynchronizedSessionLockedException.

func (*SynchronizedSessionLockedError) Error

type SyntheticRecordTypesNotModeledError

type SyntheticRecordTypesNotModeledError struct {
	// TypeNames are the synthetic declarations found, so a caller can say which.
	//
	// STORAGE names -- and unlike every other name field outside
	// StatisticsStatus, they stay that way all the way out: Error() prints them
	// verbatim too, for the reason given there. A programmatic consumer matching
	// against metadata needs them in the namespace metadata uses, and here so
	// does the human.
	TypeNames []string
}

SyntheticRecordTypesNotModeledError reports metadata declaring JOINED or UNNESTED record types, which this port carries opaquely rather than modelling.

Collection refuses such a schema before scanning. RecordTypes() omits those declarations, so a run would read every record in the store to produce a set that is a PARTIAL view of the schema by construction — and would then write a header vouching for it. Completeness is undecidable against a partial set, so there is no reading of that outcome a planner could safely use.

func (*SyntheticRecordTypesNotModeledError) Error

Error renders the refusal for a human, naming the declarations EXACTLY as the metadata stores them.

The reason is the PRIOR on what produced the string -- not the domain, and not proof of provenance. Both kinds of name end up as protobuf message names (Java's SyntheticRecordTypeBuilder.buildDescriptor sets the synthetic one via DescriptorProto.setName, so it is validated exactly like a record type's), so the domain does not separate them; and DecodeOnceIfReversible is a pure function of the string, so its round-trip test would run identically on either. What differs is what could have written the name:

  • A record type declared through SQL DDL passes through protoname.ToProtoBufCompliantName. Among stored names that round-trip, escaped ones are therefore the case worth serving, and decoding is the best available reading. It is NOT proof -- SetRecords copies descriptor names verbatim -- which is exactly why that guard exists instead of an unconditional decode.

  • Nothing escapes into a synthetic name. Java's RecordMetaDataBuilder.addJoinedRecordType / addUnnestedRecordType store the string their caller passed, and this port never CREATES one; it only round-trips what Java wrote. So a synthetic MY__1JOINED that round-trips is not evidence of escaping, and decoding it would invent MY$JOINED -- a declaration the operator cannot find.

That matters most precisely here: this refusal exists because the port does not model these types, so the only thing an operator can do with the name is match it against their Java-side metadata, which holds the verbatim string.

The list arrives sorted by SyntheticRecordTypeNames and stays in that order; with no decoding step there are not two namespaces to disagree about.

type TagValidationError

type TagValidationError struct {
	Message string
	// Tag is the offending tag for a length violation, empty for a count violation.
	Tag string
}

TagValidationError reports a tag set rejected by the record layer's limits. Java throws IllegalArgumentException with these exact messages.

func (*TagValidationError) Error

func (e *TagValidationError) Error() string

type TakeoverType

type TakeoverType int

TakeoverType represents allowed build method conversions when resuming an index build that was started by a different indexing method. Matches Java's OnlineIndexer.IndexingPolicy.TakeoverTypes.

const (
	// TakeoverMultiTargetToSingle allows a single-target BY_RECORDS build to continue
	// a MULTI_TARGET_BY_RECORDS build.
	TakeoverMultiTargetToSingle TakeoverType = iota
	// TakeoverMutualToSingle allows a single-target BY_RECORDS build to continue
	// a MUTUAL_BY_RECORDS build.
	TakeoverMutualToSingle
	// TakeoverByRecordsToMutual allows a MUTUAL_BY_RECORDS build to continue
	// a BY_RECORDS or MULTI_TARGET_BY_RECORDS build.
	TakeoverByRecordsToMutual
)

type TextIndexBunchedSerializer

type TextIndexBunchedSerializer struct{}

TextIndexBunchedSerializer serializes Tuple keys and position lists ([]int) for TEXT indexes. Position lists are delta-compressed using base-128 variable-length integer encoding. Wire-compatible with Java's com.apple.foundationdb.record.provider.foundationdb.indexes.TextIndexBunchedSerializer.

func TextIndexBunchedSerializerInstance

func TextIndexBunchedSerializerInstance() *TextIndexBunchedSerializer

TextIndexBunchedSerializerInstance returns the singleton serializer.

func (*TextIndexBunchedSerializer) CanAppend

func (s *TextIndexBunchedSerializer) CanAppend() bool

CanAppend returns true — this format supports appending entries without re-serialization.

func (*TextIndexBunchedSerializer) DeserializeEntries

func (s *TextIndexBunchedSerializer) DeserializeEntries(key tuple.Tuple, data []byte) ([]BunchedEntry[tuple.Tuple, []int], error)

DeserializeEntries deserializes a bunch of entries from data, using key as the first entry's key. Matches Java's TextIndexBunchedSerializer.deserializeEntries().

func (*TextIndexBunchedSerializer) DeserializeKey

func (s *TextIndexBunchedSerializer) DeserializeKey(data []byte, offset, length int) (tuple.Tuple, error)

DeserializeKey unpacks a Tuple key from data[offset:offset+length]. Matches Java's TextIndexBunchedSerializer.deserializeKey().

func (*TextIndexBunchedSerializer) DeserializeKeys

func (s *TextIndexBunchedSerializer) DeserializeKeys(key tuple.Tuple, data []byte) ([]tuple.Tuple, error)

DeserializeKeys deserializes only the keys from a bunch (skipping values for efficiency). Matches Java's TextIndexBunchedSerializer.deserializeKeys().

func (*TextIndexBunchedSerializer) SerializeEntries

func (s *TextIndexBunchedSerializer) SerializeEntries(entries []BunchedEntry[tuple.Tuple, []int]) ([]byte, error)

SerializeEntries packs an entry list into a single byte array. Format: PREFIX + entries (first entry's key omitted). Matches Java's TextIndexBunchedSerializer.serializeEntries().

func (*TextIndexBunchedSerializer) SerializeEntry

func (s *TextIndexBunchedSerializer) SerializeEntry(key tuple.Tuple, value []int) ([]byte, error)

SerializeEntry packs a single key-value pair (for appending to a bunch). Format: varInt(keyLen) + keyBytes + varInt(listSize) + deltaCompressedList Matches Java's TextIndexBunchedSerializer.serializeEntry().

func (*TextIndexBunchedSerializer) SerializeKey

func (s *TextIndexBunchedSerializer) SerializeKey(key tuple.Tuple) []byte

SerializeKey packs a key using standard Tuple encoding. Matches Java's TextIndexBunchedSerializer.serializeKey().

type TextSubspaceSplitter

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

TextSubspaceSplitter splits TEXT index keys by grouping columns. Given an FDB key in the index subspace, extracts the first groupingColumns elements as the subspace prefix for that token's BunchedMap. Matches Java's TextSubspaceSplitter.

func NewTextSubspaceSplitter

func NewTextSubspaceSplitter(indexSubspace subspace.Subspace, groupingColumns int) *TextSubspaceSplitter

NewTextSubspaceSplitter creates a splitter for TEXT index scans.

func (*TextSubspaceSplitter) SubspaceOf

func (s *TextSubspaceSplitter) SubspaceOf(keyBytes []byte) (subspace.Subspace, error)

SubspaceOf extracts the subspace for a given FDB key by taking the first groupingColumns elements of the unpacked tuple.

func (*TextSubspaceSplitter) SubspaceTag

func (s *TextSubspaceSplitter) SubspaceTag(ss subspace.Subspace) (tuple.Tuple, error)

SubspaceTag returns the grouping key tuple for a subspace.

type TextTokenizer

type TextTokenizer interface {
	// Name returns the tokenizer's name. Matches Java's getName().
	Name() string

	// Tokenize returns an iterator of tokens from the input text.
	// Returns an error if the version is out of bounds.
	// Matches Java's tokenize(String, int, TokenizerMode).
	Tokenize(text string, version int, mode TokenizerMode) (TokenIterator, error)

	// TokenizeToMap returns a map from token to position list (0-indexed).
	// Returns an error if the version is out of bounds.
	// Matches Java's tokenizeToMap(String, int, TokenizerMode).
	TokenizeToMap(text string, version int, mode TokenizerMode) (map[string][]int, error)

	// TokenizeToList returns all tokens as a list.
	// Returns an error if the version is out of bounds.
	// Matches Java's tokenizeToList(String, int, TokenizerMode).
	TokenizeToList(text string, version int, mode TokenizerMode) ([]string, error)

	// MaxVersion returns the maximum supported tokenizer version.
	MaxVersion() int

	// MinVersion returns the minimum supported tokenizer version.
	MinVersion() int
}

TextTokenizer tokenizes text fields for full-text indexes. Matches Java's com.apple.foundationdb.record.provider.common.text.TextTokenizer.

func GetTextTokenizer

func GetTextTokenizer(name string) (TextTokenizer, error)

GetTextTokenizer returns the tokenizer with the given name from the global registry. If name is empty, returns the default tokenizer.

type TextTokenizerFactory

type TextTokenizerFactory interface {
	// Name returns the tokenizer name.
	Name() string
	// GetTokenizer returns a tokenizer instance.
	GetTokenizer() TextTokenizer
}

TextTokenizerFactory creates instances of a TextTokenizer. Matches Java's TextTokenizerFactory.

type TextTokenizerRegistry

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

TextTokenizerRegistry manages TextTokenizer instances by name. Matches Java's TextTokenizerRegistry / TextTokenizerRegistryImpl.

func GlobalTextTokenizerRegistry

func GlobalTextTokenizerRegistry() *TextTokenizerRegistry

GlobalTextTokenizerRegistry returns the singleton tokenizer registry, pre-loaded with the default tokenizer.

func (*TextTokenizerRegistry) GetTokenizer

func (r *TextTokenizerRegistry) GetTokenizer(name string) (TextTokenizer, error)

GetTokenizer returns the tokenizer with the given name. If name is empty, returns the default tokenizer. Returns an error if no tokenizer with that name exists.

func (*TextTokenizerRegistry) Register

func (r *TextTokenizerRegistry) Register(factory TextTokenizerFactory) error

Register adds a tokenizer factory to the registry. Returns an error if a different factory with the same name is already registered.

func (*TextTokenizerRegistry) Reset

func (r *TextTokenizerRegistry) Reset()

Reset clears and reinitializes the registry with only the default tokenizer.

type TimeLimitExceededError

type TimeLimitExceededError struct {
	TimeLimit time.Duration
	Elapsed   time.Duration
}

TimeLimitExceededError is returned when the OnlineIndexer's overall time limit is exceeded. Matches Java's com.apple.foundationdb.record.provider.foundationdb.IndexingBase.TimeLimitException.

func (*TimeLimitExceededError) Error

func (e *TimeLimitExceededError) Error() string

type TimeWindowForFunction

type TimeWindowForFunction struct {
	LeaderboardType      int
	LeaderboardTimestamp int64
}

TimeWindowForFunction specifies a leaderboard time window for record/aggregate functions. Matches Java's com.apple.foundationdb.record.provider.foundationdb.leaderboard.TimeWindowForFunction.

type TimeWindowLeaderboardWindowUpdate

type TimeWindowLeaderboardWindowUpdate struct {
	UpdateTimestamp int64
	HighScoreFirst  bool
	DeleteBefore    int64
	AllTime         bool
	Specs           []TimeWindowSpec
	NLevels         int
	Rebuild         TimeWindowRebuild
}

TimeWindowLeaderboardWindowUpdate is an operation to manage active time windows. Matches Java's TimeWindowLeaderboardWindowUpdate.

type TimeWindowRebuild

type TimeWindowRebuild int

TimeWindowRebuild controls when to rebuild the index after adding windows.

const (
	TimeWindowRebuildNever                TimeWindowRebuild = 0
	TimeWindowRebuildAlways               TimeWindowRebuild = 1
	TimeWindowRebuildIfOverlappingChanged TimeWindowRebuild = 2
)

type TimeWindowSpec

type TimeWindowSpec struct {
	Type           int
	BaseTimestamp  int64
	StartIncrement int64
	Duration       int64
	Count          int
}

TimeWindowSpec defines parameters for creating a batch of time windows. Matches Java's TimeWindowLeaderboardWindowUpdate.TimeWindowSpec.

type TokenIterator

type TokenIterator interface {
	// HasNext returns true if there are more tokens.
	HasNext() bool
	// Next returns the next token. Panics if no more tokens.
	Next() string
}

TokenIterator iterates over tokens produced by a TextTokenizer.

type TokenizerMode

type TokenizerMode int

TokenizerMode controls whether tokenization is for indexing or querying. Some tokenizers may behave differently in each mode (e.g., n-gram tokenizers). Matches Java's TextTokenizer.TokenizerMode.

const (
	// TokenizerModeIndex tokenizes for indexing documents.
	TokenizerModeIndex TokenizerMode = iota
	// TokenizerModeQuery tokenizes for query strings.
	TokenizerModeQuery
)

type TransactionPriority

type TransactionPriority int

TransactionPriority controls the priority of FDB transactions. Matches Java's FDBTransactionPriority.

const (
	// PriorityDefault is the default transaction priority.
	PriorityDefault TransactionPriority = iota
	// PriorityBatch is a lower priority for background/batch operations.
	PriorityBatch
	// PrioritySystemImmediate is the highest priority, bypasses throttling.
	// Use with extreme care — only for system-level operations.
	PrioritySystemImmediate
)

type TransactionSizeExceededError

type TransactionSizeExceededError struct {
	CurrentBytes int64
	LimitBytes   int64
}

TransactionSizeExceededError is returned when the approximate transaction size exceeds the configured error threshold. Callers should commit the current transaction and start a new one.

func (*TransactionSizeExceededError) Error

type TransactionSizeWarningError

type TransactionSizeWarningError struct {
	CurrentBytes int64
	LimitBytes   int64
}

TransactionSizeWarningError is returned once per transaction when the approximate size exceeds the configured warning threshold.

func (*TransactionSizeWarningError) Error

type TupleRange

type TupleRange struct {
	Low          tuple.Tuple
	High         tuple.Tuple
	LowEndpoint  EndpointType
	HighEndpoint EndpointType
}

TupleRange specifies a range of tuples for index scanning. Matches Java's com.apple.foundationdb.record.TupleRange.

func TupleRangeAllOf

func TupleRangeAllOf(prefix tuple.Tuple) TupleRange

TupleRangeAllOf returns a range for all entries with the given tuple prefix. Matches Java's TupleRange.allOf(Tuple). For example, TupleRangeAllOf(tuple.Tuple{"alice"}) returns all index entries where the first indexed value is "alice".

func TupleRangeBetween

func TupleRangeBetween(low, high tuple.Tuple) TupleRange

TupleRangeBetween returns a range [low, high) — low inclusive, high exclusive. Matches Java's TupleRange.between(Tuple, Tuple).

func TupleRangeBetweenInclusive

func TupleRangeBetweenInclusive(low, high tuple.Tuple) TupleRange

TupleRangeBetweenInclusive returns a range [low, high] — both inclusive. Matches Java's TupleRange.betweenInclusive(Tuple, Tuple).

func TupleRangePrefixString

func TupleRangePrefixString(token string) TupleRange

TupleRangePrefixString creates a range scanning all entries whose string element starts with the given prefix. Both endpoints use EndpointTypePrefixString so that the tuple-packed trailing null byte is stripped (low) or strinc'd (high). Matches Java's TupleRange.prefixedBy(String).

func VectorDistanceScanRange

func VectorDistanceScanRange(queryVector []float64, k, efSearch int) TupleRange

VectorDistanceScanRange creates a TupleRange encoding a BY_DISTANCE kNN query. This is the Go equivalent of Java's VectorIndexScanBounds. The query vector, k, and efSearch are encoded into TupleRange fields so they can be passed through the standard ScanIndexByType API.

Usage:

store.ScanIndexByType(index, IndexScanByDistance,
    VectorDistanceScanRange(queryVec, 10, 200),
    nil, ForwardScan)

func VectorDistanceScanRangeOrdered

func VectorDistanceScanRangeOrdered(queryVector []float64, k, efSearch, cRerank int, prefix tuple.Tuple) TupleRange

VectorDistanceScanRangeOrdered builds a BY_DISTANCE scan range for the RFC-156 Phase B distance-ORDERED stream. It DECOUPLES the re-rank budget from the probe width: cRerank (the re-rank budget c) rides the High-tuple's c slot (index 3 of the SPFresh (k, kc, w, c, ε) contract), while efSearch stays the index's TUNED probe width (SPFresh kc=64, HNSW ef) rather than being forced up to the horizon. Threading c directly avoids the efSearch>0 path's 4×k re-rank inflation and kc override (rejected in Phase B review). The intermediate w slot is 0 ("use the index default fine-probe width"). HNSW reads only (k, efSearch) and ignores the extra slots.

Who reads cRerank: ONLY the legacy self-limiting one-shot SPFresh path (ScanByDistance → searchCurrentGeneration, where c bounds the finalize re-rank). The STREAMING ordered path (IndexScanByDistanceOrderedStream → newOrderedStreamCursor) does NOT read slot 3 at all — its re-rank/candidate cap is the demand-driven stream budget (spfreshDefaultStreamCandidateBudget=4000 in defaultSPFreshStreamBudget), NOT this High-tuple slot. So cRerank/slot-3 is currently UNUSED by the streaming path; it is honoured only when this range feeds the one-shot reader, and is otherwise inert (carried for shape symmetry). Kept in the signature so the one-shot path stays expressible without a second builder; do not remove it just because the streaming path ignores it.

func VectorDistanceScanRangeWithPrefix

func VectorDistanceScanRangeWithPrefix(queryVector []float64, k, efSearch int, prefix tuple.Tuple) TupleRange

VectorDistanceScanRangeWithPrefix creates a TupleRange encoding a BY_DISTANCE kNN query scoped to a specific prefix partition. The prefix identifies which independent HNSW graph to search (e.g., a specific group_id value).

Usage:

store.ScanIndexByType(index, IndexScanByDistance,
    VectorDistanceScanRangeWithPrefix(queryVec, 10, 200, tuple.Tuple{int64(42)}),
    nil, ForwardScan)

func (TupleRange) Contains

func (r TupleRange) Contains(t tuple.Tuple) bool

Contains checks if a tuple falls within the range.

func (TupleRange) Prepend

func (r TupleRange) Prepend(prefix tuple.Tuple) TupleRange

Prepend prepends a tuple prefix to both Low and High bounds. Matches Java's TupleRange.prepend(Tuple).

func (TupleRange) ToFDBRange

func (r TupleRange) ToFDBRange(ss subspace.Subspace) fdb.KeyRange

ToFDBRange converts this TupleRange to an fdb.KeyRange relative to a subspace. Matches Java's TupleRange.toRange(Subspace).

type TypedFDBRecordStore

type TypedFDBRecordStore[T proto.Message] struct {
	// contains filtered or unexported fields
}

TypedFDBRecordStore provides type-safe record operations with generics

func GetTypedRecordStore

func GetTypedRecordStore[T proto.Message](store *FDBRecordStore, recordTypeName string) (*TypedFDBRecordStore[T], error)

GetTypedRecordStore creates a type-safe wrapper for a specific record type This follows Java's FDBRecordStore.getTypedRecordStore() pattern

func NewTypedRecordStore

func NewTypedRecordStore[T proto.Message](
	baseStore *FDBRecordStore,
	recordType *RecordType,
	unwrapFunc func(*gen.UnionDescriptor) (T, error),
	wrapFunc func(T) (*gen.UnionDescriptor, error),
) *TypedFDBRecordStore[T]

NewTypedRecordStore creates a new typed record store that wraps the base store

func (*TypedFDBRecordStore[T]) AddRecordReadConflict

func (ts *TypedFDBRecordStore[T]) AddRecordReadConflict(primaryKey tuple.Tuple) error

AddRecordReadConflict adds a read conflict range for the given primary key

func (*TypedFDBRecordStore[T]) AddRecordWriteConflict

func (ts *TypedFDBRecordStore[T]) AddRecordWriteConflict(primaryKey tuple.Tuple) error

AddRecordWriteConflict adds a write conflict range for the given primary key

func (*TypedFDBRecordStore[T]) Context

func (ts *TypedFDBRecordStore[T]) Context() *FDBRecordContext

Context returns the record context this store is using

func (*TypedFDBRecordStore[T]) DeleteAllRecords

func (ts *TypedFDBRecordStore[T]) DeleteAllRecords() error

DeleteAllRecords deletes all records from the store

func (*TypedFDBRecordStore[T]) DeleteRecord

func (ts *TypedFDBRecordStore[T]) DeleteRecord(primaryKey tuple.Tuple) (bool, error)

DeleteRecord deletes a typed record by its primary key

func (*TypedFDBRecordStore[T]) GetRecordCount

func (ts *TypedFDBRecordStore[T]) GetRecordCount() (int64, error)

GetRecordCount returns the total record count

func (*TypedFDBRecordStore[T]) GetSnapshotRecordCount

func (ts *TypedFDBRecordStore[T]) GetSnapshotRecordCount(countKey tuple.Tuple) (int64, error)

GetSnapshotRecordCount returns the count for a specific count key

func (*TypedFDBRecordStore[T]) InsertRecord

func (ts *TypedFDBRecordStore[T]) InsertRecord(record T) (*FDBStoredRecord[T], error)

InsertRecord saves a typed record and throws an error if it already exists

func (*TypedFDBRecordStore[T]) LoadRecord

func (ts *TypedFDBRecordStore[T]) LoadRecord(primaryKey tuple.Tuple) (*FDBStoredRecord[T], error)

LoadRecord loads a record by its primary key with compile-time type safety

func (*TypedFDBRecordStore[T]) RecordExists

func (ts *TypedFDBRecordStore[T]) RecordExists(primaryKey tuple.Tuple, isolationLevel IsolationLevel) (bool, error)

RecordExists checks if a record exists with the given primary key

func (*TypedFDBRecordStore[T]) SaveRecord

func (ts *TypedFDBRecordStore[T]) SaveRecord(record T) (*FDBStoredRecord[T], error)

SaveRecord saves a typed record to the store

func (*TypedFDBRecordStore[T]) SaveRecordWithOptions

func (ts *TypedFDBRecordStore[T]) SaveRecordWithOptions(
	record T,
	existenceCheck RecordExistenceCheck,
) (*FDBStoredRecord[T], error)

SaveRecordWithOptions saves a typed record with existence checking

func (*TypedFDBRecordStore[T]) ScanRecords

func (ts *TypedFDBRecordStore[T]) ScanRecords(continuation []byte, scanProperties ScanProperties) RecordCursor[*FDBStoredRecord[T]]

ScanRecords returns a typed cursor scanning all records of this store's type. Records are auto-filtered to the store's type and type-asserted to T.

func (*TypedFDBRecordStore[T]) Subspace

func (ts *TypedFDBRecordStore[T]) Subspace() subspace.Subspace

Subspace returns the subspace this store is using

func (*TypedFDBRecordStore[T]) UpdateRecord

func (ts *TypedFDBRecordStore[T]) UpdateRecord(record T) (*FDBStoredRecord[T], error)

UpdateRecord saves a typed record and throws an error if it does not already exist or if its type has changed

type UniquenessViolation

type UniquenessViolation struct {
	IndexName   string
	IndexKey    tuple.Tuple
	PrimaryKey  tuple.Tuple
	ExistingKey tuple.Tuple // Conflicting PK stored by the other side (may be nil)
}

UniquenessViolation represents a recorded uniqueness violation entry. Matches Java's RecordIndexUniquenessViolation.

type UnknownIndexTypeError

type UnknownIndexTypeError struct {
	IndexName string
	IndexType string
}

UnknownIndexTypeError is raised when no index maintainer implements an index's type. It is the port of Java's registry miss: IndexMaintainerFactoryRegistryImpl.getIndexMaintainerFactory looks the type up and throws MetaDataException("Unknown index type for " + index) when the lookup returns null (IndexMaintainerFactoryRegistryImpl.java:78-82).

It FAILS CLOSED, and the reason is a wire-format one rather than a tidiness one. Go used to answer an unrecognised type with the STANDARD maintainer — the VALUE-index one. An index whose type this build does not implement was therefore not rejected: it was silently maintained as a value index, writing value-index key bytes into that index's own subspace. Nothing surfaces at write time, because a value index is perfectly happy to serve those writes. The damage appears when a build that DOES implement the type reads that subspace and finds another format's entries, or when Java reads it and finds entries its maintainer never wrote. An index this process cannot maintain must stop the write, not receive a guess.

Unwraps to *MetaDataError so `errors.As` for the general metadata failure matches — the Go equivalent of Java's `catch (MetaDataException)`, which is what callers of the registry actually catch — while a caller that wants the index identity can match this type directly.

func (*UnknownIndexTypeError) Error

func (e *UnknownIndexTypeError) Error() string

func (*UnknownIndexTypeError) Unwrap

func (e *UnknownIndexTypeError) Unwrap() error

Unwrap reports this as a metadata failure. Java's exception IS a MetaDataException; in Go the identity fields need their own struct, so the relationship Java gets from inheritance is spelled out here.

type UnknownOrElseCursorStateError

type UnknownOrElseCursorStateError struct{}

UnknownOrElseCursorStateError is returned when an OrElse continuation carries a state value outside the known OrElseContinuation_State range. Matches Java's OrElseCursor.UnknownOrElseCursorStateException.

func (*UnknownOrElseCursorStateError) Error

type UnknownStoreLockStateError

type UnknownStoreLockStateError struct {
	LockStateValue int32
}

UnknownStoreLockStateError is returned when a store has an unrecognized lock state at FormatVersion >= FULL_STORE_LOCK (14). This prevents opening stores with lock states added by newer versions that we don't understand. Matches Java's com.apple.foundationdb.record.UnknownStoreLockStateException.

func (*UnknownStoreLockStateError) Error

type UnsupportedFeatureForFormatVersionError

type UnsupportedFeatureForFormatVersionError struct {
	Feature         string
	Version         int32
	RequiredVersion int32
}

UnsupportedFeatureForFormatVersionError is returned when a version-gated store feature is used on a store whose header format version predates it.

Java raises a RecordCoreException with LogMessageKeys.FORMAT_VERSION at each such site — e.g. "Store does not support setting a store lock state" (FDBRecordStore.java:3478/:3494), "Store does not support updating record count state" (:3443), "Store does not support incarnation" (:3503/:3517), "cannot access header user fields at current format version" (:3222). The guard exists because these features WRITE new store-header fields: an instance still pinned at the older format can open the store and will simply not understand them, so a lock it cannot see is a lock it will not honour.

func (*UnsupportedFeatureForFormatVersionError) Error

type UnsupportedFormatVersionError

type UnsupportedFormatVersionError struct {
	Version    int32
	MaxVersion int32
}

UnsupportedFormatVersionError is returned when a store header contains a format version higher than the maximum version this code supports. Matches Java's com.apple.foundationdb.record.provider.foundationdb.UnsupportedFormatVersionException.

func (*UnsupportedFormatVersionError) Error

type VectorMetric

type VectorMetric int

VectorMetric identifies a distance metric for HNSW vector search.

const (
	// VectorMetricEuclidean is true L2: sqrt(Σ(a_i−b_i)²). Matches Java's
	// EUCLIDEAN_METRIC (MetricDefinition.EuclideanMetric.distance = Math.sqrt(...)).
	VectorMetricEuclidean VectorMetric = iota
	VectorMetricCosine
	VectorMetricInnerProduct
	// VectorMetricEuclideanSquare is squared L2: Σ(a_i−b_i)² (no sqrt). Matches
	// Java's EUCLIDEAN_SQUARE_METRIC — same KNN ordering as L2, cheaper, but it is
	// NOT a true metric (does not satisfy the triangle inequality). Added at the end
	// so the existing iota values are unchanged (the int is never persisted; configs
	// round-trip via the option string, e.g. "EUCLIDEAN_SQUARE_METRIC").
	VectorMetricEuclideanSquare
)

type VectorQuantizer

type VectorQuantizer interface {
	// Encode quantizes a float64 vector into compact bytes for storage.
	Encode(vector []float64) []byte

	// Distance estimates the distance between a raw query vector and stored
	// quantized bytes. Returns the estimated distance.
	Distance(query []float64, storedBytes []byte, numDimensions int) (float64, error)

	// Decode reconstructs an approximate float64 vector from stored quantized bytes.
	// Used for pairwise distance in the neighbor selection heuristic.
	Decode(storedBytes []byte, numDimensions int) ([]float64, error)

	// GetTypeByte returns the type ordinal byte used as the first byte of encoded data.
	// This is used to dispatch between raw and quantized storage.
	GetTypeByte() byte
}

VectorQuantizer is an optional quantization strategy for HNSW vector storage. When set on HNSWConfig, HNSW uses it for encoding, distance estimation, and vector reconstruction instead of raw float64 storage.

This interface allows quantization implementations (like RaBitQ) to live in separate packages, matching Java's architecture where RaBitQ is in fdb-extensions.

type VectorScanBounds

type VectorScanBounds struct {
	QueryVector []float64 // The query vector for similarity search.
	K           int       // Number of nearest neighbors to return.
	EfSearch    int       // Search exploration factor (0 = auto from K).
}

VectorScanBounds carries the parameters for a BY_DISTANCE kNN scan. Matches Java's VectorIndexScanBounds (query vector, k limit, efSearch, options).

type VectorSearchResult

type VectorSearchResult struct {
	PrimaryKey tuple.Tuple
	Distance   float64
}

VectorSearchResult is a single result from a vector similarity search.

func SearchSPFreshIndex

func SearchSPFreshIndex(store *FDBRecordStore, indexName string, queryVector []float64, k int) ([]VectorSearchResult, error)

SearchSPFreshIndex runs a kNN search against a readable SPFresh index, returning up to k nearest primary keys (with exact, sidecar-re-ranked distances, ascending) to queryVector using the index's default probe budget. It is the SPFresh counterpart to FDBRecordStore.SearchVectorIndex (vector_index_maintainer.go), which type-asserts the HNSW maintainer and rejects SPFresh. Callers needing per-query probe knobs (k, kc, w, c, ε) drive ScanByDistance through the executor instead.

Returns *IndexNotReadableError if the index is not scannable (matching the HNSW SearchVectorIndex contract). NOT purely read-only: like the SPFresh read path it shares (searchCurrentGeneration), a probe that reads a posting at the 4×Lmax+1 cap re-files that posting's split task (the read-path envelope repair) — a write the surrounding transaction commits. On a maintained index no posting is over the cap, so the common path performs no writes.

type VersionKeyExpression

type VersionKeyExpression struct{}

VersionKeyExpression extracts the record version as a Versionstamp tuple element. Used by VERSION indexes to index records by their commit version. Matches Java's com.apple.foundationdb.record.metadata.expressions.VersionKeyExpression.

func VersionKey

func VersionKey() *VersionKeyExpression

VersionKey creates a key expression that evaluates to the record's version. Matches Java's Key.Expressions.version().

func (*VersionKeyExpression) ColumnSize

func (v *VersionKeyExpression) ColumnSize() int

ColumnSize returns 1 — a version expression produces a single tuple element.

func (*VersionKeyExpression) Evaluate

func (v *VersionKeyExpression) Evaluate(record *FDBStoredRecord[proto.Message], _ proto.Message) ([][]any, error)

Evaluate returns the record's version as a Versionstamp. For complete versions, returns a complete tuple.Versionstamp. For incomplete versions, returns an incomplete tuple.Versionstamp (with placeholder bytes). If record or version is nil, returns [[nil]]. Matches Java's VersionKeyExpression.evaluateMessage().

func (*VersionKeyExpression) FieldNames

func (v *VersionKeyExpression) FieldNames() []string

FieldNames returns an empty slice — version expressions don't access proto fields.

func (*VersionKeyExpression) ToKeyExpression

func (v *VersionKeyExpression) ToKeyExpression() *gen.KeyExpression

ToKeyExpression serializes a VersionKeyExpression to proto. Matches Java's VersionKeyExpression.toKeyExpression().

type VersionMutationType

type VersionMutationType int

VersionMutationType represents the type of versionstamp mutation. Matches Java's MutationType used in FDBRecordContext.addVersionMutation().

const (
	// MutationTypeSetVersionstampedValue queues a SET_VERSIONSTAMPED_VALUE mutation.
	MutationTypeSetVersionstampedValue VersionMutationType = iota
	// MutationTypeSetVersionstampedKey queues a SET_VERSIONSTAMPED_KEY mutation.
	MutationTypeSetVersionstampedKey
)

type WeakReadSemantics

type WeakReadSemantics struct {
	// MinVersion is the minimum read version acceptable. If the cached read
	// version is >= MinVersion, it will be reused without a GRV round-trip.
	MinVersion int64

	// StalenessBoundMillis is the maximum staleness (in ms) of a cached read
	// version. If the cached version is older than this, a fresh GRV is fetched.
	StalenessBoundMillis int64

	// IsCausalReadRisky sets the FDB_TR_OPTION_CAUSAL_READ_RISKY flag.
	// This allows the transaction to read from any storage replica, not
	// just the one with the latest committed data.
	IsCausalReadRisky bool
}

WeakReadSemantics configures relaxed read consistency for a transaction. When provided, the transaction may read at a slightly stale version, reducing GRV latency at the cost of freshness. Matches Java's FDBDatabase.WeakReadSemantics.

Source Files

Directories

Path Synopsis
Package chaos provides model-based chaos testing for the FDB Record Layer.
Package chaos provides model-based chaos testing for the FDB Record Layer.
Package keyspace provides a logical directory tree abstraction over FDB.
Package keyspace provides a logical directory tree abstraction over FDB.
Package protoname is the protobuf identifier escaping shared by every descriptor emitter in the tree, and the DISPLAY policy that decides when an escaped name may safely be shown as the SQL identifier it came from.
Package protoname is the protobuf identifier escaping shared by every descriptor emitter in the tree, and the DISPLAY policy that decides when an escaped name may safely be shown as the SQL identifier it came from.
query
executor
Package executor bridges RecordQueryPlan trees (Cascades planner output) and the FDBRecordStore scanning API to produce RecordCursor[QueryResult] streams.
Package executor bridges RecordQueryPlan trees (Cascades planner output) and the FDBRecordStore scanning API to produce RecordCursor[QueryResult] streams.
plan/cascades
derivations_evaluator.go — evaluates DerivationsProperty for physical plan wrapper expressions.
derivations_evaluator.go — evaluates DerivationsProperty for physical plan wrapper expressions.
plan/cascades/expressions
Package expressions ports the Cascades-side relational expression hierarchy from Java's `com.apple.foundationdb.record.query.plan.cascades.expressions`.
Package expressions ports the Cascades-side relational expression hierarchy from Java's `com.apple.foundationdb.record.query.plan.cascades.expressions`.
plan/cascades/properties
Package properties — Cardinality and Cardinalities types.
Package properties — Cardinality and Cardinalities types.
plan/cascades/values
Package values is the Value-tier of the Go Cascades planner port — scalar / row-context expressions that compose into predicates, projections, and join keys.
Package values is the Value-tier of the Go Cascades planner port — scalar / row-context expressions that compose into predicates, projections, and join keys.
plan/plans
Go extension — no Java equivalent.
Go extension — no Java equivalent.
Package rlmetrics exposes a record-layer StoreTimer (the Go port of Java's FDBStoreTimer) in the Prometheus text exposition format, ready to scrape — with zero dependencies.
Package rlmetrics exposes a record-layer StoreTimer (the Go port of Java's FDBStoreTimer) in the Prometheus text exposition format, ready to scrape — with zero dependencies.
Package vectorcodec is the on-disk byte codec for HNSW vector columns, wire-compatible with Java's RealVector.fromBytes / VectorType.
Package vectorcodec is the on-disk byte codec for HNSW vector columns, wire-compatible with Java's RealVector.fromBytes / VectorType.

Jump to

Keyboard shortcuts

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