anystore

package module
v2.0.0-beta.2 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 30 Imported by: 0

README

Any Store

Go Reference Go Report Card MIT License

A document-oriented embedded database for Go with a MongoDB-like query language, built on an in-tree btree/pager/WAL storage engine derived from SQLite (aligned with its design, not a literal port — the on-disk format is our own). Schema-less documents, rich indexes, full-text and vector search, ACID transactions, multi-process access, page-level integrity and optional encryption — pure Go, no CGO.

Status: v2 is in beta (v2.0.0-beta.x) — the API is stabilizing; remaining changes before GA are expected to be minor. We actively dog-food the library in production and welcome early adopters & contributors.

Features

  • Mongo-style queries$eq/$in/$gt/..., logical operators, dot-path fields, modifiers ($set, $inc, ...). Formal semantics in docs/query-filter-contract.md.
  • Indexes — compound, unique, sparse, multikey (arrays), asc/desc per field; created and dropped at runtime.
  • Cost-based planner — index selection driven by btree page statistics and per-prefix selectivity sketches; Explain() shows the chosen plan and its candidates.
  • Full-text search — btree-resident inverted index with BM25 ranking via $text. See docs/full-text-search.md.
  • Vector search — btree-resident ANN indexes (IVF-PQ / IVF-SQ, HNSW, brute-force) via $knn. See docs/vector-search.md and docs/vector-engine.md.
  • Aggregation — MongoDB-style pipelines ($match, $group, $sort, $unwind, ...) with planner pushdown. See docs/aggregation.md.
  • ACID transactions — snapshot-isolated read transactions, single-writer write transactions.
  • Multi-process — SQLite-like contract: any number of OS processes may open, read and write the same database file at any time (WAL + shared-memory index, busy handling, cross-process DDL reconciliation).
  • Durability — idle auto-flush, explicit checkpointing, crash-safe WAL recovery, sentinel-triggered quick check after unclean shutdown.
  • Integrity & encryption — per-page XXH3-128 checksums by default; optional AES-GCM or (X)ChaCha20-Poly1305 (AEAD doubles as integrity).
  • Streaming iterators — low-memory scans with a cursor API; AnyEnc arena encoding minimizes GC churn.
  • Cross-platform — pure Go, no CGO; runs anywhere Go runs, including js/wasm (see wasm/).
  • CLI — inspection, import/export and interactive shell.

Quick start

Install
go get github.com/anyproto/any-store/v2

CLI (optional):

go install github.com/anyproto/any-store/cmd/any-store-cli2/v2@latest
Hello, Any Store
package main

import (
    "context"
    "fmt"
    "log"

    anystore "github.com/anyproto/any-store/v2"
    "github.com/anyproto/any-store/v2/anyenc"
)

func main() {
    ctx := context.Background()

    db, err := anystore.Open(ctx, "/tmp/demo.db", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    users, _ := db.Collection(ctx, "users")

    _ = users.Insert(ctx,
        anyenc.MustParseJson(`{"id": 1, "name": "John"}`),
        anyenc.MustParseJson(`{"id": 2, "name": "Jane"}`),
    )

    res, _ := users.Find(`{"id": {"$in": [1,2]}}`).Sort("-name").Iter(ctx)
    for res.Next() {
        doc, _ := res.Doc()
        fmt.Println(doc.Value().String())
    }

    // Storage footprint: doc count, sizes, compression and per-index stats.
    st, _ := users.Stats(ctx)
    fmt.Printf("docs=%d total=%d bytes ratio=%.2fx\n",
        st.DocCount, st.TotalSizeBytes, st.CompressionRatio)
}

The full end-to-end example lives in example/ and the API docs.

Choosing document ids. Unlike SQLite there is no hidden rowid — the document id is the btree key. Increment-like ids (anyenc.NewObjectID(), timestamps, sequences) keep inserts append-ordered and reads cache-friendly; fully random ids (e.g. UUIDv4) scatter them. In practice this only matters for very hot write/read paths or huge collections.

Transactions

tx, _ := db.WriteTx(ctx)
_ = users.Insert(tx.Context(), anyenc.MustParseJson(`{"id": 3}`))
_, _ = users.Find(`{"id": 3}`).Update(tx.Context(), `{"$set": {"seen": true}}`)
if err := tx.Commit(); err != nil { // or tx.Rollback()
    log.Fatal(err)
}

Any operation run with tx.Context() joins the transaction. Read transactions (db.ReadTx) pin a consistent snapshot; writers never block readers. One write transaction is active at a time — across all processes.

Indexes

_ = users.EnsureIndex(ctx,
    anystore.IndexInfo{Fields: []string{"name", "-createdDate"}},          // compound, mixed order
    anystore.IndexInfo{Fields: []string{"email"}, Unique: true},           // unique
    anystore.IndexInfo{Fields: []string{"nick"}, Sparse: true},            // skips missing/null
)

exp, _ := users.Find(`{"name": "Jane"}`).Explain(ctx)
fmt.Println(exp.Plan) // chosen index, cost breakdown, rejected candidates

Array fields index every element (multikey). The cost-based planner picks the cheapest index per query; IndexHint overrides it.

_ = docs.EnsureIndex(ctx, anystore.IndexInfo{
    Kind:   anystore.IndexKindFulltext,
    Fields: []string{"title", "body"},
})

res, _ := docs.Find(`{"$text": {"$search": "wal checkpoint"}}`).Limit(10).Iter(ctx)

BM25-ranked, transactional (a $text write is visible to queries in the same tx), combinable with any other filter, sort and limit.

_ = docs.EnsureIndex(ctx, anystore.IndexInfo{
    Kind: anystore.IndexKindVector,
    Vector: &anystore.VectorParams{
        Field:  "embedding",
        Dim:    768,
        Metric: anystore.VectorCosine, // or VectorL2, VectorDot
        Mode:   anystore.VectorModeIVFSQ, // or IVFPQ, BTree/Hybrid (HNSW), BruteForce
    },
})

res, _ := docs.Find(`{"embedding": {"$knn": {"$query": [0.1, 0.2, ...], "$k": 10}}}`).Iter(ctx)

ANN results stream in distance order and compose with filters; $ef tunes recall per query, int8 quantization and IVF cell counts are per-index options. IVF modes are the production default for large collections.

Aggregation

res, _ := orders.Aggregate(`[
    {"$match": {"status": "paid"}},
    {"$group": {"_id": "$region", "total": {"$sum": "$amount"}}},
    {"$sort": {"total": -1}}
]`).Iter(ctx)

$match/$sort/$limit prefixes are pushed down into the index planner; the rest streams through the pipeline.

Multi-process

The database file behaves like SQLite in WAL mode: any process may open it at any moment and read or write concurrently. Coordination runs through file locks and a shared-memory WAL index; snapshot reads are never blocked, writers serialize, and busy situations back off through a configurable handler. DDL from a peer process (created/dropped collections and indexes) is reconciled automatically on the next transaction.

Durability

Any Store performs WAL checkpoints and fsync after idle periods.

db, _ := anystore.Open(ctx, "data.db", &anystore.Config{
    Durability: anystore.DurabilityConfig{
        AutoFlush: true,
        IdleAfter: 20 * time.Second,  // flush after 20s of inactivity
        FlushMode: anystore.FlushModeCheckpointPassive, // ...Full, ...Restart, ...Truncate
        Sentinel:  true,  // track dirty state, quick-check on next open
    },
})

// Manual flush, e.g. before app suspension (waits up to 100ms for pending writes to settle).
db.Flush(ctx, 100*time.Millisecond, anystore.FlushModeCheckpointPassive)

Sentinel: when enabled, a .lock file marks not-explicitly-persisted writes so an unclean shutdown triggers an integrity quick check on open.

Integrity

Every non-encrypted database carries an XXH3-128 page-trailer checksum (16 bytes/page) by default — corruption is caught on read. There is no opt-out; the cost is <1% on writes and effectively zero on reads. Encrypted databases derive integrity from the cipher's AEAD tag instead. File state is authoritative on reopen — existing plain databases stay plain, existing checksum databases auto-install the codec regardless of caller config.

Conceptually mirrors SQLite's cksumvfs, generalized to also surface AEAD failures via the same API.

db, _ := anystore.Open(ctx, "data.db", &anystore.Config{
    // Wire monitoring at Open time so failures during the first page-1
    // read (inside Open) are observable.
    OnIntegrityError: func(e anystore.IntegrityError) {
        log.Printf("integrity: page %d %v: %v", e.PageNo, e.Kind, e.Inner)
    },
    // Default false: corrupt pages fail reads. True is for forensic dumps.
    ContinueOnIntegrityError: false,
})

// Walk every page and report mismatches (works in encrypted mode too).
rep, _ := db.VerifyIntegrity(ctx)
fmt.Printf("scanned %d pages, %d errors\n", rep.Pages, len(rep.Errors))

The page-1 DB header (first 100 bytes) is outside the per-page hash; its invariants are validated separately at open. Full design: docs/btree/specs/integrity.md.

Encryption

db, _ := anystore.Open(ctx, "data.db", &anystore.Config{
    Encryption: anystore.EncryptionConfig{
        Passphrase: []byte("correct horse battery staple"),
        CipherType: anystore.CipherXChaCha20Poly1305, // or CipherAES256GCM, CipherChaCha20Poly1305
    },
})

Whole-file page-level AEAD; bring-your-own key management via EncryptionConfig.Codec.

Documentation

Contributing

  1. Fork & clone
  2. make test — run unit tests
  3. Create your feature branch
  4. Open a PR and sign the CLA

Please read our Code of Conduct before contributing.

⚖️ License

Any Store is released under the MIT License — see LICENSE for details.

Documentation

Index

Constants

View Source
const CipherAES256GCM = btree.CipherAES256GCM

CipherAES256GCM is AES-256 in Galois/Counter Mode. Default choice; hardware-accelerated on modern CPUs. Per-page overhead: 32 bytes.

View Source
const CipherChaCha20Poly1305 = btree.CipherChaCha20Poly1305

CipherChaCha20Poly1305 is ChaCha20 with Poly1305 authentication. Constant-time in pure software. Per-page overhead: 32 bytes.

View Source
const CipherXChaCha20Poly1305 = btree.CipherXChaCha20Poly1305

CipherXChaCha20Poly1305 is the 24-byte-nonce variant of ChaCha20- Poly1305. Per-page overhead: 48 bytes. Use only for workloads with very high per-key write volumes where 12-byte random-nonce collision is a concern.

View Source
const DefaultKDFIterations = btree.DefaultKDFIterations

DefaultKDFIterations is the PBKDF2 iteration count used when EncryptionConfig.KDFIterations is zero.

Variables

View Source
var (
	// ErrGroupLimitExceeded is returned when an aggregation $group stage
	// exceeds the configured maximum number of unique group keys
	// (AggQuery.GroupLimit).
	ErrGroupLimitExceeded = aggregate.ErrGroupLimitExceeded

	// ErrAccumArrayLimitExceeded is returned when a $push or $addToSet
	// accumulator exceeds the configured maximum array length
	// (AggQuery.AccumArrayLimit).
	ErrAccumArrayLimitExceeded = aggregate.ErrAccumArrayLimitExceeded

	// ErrAggMemoryLimitExceeded is returned when the blocking stages of an
	// aggregation pipeline retain more than the configured memory budget
	// (AggQuery.MemoryLimit).
	ErrAggMemoryLimitExceeded = aggregate.ErrMemoryLimitExceeded
)
View Source
var (
	// ErrDocExists is returned when attempting to insert a document that already exists.
	ErrDocExists = errors.New("any-store: document already exists")

	// ErrDocNotFound is returned when a document cannot be found by its ID.
	ErrDocNotFound = errors.New("any-store: document not found")

	// ErrDocWithoutId is returned when a document is provided without a required ID.
	ErrDocWithoutId = errors.New("any-store: document missing ID")

	// ErrArrayPrimaryKey is returned when a document's primary-key value is an
	// array. Array pks are rejected because filter semantics on arrays are
	// element-wise while the storage key is the whole-array encoding, so a doc
	// could match a pk filter yet sit outside the scanned key range. Files
	// written before this ban that contain array pks must be recreated.
	ErrArrayPrimaryKey = errors.New("any-store: array values are not allowed as primary key")

	// ErrPrimaryKeyMismatch is returned by Collection when the requested
	// PrimaryKey option conflicts with an existing collection's stored primary
	// key. The primary key is immutable after creation.
	ErrPrimaryKeyMismatch = errors.New("any-store: primary key mismatch")

	// ErrPrimaryKeyModification is returned when an update would change a
	// document's primary-key value. The primary key is immutable (Mongo
	// _id semantics): a $set on the id field is rejected rather than
	// leaving a ghost data record under the document's old key.
	ErrPrimaryKeyModification = errors.New("any-store: primary key modification is not allowed")

	// ErrCollectionExists is returned when attempting to create a collection that already exists.
	ErrCollectionExists = errors.New("any-store: collection already exists")

	// ErrInvalidCollectionName is returned when a collection name is empty or
	// collides with a reserved namespace family (see validateCollectionName).
	ErrInvalidCollectionName = errors.New("any-store: invalid collection name")

	// ErrCollectionNotFound is returned when a collection cannot be found.
	ErrCollectionNotFound = errors.New("any-store: collection not found")

	// ErrCollectionClosed is returned by operations on a collection handle
	// that is no longer live: explicitly closed, dropped, or invalidated
	// because another process renamed or dropped the collection (SQLite
	// re-prepare style — re-open the collection to continue). It wraps
	// ErrCollectionNotFound so errors.Is matches what a fresh OpenCollection
	// under the stale name returns.
	ErrCollectionClosed = fmt.Errorf("any-store: collection handle is closed (dropped, renamed, or closed): %w", ErrCollectionNotFound)

	// ErrInvalidIndexName is returned when an index name (given or derived from
	// the field list) exceeds the maximum length (see validateIndexName).
	ErrInvalidIndexName = errors.New("any-store: invalid index name")

	// ErrIndexExists is returned when attempting to create an index that already exists.
	ErrIndexExists = errors.New("any-store: index already exists")

	// ErrIndexNotFound is returned when an index cannot be found.
	ErrIndexNotFound = errors.New("any-store: index not found")

	// ErrIndexMismatch is returned when an index with the requested name already
	// exists but with a different definition (different fields, unique, or sparse
	// flags). A redefinition is never applied silently: drop the existing index
	// first, then create it with the new definition.
	ErrIndexMismatch = errors.New("any-store: index exists with a different definition")

	// ErrTxIsReadOnly is returned when a write operation is attempted in a read-only transaction.
	ErrTxIsReadOnly = errors.New("any-store: transaction is read-only")

	// ErrTxIsUsed is returned when an operation is attempted on a transaction that has already been committed or rolled back.
	ErrTxIsUsed = errors.New("any-store: transaction has already been used")

	// ErrTxOtherInstance is returned when an operation is attempted using a transaction from a different database instance.
	ErrTxOtherInstance = errors.New("any-store: transaction belongs to another database instance")

	// ErrUniqueConstraint is returned when a unique constraint violation occurs.
	ErrUniqueConstraint = errors.New("any-store: unique constraint violation")

	// ErrIterClosed is returned when operations are attempted on a closed iterator.
	ErrIterClosed = errors.New("any-store: iterator is closed")

	// ErrQuickCheckFailed is returned when we did a quick check (e.g. when opening db in a dirty state with sentinel config on) and it failed, indicating possible database corruption.
	ErrQuickCheckFailed = errors.New("any-store: quick check failed on db open")

	ErrDBIsClosed = btree.ErrClosed

	ErrDBIsNotOpened       = errors.New("any-store: database is not opened")
	ErrIncompatibleVersion = errors.New("any-store: incompatible version")

	// ErrPageBufferNotInitialized is returned when UseGlobalPageBuffer is set
	// but InitPageBuffer was not called before opening the database.
	ErrPageBufferNotInitialized = errors.New("any-store: global page buffer not initialized (call InitPageBuffer first)")
)
View Source
var ErrAmbiguousVectorIndex = errors.New("any-store: field has multiple vector indexes; name one with $index")

ErrAmbiguousVectorIndex is returned when a $knn clause targets a field with more than one vector index and no $index to disambiguate. Without it the searched index — and therefore the selected documents, on Delete too — would depend on index load order.

View Source
var ErrDistanceWithoutVector = errors.New("any-store: _distance is only available in a vector query")

ErrDistanceWithoutVector is returned when the synthetic _distance field is used in a filter or sort but the query has no $knn clause to produce it.

View Source
var ErrFtsFormatOutdated = errors.New("any-store: full-text index uses an older on-disk format; drop and recreate the index")

ErrFtsFormatOutdated is returned when a full-text index's postings were written by an older build (a different on-disk format version). The fix is to drop and recreate the index. any-store makes no on-disk back-compatibility promise for full-text indexes yet (see docs/fts/DESIGN.md); the postings version byte makes this safe (the old blob is rejected, never mis-decoded as corruption).

View Source
var ErrInvalidVectorQuery = errors.New("any-store: invalid $knn clause")

ErrInvalidVectorQuery is returned when a $knn clause is malformed: an empty or non-finite $query, a $query whose length differs from the index dimension, or an out-of-range $k/$ef. It says nothing about non-$knn clauses on a vector-indexed field — those are ordinary filters on every verb.

View Source
var ErrKnnBadPlacement = errors.New("any-store: $knn is only allowed at the top level or under $and")

ErrKnnBadPlacement is returned when a $knn clause sits anywhere other than the top level or under $and: a ranked source cannot be a disjunct ($or/$nor), a negation ($not — fail-closed Ok would reflect to match-all), or a nested sub-filter.

View Source
var ErrKnnWithText = errors.New("any-store: $knn and $text cannot be combined in one query")

ErrKnnWithText is returned when one query carries both $knn and $text: a query has one source.

View Source
var ErrLegacyVectorClause = errors.New(`any-store: field has a vector index; a bare-array equality clause is no longer an ANN query — use {"$knn":{"$query":[...],"$k":N}} (or query.NewKnn) to search it`)

ErrLegacyVectorClause is returned, on every verb, for the pre-$knn ANN spelling — a bare equality of a dim-sized numeric array against a vector-indexed field. Silent demotion to a literal filter would mean 0 rows (packed storage) or 1 row (plain-array storage) with err == nil; a hard error turns every migration point into a test failure instead.

View Source
var ErrMultipleVectorClauses = errors.New("any-store: query has multiple vector clauses")

ErrMultipleVectorClauses is returned when a query carries more than one $knn clause (unsupported).

View Source
var ErrNoFulltextIndex = errors.New("any-store: collection has no full-text index for $text")

ErrNoFulltextIndex is returned when a $text query runs against a collection that has no full-text index.

View Source
var ErrNoVectorIndex = errors.New("any-store: no vector index on field")

ErrNoVectorIndex is returned when a $knn clause targets a field with no vector index (or $index names one that doesn't exist). Exact search is an index MODE (VectorModeBruteForce), not a fallback.

View Source
var ErrPageIntegrity = btree.ErrPageIntegrity

ErrPageIntegrity is the umbrella error returned (wrapped) by every codec when a page fails its on-disk integrity check, regardless of mode. Callers match it with errors.Is to handle both the cksum-only and AEAD modes uniformly:

if errors.Is(err, anystore.ErrPageIntegrity) { ... }

The wrapped inner detail carries the mode-specific message ("checksum mismatch" or "AEAD authentication failed"); callers that need a programmatic discriminator should use SweepError.Kind from VerifyIntegrity instead of parsing strings.

View Source
var ErrVectorMetricUnsupported = errors.New("any-store: vector index: VectorDot is not supported by IVF modes; use VectorModeBTree, VectorModeHybrid or VectorModeBruteForce, or the Cosine/L2 metrics")

ErrVectorMetricUnsupported is returned when an index declares a metric the selected mode cannot rank by. Validation runs on both create and open, so a persisted index with an unsupported combination fails loudly instead of silently ranking by the wrong metric.

Functions

func DeriveKey

func DeriveKey(passphrase, salt []byte, iterations int) []byte

DeriveKey stretches a user passphrase to a 32-byte AES key using PBKDF2-HMAC-SHA256 against the supplied salt. Iterations defaults to 256,000 (SQLCipher v4 default) when <= 0. Exposed for users who need to derive keys for BYO codecs; normal usage should set EncryptionConfig.Passphrase directly.

func EnablePipelinePerfCounters

func EnablePipelinePerfCounters(enabled bool)

EnablePipelinePerfCounters toggles pipeline profiling counters.

func FtsAnalyzeTerms

func FtsAnalyzeTerms(text string) []string

FtsAnalyzeTerms runs the full-text analyzer pipeline (NFKC + case folding + UAX#29 word boundaries + CJK bigrams) over text and returns the resulting terms in token order.

func FtsBM25Params

func FtsBM25Params() (k1, b float64)

FtsBM25Params returns the BM25 k1 and b parameters used by $text scoring.

func InitPageBuffer

func InitPageBuffer(pageSize, nPages int)

InitPageBuffer pre-allocates a global pool of nPages page-sized buffers. Must be called before opening any databases that use UseGlobalPageBuffer. Mirrors sqlite3_config(SQLITE_CONFIG_PAGECACHE). Call once at process startup.

Example: InitPageBuffer(4096, 5000) pre-allocates ~20MB of page buffers.

func ResetOpenRegistry

func ResetOpenRegistry()

ResetOpenRegistry clears the process-global registry of open databases. Tests that simulate process crashes (where Close is intentionally skipped) call this to allow a subsequent Open of the same file to succeed. Panics unless built with -tags vfs or GOOS=js GOARCH=wasm.

func ResetPipelinePerfCounters

func ResetPipelinePerfCounters()

ResetPipelinePerfCounters clears internal iterator/doc profiling counters.

func ResetVFS

func ResetVFS()

ResetVFS restores defaults. Panics unless built with -tags vfs or GOOS=js GOARCH=wasm.

func SetVFS

func SetVFS(vfs VFS)

SetVFS replaces OS-level operations. Panics unless built with -tags vfs or GOOS=js GOARCH=wasm.

func SetVectorBuildConcurrency

func SetVectorBuildConcurrency(n int)

SetVectorBuildConcurrency bounds the worker goroutines used to BUILD and COMPACT vector (HNSW) indexes — the only place any-store uses internal parallelism. It is a single process-wide budget shared by every vector index in the process, so many simultaneous builds/compactions cannot oversubscribe the CPU. n <= 0 (the default) uses GOMAXPROCS; set it low (e.g. 1–2) on constrained or many-database deployments. It does NOT affect search, insert/update/delete, or any non-vector operation.

Like InitPageBuffer this is a process-global setting, not per-database: call it once at process startup, before opening databases.

func StampPageChecksumForTest

func StampPageChecksumForTest(page []byte)

StampPageChecksumForTest writes a valid XXH3-128 trailer into the last 16 bytes of `page`. Test/migration helper; production code never calls this directly — codec.Encrypt does it during normal page writes.

func VerifyPageChecksum

func VerifyPageChecksum(page []byte) bool

VerifyPageChecksum returns true iff the trailing 16 bytes of `page` are the XXH3-128 of page[:len(page)-16]. Returns false for invalid sizes. Public; mirrors SQLite's verify_checksum() SQL function. Computes over the full page; for the codec-equivalent (which excludes the page-1 DB header for page 1), use (DB).VerifyIntegrity instead.

Types

type AggQuery

type AggQuery interface {
	// GroupLimit overrides the maximum number of unique $group keys
	// (default aggregate.DefaultGroupLimit; negative = unlimited).
	GroupLimit(n int) AggQuery

	// AccumArrayLimit overrides the maximum $push / $addToSet array length
	// (default aggregate.DefaultAccumArrayLimit; negative = unlimited).
	AccumArrayLimit(n int) AggQuery

	// MemoryLimit overrides the retained-bytes budget shared by the
	// pipeline's blocking stages — $group state and in-pipeline $sort
	// (default aggregate.DefaultMemoryLimit; negative = unlimited).
	MemoryLimit(bytes int) AggQuery

	// IndexHint adds or removes boost for indexes considered by the pushed
	// prefix's query plan.
	IndexHint(hints ...IndexHint) AggQuery

	// Iter executes the pipeline and returns an Iterator over result
	// documents. Score and Distance report 0 on aggregation iterators; for a
	// full-text or vector $match prefix the values remain available as the
	// _score / _distance document fields. See Iterator for the
	// mutation-while-iterating contract (undefined, SQLite-style).
	Iter(ctx context.Context) (Iterator, error)

	// Count executes the pipeline and returns the number of result documents.
	Count(ctx context.Context) (int, error)

	// Explain returns the access plan of the pushed prefix and the list of
	// in-pipeline stages.
	Explain(ctx context.Context) (Explain, error)
}

AggQuery is a MongoDB-style aggregation pipeline over a collection.

The longest pushable pipeline prefix ($match chains, then $sort/$skip/ $limit in canonical order) is compiled into a regular query and executed by the access planner — indexes, CBO, full-text ($text) and vector sources all apply, including their _score/_distance virtual fields. The remaining stages stream over the result inside the same read transaction.

type CipherType

type CipherType = btree.CipherType

CipherType selects a built-in AEAD. See the individual constants for trade-offs.

type Codec

type Codec = btree.Codec

Codec is the pluggable page-encryption interface. Implement this to provide a custom AEAD (HSM-backed, external key management, or any authenticated cipher not offered by the built-in CipherType values).

The pager installs one Codec per DB and routes every file / WAL I/O site through it. Implementations must be safe for concurrent use: a single Codec serves the writer and all reader goroutines.

Overhead() bytes are reserved at the end of every page for codec metadata (nonce, tag, any padding). The btree cell layout automatically respects this via (page_size - reserve_size). Must be constant for the codec's lifetime and a multiple of 16 (AES block size) for alignment.

Encrypt/Decrypt operate on full-page buffers. src and dst are both exactly pageSize; dst must not alias src. The 1-based page number pgno is bound into the authentication tag as associated data so that moving a page to a different file offset invalidates its MAC.

func NewAESCodec

func NewAESCodec(key []byte) (Codec, error)

NewAESCodec constructs a codec using AES-256-GCM with a raw 32-byte key. Prefer EncryptionConfig.Passphrase for the common passphrase-based case; use this directly when managing key material externally (HSM, KMS, etc).

func NewChaCha20Poly1305Codec

func NewChaCha20Poly1305Codec(key []byte) (Codec, error)

NewChaCha20Poly1305Codec constructs a ChaCha20-Poly1305 codec with a raw 32-byte key.

func NewXChaCha20Poly1305Codec

func NewXChaCha20Poly1305Codec(key []byte) (Codec, error)

NewXChaCha20Poly1305Codec constructs an XChaCha20-Poly1305 codec with a raw 32-byte key. 24-byte nonce.

type Collection

type Collection interface {
	// Name returns the name of the collection.
	Name() string

	// PrimaryKey returns the document field used as this collection's primary key.
	PrimaryKey() string

	// FindId finds a document by its ID.
	// Returns the document or an error if the document is not found.
	FindId(ctx context.Context, id any) (Doc, error)

	// FindIdWithParser finds a document by its ID. Uses provided anyenc parser.
	// Returns the document or an error if the document is not found.
	FindIdWithParser(ctx context.Context, p *anyenc.Parser, id any) (Doc, error)

	// Find returns a new Query object with given filter
	Find(filter any) Query

	// Aggregate returns a new aggregation query for the given MongoDB-style
	// pipeline (an array of stages like $match, $group, $sort, $unwind, ...).
	Aggregate(pipeline any) AggQuery

	// Insert inserts multiple documents into the collection.
	// Returns an error if the insertion fails.
	Insert(ctx context.Context, docs ...*anyenc.Value) (err error)

	// UpdateOne updates a single document in the collection.
	// Provided document must contain an id field
	// Returns an error if the update fails.
	UpdateOne(ctx context.Context, doc *anyenc.Value) (err error)

	// UpdateId updates a single document in the collection with provided modifier
	// Returns a modify result or error.
	UpdateId(ctx context.Context, id any, mod query.Modifier) (res ModifyResult, err error)

	// UpsertOne inserts a document if it does not exist, or updates it if it does.
	// Returns the ID of the upserted document or an error if the operation fails.
	UpsertOne(ctx context.Context, doc *anyenc.Value) (err error)

	// UpsertId updates a single document or creates new one
	// Returns a modify result or error.
	UpsertId(ctx context.Context, id any, mod query.Modifier) (res ModifyResult, err error)

	// DeleteId deletes a single document by its ID.
	// Returns an error if the deletion fails.
	DeleteId(ctx context.Context, id any) (err error)

	// Count returns the number of documents in the collection.
	// Returns the count of documents or an error if the operation fails.
	Count(ctx context.Context) (count int, err error)

	// CreateIndex creates a new index.
	// Returns an error if index exists or the operation fails.
	CreateIndex(ctx context.Context, info ...IndexInfo) (err error)

	// EnsureIndex ensures an index exists on the specified fields.
	// Returns an error if the operation fails.
	EnsureIndex(ctx context.Context, info ...IndexInfo) (err error)

	// DropIndex drops an index by its name.
	// Returns an error if the operation fails.
	DropIndex(ctx context.Context, indexName string) (err error)

	// GetIndexes returns a list of indexes on the collection: range indexes
	// followed by full-text indexes (a full-text Len is the number of indexed
	// documents). Vector indexes are not listed yet.
	GetIndexes() (indexes []Index)

	// CompactVectorIndex rebuilds the named vector index from its live vectors,
	// reclaiming nodes and storage left behind by deletes and replaces (which only
	// tombstone). It is synchronous and holds the write lock for the rebuild;
	// prefer a maintenance window for large indexes. No-op when nothing is
	// reclaimable or for a brute-force index. Returns ErrIndexNotFound if absent.
	CompactVectorIndex(ctx context.Context, indexName string) error

	// Stats returns the storage footprint of the collection: document count,
	// stored and uncompressed sizes, compression ratio and per-index sizes.
	// It scans the whole collection and is intended for diagnostics.
	Stats(ctx context.Context) (CollectionStats, error)

	// Rename renames the collection.
	// Returns an error if the operation fails.
	Rename(ctx context.Context, newName string) (err error)

	// Drop drops the collection.
	// Returns an error if the operation fails.
	Drop(ctx context.Context) (err error)

	// ReadTx starts a new read-only transaction. It's just a proxy to db object.
	// Returns a ReadTx or an error if there is an issue starting the transaction.
	ReadTx(ctx context.Context) (ReadTx, error)

	// WriteTx starts a new read-write transaction. It's just a proxy to db object.
	// Returns a WriteTx or an error if there is an issue starting the transaction.
	WriteTx(ctx context.Context) (WriteTx, error)

	// Close closes the collection.
	// Returns an error if the operation fails.
	Close() error
}

Collection represents a collection of documents.

type CollectionOptions

type CollectionOptions struct {
	// Compression overrides the database-wide compression setting for this collection.
	// Zero value inherits the database default.
	Compression Compression

	// PrimaryKey is the document field whose value is the collection's primary
	// key. Empty defaults to "id". Honored only at CreateCollection; the primary
	// key is immutable once the collection exists.
	PrimaryKey string
}

CollectionOptions configures per-collection settings at creation time.

type CollectionStats

type CollectionStats struct {
	// Name is the collection name.
	Name string

	// DocCount is the number of documents in the collection.
	DocCount int

	// StoredDocsBytes is the sum of stored document value bytes. When
	// compression is enabled this counts the compressed form.
	StoredDocsBytes int

	// UncompressedDocsBytes is the sum of document value bytes after
	// decompression. It equals StoredDocsBytes when nothing is compressed.
	UncompressedDocsBytes int

	// CompressionEnabled reports whether compression is active for this
	// collection. Individual documents below anyenc.CompressMinSize are stored
	// uncompressed even when it is enabled.
	CompressionEnabled bool

	// CompressionRatio is UncompressedDocsBytes/StoredDocsBytes. It is 1.0 when
	// nothing is compressed and grows above 1.0 as compression saves space.
	CompressionRatio float64

	// DocsSizeBytes is the physical on-disk size of the collection's document
	// B-tree (page count including overflow pages times the page size).
	DocsSizeBytes int

	// IndexesSizeBytes is the sum of SizeBytes across all range (B-tree) indexes.
	IndexesSizeBytes int

	// VectorIndexesSizeBytes is the sum of SizeBytes across all vector indexes.
	VectorIndexesSizeBytes int

	// FtsIndexesSizeBytes is the sum of SizeBytes across all full-text indexes.
	FtsIndexesSizeBytes int

	// TotalSizeBytes is DocsSizeBytes + IndexesSizeBytes + VectorIndexesSizeBytes
	// + FtsIndexesSizeBytes.
	TotalSizeBytes int

	// Indexes holds per-(range-)index statistics.
	Indexes []IndexStats

	// VectorIndexes holds per-vector-index statistics.
	VectorIndexes []VectorIndexStats

	// FtsIndexes holds per-full-text-index statistics.
	FtsIndexes []FtsIndexStats
}

CollectionStats describes the storage footprint of a collection: its documents, compression effectiveness and per-index sizes.

type Compression

type Compression int

Compression specifies the compression algorithm for document values.

const (
	// S2 enables S2 compression for objects larger than 256 bytes (default).
	S2 Compression = 1
	// NoCompression disables compression entirely.
	NoCompression Compression = 2
)

type Config

type Config struct {
	// SyncPoolElementMaxSize defines maximum size of buffer that can be returned to the syncpool
	// default value is 2MiB
	SyncPoolElementMaxSize int

	// CommitSync forces fsync on every WAL commit (like SQLite synchronous=FULL in WAL mode).
	// When false (default), fsync is deferred to checkpoint time, which reduces write latency
	// at the cost of losing the last committed transaction(s) on power loss.
	CommitSync bool

	// DisableAutoCheckpoint disables WAL auto-checkpoint entirely.
	// When true, checkpoint must be triggered manually or via durability auto-flush.
	DisableAutoCheckpoint bool

	// AutoCheckpointAfter overrides the default WAL auto-checkpoint threshold (10000 frames).
	// Only used when DisableAutoCheckpoint is false. 0 means use default.
	AutoCheckpointAfter int

	// CacheSize overrides the default per-DB page cache size (in pages,
	// default 5000). Primarily for tests that need to force pagerStress /
	// cache-spill behavior at low data volumes. Zero means use the default.
	CacheSize int

	// ReadConcurrency caps the number of concurrent read transactions. Reads
	// beyond this many serialize on a semaphore, so on a multi-core host a low cap
	// throttles concurrent Find/$text throughput. Each live reader holds its own
	// page cache (≈ CacheSize × PageSize), created lazily, so peak reader RAM is
	// ReadConcurrency × that — only realized under actual concurrent load. Zero
	// means the default, max(NumCPU-1, 4), which scales with the host while staying
	// modest on small devices.
	ReadConcurrency int

	// InMemory keeps the entire database in memory with no files on disk.
	// The database does not survive process crashes. When true, InProcess
	// and CommitSync=false are forced on automatically.
	// The path argument to Open is ignored and can be any string (e.g. ":memory:").
	InMemory bool

	// DisableCompression disables S2 compression for document values.
	// By default, objects larger than 256 bytes are compressed with S2.
	DisableCompression bool

	// UseGlobalPageBuffer opts this DB into the global pre-allocated page
	// buffer pool. The pool must be initialized beforehand via InitPageBuffer.
	// When false (default), page buffers use sync.Pool (GC-managed, like
	// SQLite's default malloc mode).
	UseGlobalPageBuffer bool

	// MmapSize enables mmap-backed reads of the database file up to the
	// given byte limit. Zero (default) disables mmap — reads use pread
	// via ReadAt, same behavior as before this feature existed.
	// Matches SQLite's PRAGMA mmap_size. Linux/darwin + amd64/arm64
	// only; no-op on other platforms.
	//
	// SAFETY WARNINGS — read before enabling:
	//
	//   - SIGBUS on unreachable storage: if the DB file becomes
	//     truncated, unlinked, or the storage device is removed while
	//     a mapping is live (USB unplug, iCloud eviction of a
	//     "dataless" file, NFS server timeout, external process
	//     truncate), accessing the mapped bytes generates SIGBUS. Go's
	//     runtime translates SIGBUS to an uncatchable "unexpected
	//     fault address" crash — the whole process dies. The pread
	//     path (MmapSize=0) returns a normal error for all of these
	//     scenarios. Only enable on paths you control: local disk,
	//     stable filesystem, single-device. Do NOT enable for DBs
	//     stored in iCloud Drive, OneDrive, Dropbox, or any network
	//     mount.
	//
	//   - Mobile / iOS: mmap'd bytes count against the per-process
	//     memory limit and can trip iOS's "jetsam" reaper on older
	//     devices. iOS may also silently purge mmap pages under
	//     memory pressure; re-faulting succeeds for local files but
	//     fails (SIGBUS) if the backing file has become unavailable
	//     (permission revocation, iCloud eviction). Recommend leaving
	//     at zero on iOS/iPadOS unless you have a measured need.
	//
	//   - Network filesystems: mmap coherence over NFS/SMB is weaker
	//     than over local disks; concurrent writes from peers may be
	//     invisible to the mapping, and reads may observe torn state.
	//     pread does not have this issue.
	//
	// Recommended when it IS safe: 64-512 MiB on workloads with
	// frequent large-blob or repeat-page reads, on stable local
	// storage, desktop/server deployments.
	MmapSize int64

	// DurabilityConfig provides configuration for crash recovery and idle auto-flush
	Durability DurabilityConfig

	// Encryption, when non-empty, enables page-level AES-256-GCM encryption
	// of the on-disk database file. Zero value means no encryption.
	Encryption EncryptionConfig

	// OnIntegrityError, when non-nil, is invoked from the read path on
	// every per-page integrity failure (XXH3 trailer mismatch in cksum
	// mode, AEAD auth-tag failure in encryption mode). Plain databases
	// never fire it.
	//
	// The callback runs synchronously on the I/O goroutine and must not
	// block. Push to a buffered channel + drain elsewhere if you need
	// retention or cross-thread delivery.
	//
	// Set this at Open time so failures discovered during the first
	// page-1 read (which happens inside Open) are observable. There is
	// no post-Open setter — the codebase favors config-at-Open over
	// runtime mutation.
	OnIntegrityError func(IntegrityError)

	// ContinueOnIntegrityError, when true, lets reads of corrupt pages
	// return their (potentially garbage) bytes instead of erroring with
	// ErrPageIntegrity. The OnIntegrityError callback still fires —
	// only the error-return is suppressed. Mirror of cksumvfs's
	// `PRAGMA checksum_verification = OFF`.
	//
	// Default (false) is the safe choice: corrupt pages cause reads to
	// fail, callers see the error, app halts or recovers. Enable only
	// for forensic dumps where you'd rather read garbage than not be
	// able to read at all.
	//
	// Honored only in checksum mode. AEAD-encrypted databases ignore
	// this flag (disabling AEAD verification would return attacker-
	// controlled plaintext, defeating the cipher).
	ContinueOnIntegrityError bool
}

Config provides the configuration options for the database.

type DB

type DB interface {
	// CreateCollection creates a new collection with the specified name.
	// Returns the created Collection or an error if the collection already exists.
	// Possible errors:
	// - ErrCollectionExists: if the collection already exists.
	CreateCollection(ctx context.Context, collectionName string, opts ...CollectionOptions) (Collection, error)

	// OpenCollection opens an existing collection with the specified name.
	// Returns the opened Collection or an error if the collection does not exist.
	// Possible errors:
	// - ErrCollectionNotFound: if the collection does not exist.
	OpenCollection(ctx context.Context, collectionName string) (Collection, error)

	// Collection is a convenience method to get or create a collection.
	// It first attempts to open the collection, and if it does not exist, it creates the collection.
	// Returns the Collection or an error if there is an issue creating or opening the collection.
	Collection(ctx context.Context, collectionName string, opts ...CollectionOptions) (Collection, error)

	// GetCollectionNames returns a list of all collection names in the database.
	// Returns a slice of collection names or an error if there is an issue retrieving the names.
	GetCollectionNames(ctx context.Context) ([]string, error)

	// Stats returns the statistics of the database.
	// Returns a DBStats struct containing the database statistics or an error if there is an issue retrieving the stats.
	Stats(ctx context.Context) (DBStats, error)

	// QuickCheck performs a quick integrity check. If result not ok returns error.
	QuickCheck(ctx context.Context) (err error)

	// IntegrityCheck runs the full structural btree integrity check:
	// reachable-page coverage, orphan detection, freelist consistency,
	// overflow-chain validation, key ordering, and master-page consistency.
	// Returns nil if the database is structurally consistent, or an error
	// aggregating up to 100 issues found. More expensive than QuickCheck —
	// intended for stress tests and offline diagnostics, not normal opens.
	IntegrityCheck(ctx context.Context) (err error)

	// Flush perform checkpoint on the btree database
	// When waitIdleDuration > 0, wait for waitIdleTime since the last write tx got released
	Flush(ctx context.Context, waitIdleDuration time.Duration, mode FlushMode) error

	// Backup creates a backup of the database at the specified file path.
	// Returns an error if the operation fails.
	Backup(ctx context.Context, path string) (err error)

	// ReadTx starts a new read-only transaction.
	// Returns a ReadTx or an error if there is an issue starting the transaction.
	ReadTx(ctx context.Context) (ReadTx, error)

	// WriteTx starts a new read-write transaction.
	// Returns a WriteTx or an error if there is an issue starting the transaction.
	WriteTx(ctx context.Context) (WriteTx, error)

	// Close closes the database connection.
	// Returns an error if there is an issue closing the connection.
	Close() error

	// IntegrityMode reports the page-level integrity mode of this database
	// (none / checksum / AEAD).
	IntegrityMode() IntegrityMode

	// VerifyIntegrity walks every page and verifies its per-page integrity
	// tag (XXH3-128 trailer for cksum mode, AEAD auth tag for encrypted mode).
	// Plain DBs return IntegrityNone with zero pages scanned. Mismatches are
	// returned in IntegrityReport.Errors; the function only errors on I/O or
	// context cancellation. See IntegrityConfig.
	VerifyIntegrity(ctx context.Context) (IntegrityReport, error)
}

DB represents a document-oriented database.

func Open

func Open(ctx context.Context, path string, config *Config) (DB, error)

Open opens a database at the specified path with the given configuration. The config parameter can be nil for default settings. Returns a DB instance or an error.

type DBStats

type DBStats struct {
	// CollectionsCount is the total number of collections in the database.
	CollectionsCount int

	// IndexesCount is the total number of indexes across all collections in the database.
	IndexesCount int

	// TotalSizeBytes is the total size of the database in bytes.
	TotalSizeBytes int

	// DataSizeBytes is the total size of the data stored in the database in bytes, excluding free space.
	DataSizeBytes int

	DirtyOnOpen             bool          // indicates we have sentinel file on open
	DirtyQuickCheckDuration time.Duration // time spent in quickcheck if dirty
}

DBStats represents the statistics of the database.

type Doc

type Doc interface {
	// Value returns the document as a *anyenc.Value.
	// Important: When used in an iterator, the returned value is valid only until the next call to Next.
	Value() *anyenc.Value
}

Doc represents a document in the collection.

type DurabilityConfig

type DurabilityConfig struct {
	// Enable auto-flush according to IdleAfter and FlushMode
	AutoFlush bool

	// IdleAfter is the duration to wait after the last write before performing autoflush
	// Default: 20s
	IdleAfter time.Duration

	// FlushMode specifies how to autoflush data during idle periods
	// Default: FlushModeCheckpointPassive
	FlushMode FlushMode

	// Sentinel enables the sentinel file (.lock) that tracks database dirty state
	// When true (default is false), the sentinel file is used to detect unclean shutdowns and perform QuickCheck on open
	Sentinel bool
}

type EncryptionConfig

type EncryptionConfig struct {
	// Passphrase is the user-supplied secret. Nil = no encryption.
	Passphrase []byte

	// KDFIterations overrides the PBKDF2 cost. Zero means 256,000 (the
	// SQLCipher v4 default). Ignored when Passphrase is exactly 32 bytes
	// (raw-key path). Use lower values only in tests.
	KDFIterations int

	// CipherType selects the built-in AEAD when Passphrase is set.
	// Zero value (CipherAES256GCM) is the default.
	CipherType CipherType

	// Codec, when non-nil, bypasses Passphrase / KDFIterations / CipherType
	// and is used verbatim. Use this for HSM-backed or other externally-
	// keyed codecs.
	Codec Codec
}

EncryptionConfig enables page-level encryption of the database file.

Three ways to enable encryption, in increasing order of control:

  1. Passphrase (common case): cfg.Encryption.Passphrase = []byte("my-pass") Default cipher is AES-256-GCM. Set CipherType to pick a different built-in (ChaCha20-Poly1305 / XChaCha20-Poly1305).

  2. Passphrase + CipherType (pick cipher, keep passphrase KDF): cfg.Encryption.Passphrase = []byte("my-pass") cfg.Encryption.CipherType = anystore.CipherChaCha20Poly1305

  3. Bring-your-own Codec (HSM, external KMS, custom AEAD): cfg.Encryption.Codec = myCodec // must implement anystore.Codec Passphrase / CipherType / KDFIterations are ignored in this mode.

Passphrase treatment (modes 1 and 2):

  • Exactly 32 bytes → raw key, no KDF.
  • Any other length → PBKDF2-HMAC-SHA256 stretches it to 32 bytes against the 16-byte salt stored in the database header.

An empty (length 0) non-nil Passphrase is rejected with an error; use nil Passphrase to disable encryption.

Once a database is created with encryption, it must always be opened with matching Encryption config. Reopening without the passphrase (or with the wrong one) returns an error. There is no in-place rekey in v1; changing a passphrase requires exporting to a freshly-opened database.

InMemory databases ignore Encryption (there is no at-rest artifact to protect).

func (EncryptionConfig) Enabled

func (e EncryptionConfig) Enabled() bool

Enabled reports whether this config will install a codec on Open.

type Explain

type Explain struct {
	Sql string

	// Rich explain output: multi-line plan with cost breakdown and candidates
	Plan string

	Indexes []IndexExplain
}

type File

type File = btree.File

File and VFS are always available for type-checking.

type FlushMode

type FlushMode string

FlushMode controls checkpoint behavior during flush, matching SQLite's SQLITE_CHECKPOINT_* modes.

const (
	// FlushModeCheckpointPassive checkpoints as many WAL frames as possible
	// without waiting for any readers or writers to finish. Might leave the
	// checkpoint unfinished if there are concurrent readers or writers.
	FlushModeCheckpointPassive FlushMode = "CHECKPOINT_PASSIVE"

	// FlushModeCheckpointFull waits until there is no writer and all readers
	// are reading from the most recent snapshot, then checkpoints all frames.
	// Blocks new writers while pending, but new readers continue unimpeded.
	// The WAL is preserved (not reset).
	FlushModeCheckpointFull FlushMode = "CHECKPOINT_FULL"

	// FlushModeCheckpointRestart is like Full but after checkpointing also
	// waits until all readers are reading from the database file only, then
	// resets the WAL so new writes start from the beginning. Blocks new
	// writers while pending, but does not impede readers.
	FlushModeCheckpointRestart FlushMode = "CHECKPOINT_RESTART"

	// FlushModeCheckpointTruncate is like Restart but also truncates the WAL
	// file to zero bytes.
	FlushModeCheckpointTruncate FlushMode = "CHECKPOINT_TRUNCATE"
)

type FtsIndexStats

type FtsIndexStats struct {
	// Name is the index name; Fields are the indexed text fields.
	Name   string
	Fields []string

	// DocCount is the number of documents currently indexed (documents with no
	// indexable text are excluded). VocabSize is the number of distinct terms.
	// TotalTokens is the sum of all document lengths in tokens; AvgDocLen is the
	// mean (TotalTokens/DocCount) — the BM25 length-normalization baseline.
	DocCount    int
	VocabSize   int
	TotalTokens int
	AvgDocLen   float64

	// PostingsBytes / VocabBytes / DocmapBytes / DocinfoBytes / MetaBytes are the
	// on-disk sizes of the five namespaces (the postings dominate).
	PostingsBytes int
	VocabBytes    int
	DocmapBytes   int
	DocinfoBytes  int
	MetaBytes     int

	// SizeBytes is the total physical size of the index (sum of the five).
	SizeBytes int
}

FtsIndexStats reports the storage and corpus statistics of a full-text index.

type FulltextParams

type FulltextParams struct {
	// Weights is the per-field BM25F boost, keyed by indexed field name (each key
	// must be one of IndexInfo.Fields). A field absent from the map has weight 1.0.
	// Mirrors MongoDB's text-index weights option, but scoring is BM25F: the
	// per-field term frequencies are combined as Σ weight_f · tf_f and then scored
	// with standard BM25 saturation + length normalization. Set at index creation;
	// changing a weight only changes ranking (no re-index needed — weights are read
	// at query time). Boosting e.g. {"title": 5} ranks title matches above body.
	Weights map[string]float64 `json:"weights,omitempty"`
	// B is the BM25 length-normalization parameter, in [0,1]. 0 ⇒ the default
	// 0.75. Lower values reduce the bias toward short documents (e.g. 0.4 is a
	// reasonable choice for a corpus of mixed-length notes); 0 disables length
	// normalization entirely — but note 0 is read as "use the default", so to
	// approach no normalization use a small value rather than exactly 0.
	B float64 `json:"b,omitempty"`
	// K1 is the BM25 term-frequency saturation parameter (>= 0). 0 ⇒ the default
	// 1.2. Higher values make repeated term occurrences count for more.
	K1 float64 `json:"k1,omitempty"`
}

FulltextParams configures a full-text index. All fields are optional; the zero value reproduces the default BM25 scoring. Per-field weights (Weights) and analyzer/stemming selection are reserved for a later version. The default analyzer (NFKC + case-fold + UAX#29 + CJK bigram) is used for all fields.

type Index

type Index interface {
	// Info returns the IndexInfo for this index.
	Info() IndexInfo

	// Len returns the length of the index.
	Len(ctx context.Context) (int, error)
}

Index represents an index on a collection.

type IndexExplain

type IndexExplain struct {
	Name string
	Cost float64 // CBO computed cost
	Used bool
}

type IndexHint

type IndexHint struct {
	IndexName string
	Boost     int
}

type IndexInfo

type IndexInfo struct {
	// Name is the name of the index. If empty, it will be generated
	// based on the fields (e.g., "name,-createdDate").
	Name string `json:"name"`

	// Fields are the fields included in the index. Each field can specify
	// ascending (e.g., "name") or descending (e.g., "-createdDate") order.
	Fields []string `json:"fields"`

	// Unique indicates whether the index enforces a unique constraint.
	Unique bool `json:"unique"`

	// Sparse indicates whether the index is sparse, indexing only documents
	// with a present, non-null value for every indexed field.
	//
	// Null is treated the same as missing: a document whose indexed field is
	// explicitly null is NOT indexed (this differs from MongoDB, where a sparse
	// index stores present-but-null values). The choice keeps sparse indexing
	// consistent with query matching, where {field: null} matches both a null
	// and a missing field.
	//
	// As a consequence, the planner only uses a sparse index for a query that
	// guarantees every indexed field is present and non-null; otherwise it would
	// silently drop matching documents the index never stored. In particular a
	// query whose only constraint on a field is {$exists: true} cannot use a
	// sparse index here, because an explicit-null document matches $exists:true
	// yet is absent from the index — such a query falls back to a complete index
	// or a full scan. For a unique sparse index, several documents with a missing
	// or null field coexist freely, since none of them are indexed.
	Sparse bool `json:"sparse"`

	// Kind selects the index type (range by default, full-text, or vector).
	Kind IndexKind `json:"kind,omitempty"`

	// Fulltext configures a full-text index when Kind == IndexKindFulltext.
	Fulltext *FulltextParams `json:"fulltext,omitempty"`

	// Vector configures a vector index when Kind == IndexKindVector.
	Vector *VectorParams `json:"vector,omitempty"`
}

IndexInfo provides information about an index.

type IndexKind

type IndexKind uint8

IndexKind selects the kind of index.

const (
	// IndexKindRange is the default B-tree range/equality index.
	IndexKindRange IndexKind = iota
	// IndexKindVector is an approximate-nearest-neighbour index over a vector
	// (embedding) field. Queried via Find() with a
	// `{field: {"$knn": {"$query": [...], "$k": N}}}` clause; results carry a
	// synthetic _distance field (see docs/vector-search.md).
	IndexKindVector
	// IndexKindFulltext is an inverted full-text index over one or more text
	// fields, queried with the $text operator and ranked by BM25. See
	// docs/fts/DESIGN.md.
	IndexKindFulltext
)

type IndexSketchInfo

type IndexSketchInfo struct {
	Size     int      // buckets per level; normally qplanner.DefaultSketchSize
	Levels   int      // number of prefix levels (= number of index fields)
	DocCount uint64   // total documents tracked by this index
	Buckets  []uint64 // per-bucket frequency counts, len == Size*Levels; level L is Buckets[L*Size:(L+1)*Size]
}

IndexSketchInfo is a read-only snapshot of an index's persisted sketch.

type IndexSketchInspector

type IndexSketchInspector interface {
	InspectIndexSketch(ctx context.Context, collName, indexName string) (IndexSketchInfo, error)
}

IndexSketchInspector exposes decoded sketch state for diagnostic and test use. It intentionally lives outside the DB interface — consumers opt in via type assertion so the main public API stays focused.

type IndexStats

type IndexStats struct {
	// Name is the index name.
	Name string

	// Fields are the indexed fields, with leading '-' marking descending order.
	Fields []string

	// Unique reports whether the index enforces a unique constraint.
	Unique bool

	// Sparse reports whether the index skips documents missing the fields.
	Sparse bool

	// EntryCount is the number of entries (key/value pairs) in the index B-tree.
	EntryCount int

	// PayloadBytes is the sum of key+value byte lengths across all entries.
	PayloadBytes int

	// SizeBytes is the physical on-disk size of the index B-tree, computed as
	// its page count (including overflow pages) times the database page size.
	SizeBytes int

	// SketchDocCount is the document count tracked by the index sketch.
	SketchDocCount uint64

	// SketchSize is the number of buckets in the index sketch.
	SketchSize int

	// SketchDistribution summarizes the sketch's bucket frequency distribution.
	SketchDistribution qplanner.SketchDistribution
}

IndexStats describes the storage footprint and sketch state of a single index.

type IntegrityError

type IntegrityError struct {
	PageNo uint32
	Kind   IntegrityErrorKind
	Inner  error
}

IntegrityError describes a single per-page verification failure.

type IntegrityErrorKind

type IntegrityErrorKind int

IntegrityErrorKind discriminates per-page failure types.

const (
	// IntegrityKindUnknown is reserved.
	IntegrityKindUnknown IntegrityErrorKind = IntegrityErrorKind(btree.IntegrityKindUnknown)
	// IntegrityChecksumMismatch indicates an XXH3-128 trailer mismatch
	// (cksum mode).
	IntegrityChecksumMismatch IntegrityErrorKind = IntegrityErrorKind(btree.IntegrityChecksumMismatch)
	// IntegrityAEADAuthFail indicates an AEAD auth-tag failure (AES-GCM
	// or ChaCha20-Poly1305 mode).
	IntegrityAEADAuthFail IntegrityErrorKind = IntegrityErrorKind(btree.IntegrityAEADAuthFail)
)

type IntegrityMode

type IntegrityMode int

IntegrityMode is the codec mode of a DB.

type IntegrityReport

type IntegrityReport struct {
	Mode   IntegrityMode
	Pages  int
	Errors []IntegrityError
}

IntegrityReport is the outcome of VerifyIntegrity.

type Iterator

type Iterator interface {
	// Next advances the iterator to the next document.
	Next() bool

	// Doc returns the current document.
	Doc() (Doc, error)

	// Distance returns the vector distance of the current document to the query
	// for a vector search (smaller is closer). It returns 0 for non-vector
	// queries.
	Distance() float32

	// Score returns the BM25 relevance score of the current document for a $text
	// search (larger is more relevant). It returns 0 for non-fts queries.
	Score() float64

	// Err returns any error encountered during the lifetime of the iterator.
	Err() error

	// Close closes the iterator and releases any associated resources.
	Close() error
}

Iterator represents an iterator over query results.

Mutating the iterated collection through the same write transaction while the iterator is open (deleting or updating documents mid-loop) is UNDEFINED, matching SQLite's same-connection isolation contract: live rows may be skipped or returned more than once, silently. It never corrupts the store and never surfaces an error. To mutate every matched document, collect the ids during iteration and mutate after Close — any-store's own Query.Update/Delete do exactly that internally — or use those verbs directly.

type ModifyResult

type ModifyResult struct {
	// Matched is the number of documents matched by the query.
	Matched int

	// Modified is the number of documents that were actually modified.
	Modified int
}

ModifyResult represents the result of a modification operation.

type PipelinePerfCounters

type PipelinePerfCounters struct {
	Planner qplanner.PerfCounters

	DocCalls           uint64
	DocParsedHits      uint64
	DocFallbacks       uint64
	DocFallbackSeekNs  uint64
	DocFallbackParseNs uint64
}

PipelinePerfCounters aggregates iterator and Doc() fallback profiling counters.

func SnapshotPipelinePerfCounters

func SnapshotPipelinePerfCounters() PipelinePerfCounters

SnapshotPipelinePerfCounters returns current profiling counters.

type Query

type Query interface {

	// Limit sets the maximum number of documents to return.
	Limit(limit uint) Query

	// Offset sets the number of documents to skip before starting to return results.
	Offset(offset uint) Query

	// Sort sets the sort order for the query results.
	Sort(sort ...any) Query

	// IndexHint adds or removes boost for some indexes
	IndexHint(hints ...IndexHint) Query

	// Iter executes the query and returns an Iterator for the results. See
	// Iterator for the mutation-while-iterating contract (undefined,
	// SQLite-style).
	Iter(ctx context.Context) (Iterator, error)

	// Count returns the number of documents matching the query.
	Count(ctx context.Context) (count int, err error)

	// Update modifies documents matching the query.
	Update(ctx context.Context, modifier any) (res ModifyResult, err error)

	// Delete removes documents matching the query.
	Delete(ctx context.Context) (res ModifyResult, err error)

	// Explain provides the query execution plan.
	Explain(ctx context.Context) (explain Explain, err error)
}

Query represents a query on a collection.

type ReadTx

type ReadTx interface {
	// Context returns the context associated with the transaction.
	Context() context.Context

	// Commit commits the transaction.
	// Returns an error if the commit fails.
	Commit() error

	// Done returns true if the transaction is completed (committed or rolled back).
	Done() bool
	// contains filtered or unexported methods
}

ReadTx represents a read-only transaction.

type VFS

type VFS = btree.VFS

type VectorIndexStats

type VectorIndexStats struct {
	// Name is the index name.
	Name string

	// Field is the indexed embedding field path.
	Field string

	// Dim is the embedding dimension; Metric is the distance metric.
	Dim    int
	Metric string
	// Mode is the index strategy ("btree" | "hybrid" | "brute").
	Mode string
	// Quantization is the stored vector format ("none" | "int8").
	Quantization string

	// M / EfSearch are the HNSW graph parameters.
	M        int
	EfSearch int

	// NodeCount is the number of nodes ever allocated (including tombstones);
	// LiveCount excludes tombstones; DeletedCount is the tombstone count. A high
	// DeletedCount/NodeCount ratio signals that a compaction/rebuild is due.
	NodeCount    int
	LiveCount    int
	DeletedCount int

	// VectorBytes / GraphBytes / MappingBytes / MetaBytes are the on-disk sizes
	// of the vector, adjacency, docId<->label, and meta namespaces respectively.
	VectorBytes  int
	GraphBytes   int
	MappingBytes int
	MetaBytes    int

	// SizeBytes is the total physical size of the index (sum of the above).
	SizeBytes int
}

VectorIndexStats describes a vector (HNSW) index: its parameters, node occupancy, and the physical size of its backing namespaces.

type VectorMetric

type VectorMetric uint8

VectorMetric selects the distance measure for a vector index.

const (
	VectorCosine VectorMetric = iota
	VectorL2
	VectorDot
)

type VectorMode

type VectorMode uint8

VectorMode selects the index strategy for a vector index.

const (
	// VectorModeBTree stores the full HNSW graph in the btree (default): lowest
	// RAM, multiprocess-safe writes, approximate search.
	VectorModeBTree VectorMode = iota
	// VectorModeHybrid is VectorModeBTree plus a RAM-resident copy of HNSW layer 0
	// for faster search; the btree remains the source of truth.
	VectorModeHybrid
	// VectorModeBruteForce stores no index data — the index is only a declaration
	// that a field is a vector. Search scans the collection and computes exact
	// distances: ~free writes, ~0 storage, exact (100%) recall, O(N) search.
	VectorModeBruteForce
	// VectorModeIVFPQ is a btree-resident IVF-PQ index (see
	// vector/RESEARCH_IVFPQ_BTREE.md): vectors are partitioned into nlist coarse
	// cells and each is stored as a compact product-quantization code of its
	// residual. Search probes a few cells (contiguous btree range scans — no random
	// graph traversal) and re-ranks the survivors by exact distance. Far smaller hot
	// RAM set (only the coarse centroids) and a sequential read pattern, at the cost
	// of approximate recall recovered by re-rank. See VectorParams NList/NProbe/Closure.
	VectorModeIVFPQ
	// VectorModeIVFSQ is the same IVF partition but stores each vector as an int8
	// scalar-quantized full vector per cell (no product quantization), scanned
	// directly with exact int8 distance — no ADC table, no precomputed table, no
	// re-rank. Versus IVFPQ: much faster builds (no PQ training), higher recall (int8
	// is more faithful than PQ), smaller (no separate re-rank store), and no
	// precompute-table RAM — at higher search cost (it reads full vectors, not codes).
	// Favours Closure=1 with a higher NProbe (replicating full vectors is costly).
	VectorModeIVFSQ
)

func (VectorMode) String

func (m VectorMode) String() string

type VectorParams

type VectorParams struct {
	// Field is the path to the embedding field (an array of numbers).
	Field string `json:"field"`
	// Dim is the embedding dimension.
	Dim int `json:"dim"`
	// Metric is the distance measure (default cosine).
	Metric VectorMetric `json:"metric"`
	// M / EfConstruction / EfSearch tune the HNSW graph; 0 = sensible defaults.
	M              int `json:"m,omitempty"`
	EfConstruction int `json:"efConstruction,omitempty"`
	EfSearch       int `json:"efSearch,omitempty"`
	// Quantization selects the stored vector format (default full float32). For
	// VectorModeIVFPQ it sets the format of the re-rank vector store (the bulk of the
	// index): VectorQuantInt8 makes it ~4× smaller on disk and in the page cache —
	// cutting RAM and speeding the random per-candidate re-rank reads — at ~0.5%
	// recall, and is recommended there.
	Quantization VectorQuantization `json:"quantization,omitempty"`
	// Mode selects the index strategy (default btree). See VectorMode.
	Mode VectorMode `json:"mode,omitempty"`
	// HybridCacheVectors, for VectorModeHybrid, also caches vectors in RAM (the
	// "vector tier") so layer-0 search avoids btree vector reads entirely — the
	// bulk of search cost. RAM ≈ liveVectors × recordSize (use int8 to keep it
	// small). Ignored unless Mode == VectorModeHybrid.
	HybridCacheVectors bool `json:"hybridCacheVectors,omitempty"`

	// CompactRatio enables automatic compaction of the HNSW graph. Deletes and
	// replaces only tombstone nodes; when tombstones reach CompactRatio × live
	// nodes, the index is rebuilt to reclaim them (dropping dead nodes, superseded
	// vectors, and re-densifying labels). 0 (the default) disables auto-compaction
	// — the index is then only compacted via Collection.CompactVectorIndex.
	//
	// Choosing a value (measured — see TestVectorCompactThreshold): tombstoned
	// nodes are still traversed during search, so latency rises roughly linearly
	// with the deleted/live ratio (recall does NOT — it holds/improves as the live
	// set shrinks). Search latency degrades ~30% at a ratio of ~0.2–0.25, ~50% at
	// ~0.5, and ~2x at ~1.0 (steeper at larger N / higher dim). With
	// HybridCacheVectors, tombstones also inflate the RAM vector tier (it tracks
	// the label high-water mark, not the live set), so compaction reclaims RAM too.
	// A rebuild costs ~one re-insert of the live set, so the amortized compaction
	// cost per delete is ~insertCost / CompactRatio — i.e. a smaller ratio caps
	// latency tighter but rebuilds far more often. Balanced default: ~0.5. Use
	// ~0.25 only for read-latency-sensitive, delete-light workloads.
	//
	// Auto-compaction runs synchronously, in its own transaction, right after the
	// self-contained write that crosses the threshold — never inside a
	// caller-managed transaction. The rebuild is O(live) at ~850 nodes/s for dim
	// 768 (measured, see TestVectorCompactTiming): ~23 s to rebuild 19k live, ~45 s
	// for 38k. It surfaces as a latency spike on the triggering write, so for large
	// indexes prefer leaving this 0 and scheduling Collection.CompactVectorIndex in
	// a maintenance window.
	// Ignored for VectorModeBruteForce.
	//
	// For VectorModeIVFPQ, CompactRatio instead bounds *centroid drift*: IVF deletes
	// are physical (no tombstones), but the codebooks are frozen at build, so as the
	// data distribution shifts the partition degrades. CompactRatio triggers an
	// automatic rebuild (re-train from the live set) when the drift score —
	// max(reconstruction-error ratio − 1, churn ratio) — reaches it. ~0.5 rebuilds
	// after the live set roughly doubles or new data fits the centroids ~50% worse;
	// 0 disables auto-rebuild (use Collection.CompactVectorIndex manually).
	CompactRatio float64 `json:"compactRatio,omitempty"`

	// IVF-PQ parameters (Mode == VectorModeIVFPQ only). Zero values pick defaults.
	//
	// NList is the number of coarse cells (k-means centroids); 0 ⇒ ~4·√N at build.
	// More cells = finer partition = fewer vectors scanned per probe, but more
	// centroids to compare and a larger training requirement.
	NList int `json:"nList,omitempty"`
	// NProbe is how many cells a search scans (the recall/speed dial); 0 ⇒ 16.
	// Higher = more recall, more cells scanned.
	NProbe int `json:"nProbe,omitempty"`
	// Closure is the multi-assignment factor: each vector is placed in its Closure
	// nearest cells (with a per-cell residual code) so boundary vectors are found at
	// lower NProbe (SPANN closure). 0/1 = single assignment; ~4 reaches parity at
	// ~4× lower NProbe, costing ~Closure× the on-disk code bytes. M (above) sets the
	// number of PQ subquantizers / code bytes; Dim must be divisible by M.
	Closure int `json:"closure,omitempty"`
	// PrecomputeTableMiB budgets the RAM for the IVF-PQ precomputed ADC table, which
	// removes the per-cell distance-table rebuild from search (measured ~3× faster
	// search at dim 768) at the cost of an always-resident table of nlist·M·1 KiB.
	// 0 = the default budget (128 MiB: on for typical indexes, off for very large
	// ones whose table would exceed it); a negative value disables the table
	// (smallest RAM, slower search); a positive value sets the budget in MiB.
	PrecomputeTableMiB int `json:"precomputeTableMiB,omitempty"`
}

VectorParams configures a vector (HNSW) index.

type VectorQuantization

type VectorQuantization uint8

VectorQuantization selects how the index stores vectors.

const (
	// VectorQuantNone stores full float32 vectors (default).
	VectorQuantNone VectorQuantization = iota
	// VectorQuantInt8 stores scalar-quantized int8 vectors (~4x less storage /
	// page-cache RAM) at a small recall cost.
	VectorQuantInt8
)

type WriteTx

type WriteTx interface {
	// ReadTx is embedded to provide read-only transaction methods.
	ReadTx
	// Rollback rolls back the transaction.
	// Returns an error if the rollback fails.
	Rollback() error

	// SetModified marks the transaction as having made modifications
	// used internally for sentinel mechanism
	SetModified()
	// contains filtered or unexported methods
}

WriteTx represents a read-write transaction.

Directories

Path Synopsis
cmd
vectorbench command
Command vectorbench is a self-contained benchmark for the experimental any-store vector (HNSW) search package.
Command vectorbench is a self-contained benchmark for the experimental any-store vector (HNSW) search package.
docs
btree/mappings/scripts/build_mappings command
Rebuilds the derived mapping artifacts from go_to_sqlite.json.
Rebuilds the derived mapping artifacts from go_to_sqlite.json.
btree/mappings/scripts/extract_codec_blocks command
Extracts every SQLCipher codec hook block from ../sqlcipher/src and writes docs/btree/mappings/sqlcipher_codec_blocks.gen.json so mappings_diff can surface any block that has no corresponding row in the hand-edited sqlcipher_codec.json.
Extracts every SQLCipher codec hook block from ../sqlcipher/src and writes docs/btree/mappings/sqlcipher_codec_blocks.gen.json so mappings_diff can surface any block that has no corresponding row in the hand-edited sqlcipher_codec.json.
btree/mappings/scripts/extract_funcs command
Extracts function lists from two sources and writes them to JSON files:
Extracts function lists from two sources and writes them to JSON files:
btree/mappings/scripts/mappings_diff command
Reports drift between the freshly-extracted allowlists (*.gen.json) and the hand-edited mapping inputs (go_to_sqlite.json, sqlite_skip.json, sqlcipher_codec.json).
Reports drift between the freshly-extracted allowlists (*.gen.json) and the hand-edited mapping inputs (go_to_sqlite.json, sqlite_skip.json, sqlcipher_codec.json).
internal
btree
Package btree implements an embedded, crash-safe, ordered key-value database engine in pure Go, closely following SQLite's design for the B-tree, pager, WAL, and transaction subsystems.
Package btree implements an embedded, crash-safe, ordered key-value database engine in pure Go, closely following SQLite's design for the B-tree, pager, WAL, and transaction subsystems.
fts
Package fts implements the lexical full-text-search layer of any-store.
Package fts implements the lexical full-text-search layer of any-store.
simd
Package simd is any-store's single home for vector distance math.
Package simd is any-store's single home for vector distance math.
simd/asm
asm only has amd64 specific implementations at the moment
asm only has amd64 specific implementations at the moment
vecf
Package vecf holds the float32-vector storage helpers shared by internal/vindex and internal/vivf: unsafe f32<->byte reinterpretation and the int8 offset-binary scalar-quantization core.
Package vecf holds the float32-vector storage helpers shared by internal/vindex and internal/vivf: unsafe f32<->byte reinterpretation and the int8 offset-binary scalar-quantization core.
vindex
Package vindex implements a btree-resident HNSW vector index for any-store.
Package vindex implements a btree-resident HNSW vector index for any-store.
vivf
Package vivf is a Phase-0, in-RAM prototype of a btree-native IVF-PQ vector index (see vector/RESEARCH_IVFPQ_BTREE.md).
Package vivf is a Phase-0, in-RAM prototype of a btree-native IVF-PQ vector index (see vector/RESEARCH_IVFPQ_BTREE.md).
Package vector is an experimental vector-search PROTOTYPE for any-store.
Package vector is an experimental vector-search PROTOTYPE for any-store.

Jump to

Keyboard shortcuts

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