Guidance for AI coding agents working in this repository.
DoltLite is a fork of SQLite that replaces the B-tree storage engine with a
content-addressed prolly tree,
giving a SQL database Git-like version control (branch / commit / merge / diff)
while staying embeddable as a single library. SQLite's btree.h interface is
the architectural seam: the tokenizer, parser, planner, and VDBE remain
upstream-derived, while a DoltLite-format primary database replaces SQLite's
B-tree, pager, and on-disk format with a prolly-tree engine backed by a
single-file, content-addressed chunk store.
The btree.h seam is not a do-not-edit boundary. Changes to upstream-derived
code above it are allowed when DoltLite integration or correctness requires
them, but they must be narrowly scoped and guarded by DOLTLITE_PROLLY so a
DOLTLITE_PROLLY=0 build retains stock SQLite behavior. Prefer solving a
storage-engine concern below the seam when the interface permits it.
DoltLite is developed on Git and hosted on GitHub (https://github.com/dolthub/doltlite). Unlike upstream SQLite, it uses pull requests and accepts agentic contributions — opening PRs is the normal workflow (see PR / git workflow below).
DoltLite is licensed Apache-2.0 (LICENSE.md). This differs from upstream
SQLite, which is public domain.
- Upstream SQLite files (
src/btree.c,src/vdbe.c,src/where*.c, …) keep their public-domain "blessing" comment. Preserve it unchanged; never add a license header to them. - DoltLite source (
src/doltlite_*.c,src/prolly_*.c,src/chunk_*.c, all guarded by#ifdef DOLTLITE_PROLLY) carries no per-file license header. Match the surrounding file — do not add one. - No issue or PR numbers in code comments; those references belong in commit messages and PR descriptions.
Do not add explanatory comments. The comment bar in this codebase is very high — aim for effectively none. Only keep a comment that is load-bearing: one that captures a non-obvious invariant, a subtle correctness reason, or a "why it must be this way" that the code cannot express on its own. Everything else — restating what the code does, section banners, narration of obvious steps, TODO chatter — makes the code worse and must be stripped.
Agents over-comment by default; consciously resist it. Write code that reads like the surrounding DoltLite source (which is nearly comment-free), and when in doubt, leave the comment out.
The build uses autosetup from a
separate build/ directory.
cd build
../configure
make doltlite # CLI shell — the prolly engine (default)
make doltlite-lib # libdoltlite.a + .so/.dylib + doltlite.h
make DOLTLITE_PROLLY=0 sqlite3 # stock SQLite, for oracle/perf comparison- On macOS with Homebrew, link needs
LIBRARY_PATH=/opt/homebrew/lib. - The test harness runs
build/doltlite. Rebuildbuild/after anysrc/change or you validate a stale engine. A repo-root./doltliteor./sqlite3built over the prolly.ofiles is DoltLite-in-disguise; to get real stock SQLite for comparison, build withDOLTLITE_PROLLY=0in a clean directory. make lintruns the layering and raw-file-I/O guards oversrc/.
- Upstream-derived SQLite core —
src/*.c(parser, planner,vdbe.c,where*.c, …), kept close to upstream abovebtree.h; DoltLite-specific changes there are guarded byDOLTLITE_PROLLY. Master headersrc/sqliteInt.h. - Storage engine —
src/prolly_*.c(prolly-tree node, cursor, mutmap, diff, three-way merge, chunker, cache, hashing) andsrc/chunk_*.c(content-addressed chunk store, WAL, refs, staging, file format). - Version-control surfaces —
src/doltlite_*.c, roughly one file per feature:doltlite_commit(wire format),doltlite_commit_cmd/doltlite_add/doltlite_reset/doltlite_merge_cmd/doltlite_cherry_pick/doltlite_revert/doltlite_rebase/doltlite_config(SQL commands),doltlite_core(txn seal, catalog flush, create-commit helpers),doltlite_cmd(shared command scaffolding: option errors, peer-branch BUSY, conflict/CV txn-mode outcomes),doltlite_branch(branch/checkout),doltlite_merge(catalog orchestration indoltlite_merge, pass1/pass2 indoltlite_merge_pass1/doltlite_merge_pass2, rows indoltlite_merge_rows, schema IR indoltlite_merge_schema, plus constraint detectors indoltlite_merge_constraints/_unique/_check/_fk),doltlite_diff/doltlite_diff_stat/doltlite_diff_table,doltlite_log,doltlite_history,doltlite_blame,doltlite_tag,doltlite_conflicts,doltlite_constraint_violations/doltlite_verify_constraints,doltlite_schemas/doltlite_schema_diff,doltlite_patch,doltlite_status,doltlite_workspace,doltlite_ignore,doltlite_docs,doltlite_tests,doltlite_hashof,doltlite_gc,doltlite_remote/doltlite_http_remote/doltlite_remotesrv. Internal header:src/doltlite_internal.h; surfaces are registered insrc/doltlite.c.
DoltLite exposes version control as SQLite virtual tables (system tables:
dolt_log, dolt_diff, dolt_diff_<table>, dolt_conflicts,
dolt_constraint_violations, dolt_status, …) and scalar functions / TVFs
(SELECT dolt_commit(...), SELECT dolt_merge(...), SELECT dolt_branch(...)).
Dolt exposes the same operations as stored procedures (CALL dolt_commit(...)).
This vtable form, and DoltLite-flavored column names, are intentional — not conformance bugs. When comparing against Dolt, only row-level semantics must match; do not file conformance issues over the vtable/function shape or column naming.
dolt_ignore, dolt_docs, and dolt_tests are lazy user-space system
tables: an eponymous writable vtab answers reads while no backing table
exists (SELECT never errors on a fresh repo), and the first write
statement materializes the real table, which then shadows the module.
That is Dolt's lazy-creation UX without the open-time auto-materialization
that sank the original dolt_ignore attempt (fcb7720e00). dolt_ignore
materializes
dolt_ignore(pattern TEXT NOT NULL, ignored TINYINT NOT NULL, PRIMARY KEY(pattern)).
dolt_docs materializes
dolt_docs(doc_name TEXT NOT NULL, doc_text TEXT NOT NULL, PRIMARY KEY(doc_name))
and serves a default AGENT.md row (DoltLite's own operations guide, not
Dolt's embedded text) from the module before materialization; that row is
seeded as a stored row when the table materializes. Deliberate divergences
from Dolt: the table shows in .tables/.schema once it exists; the
default AGENT.md text differs (the docs oracle compares it by name/count,
or by full row after a test overwrites it); and because the row is stored
rather than synthesized, deleting AGENT.md sticks (Dolt resurrects it on
the next read) and it appears in row-level diffs. build.c shape guards
keep hand-issued CREATE TABLE statements on the exact schema each module
creates.
dolt_tests uses the same lazy user-space system-table pattern: empty reads
resolve before a backing table exists, and the first write materializes the
six-column table with Dolt's assertion checks. dolt_test_run(...) executes
the versioned definitions as read-only single-statement queries and returns
the Dolt-compatible test result rows.
Absent on purpose, so don't file them as parity gaps or implement them unprompted:
-
dolt_stash— not on the roadmap. Stashing exists because git binds one working tree to a clone, so switching branches forces you to shelve uncommitted work first. DoltLite gives every branch its own working set:dolt_checkoutbetween dirty branches neither refuses nor carries changes over, and connections addressingdb/branchhold independent uncommitted state at the same time. The shelve/switch/unshelve dance has nothing to solve here. See Dolt Worktrees. -
CLI-parity wrappers around plain SQL —
dolt_rm,dolt_mv, and anything else whose whole job is to spell a statement SQL already has. These exist in Dolt so its subcommands have a server-side equivalent:dolt_rm's own source calls itself "the stored procedure for the cli command dolt rm", anddolt mvis a CLI command that buildsDROP TABLE/ rename SQL —dolt_mvis not even a Dolt procedure. DoltLite has no subcommand CLI to be compatible with, soDROP TABLEandALTER TABLE … RENAME TOare the surface. -
dolt_statistics— DoltLite already has branch-specific table statistics; they are spelledANALYZEandsqlite_stat1. Dolt keeps stats in a separate branch-keyed database that is never merged, because it maintains them automatically — continuously regenerated derived data cannot sit in the commit graph. SQLite produces statistics only when the user asks, sosqlite_stat1is an ordinary versioned table: per-branch for free, and it commits, diffs and merges with everything else. Keeping it accurate is the user's call, same asANALYZEanywhere else. -
dolt_query_catalog— DoltHub-specific storage, not engine behavior: it holds saved queries so DoltHub can display them. Nothing local reads it. Worth revisiting if pushing to DoltHub lands, and not before.
Dolt is the authority on what every
version-control operation should do. When a behavior is ambiguous, when
DoltLite and Dolt disagree, or when implementing a new VC surface, read the
Dolt source (Go) to determine the correct semantics — result shape, diff/
merge/conflict rules, dolt_* system-table columns, error conditions — and
match it. It's the reference for the oracle suites and for the engine itself.
Dolt may be checked out locally as a sibling of this repo; otherwise consult it
on GitHub.
Several independent layers. A change under src/ should run the relevant ones;
dolt on PATH is required for the oracle suites.
test/run_doltlite_tests.sh— DoltLite-native shell suites (doltlite_*.sh) covering branch/commit/diff/merge/checkout/etc. behavior and parity. Runsbuild/doltlite. This layer is separate from the regression and C tests and catches branch / default-branch / parity bugs they miss — always run it for version-control changes.test/vc_oracle_*_test.sh— Dolt oracle suites: run identical SQL against DoltLite (SELECT dolt_x) and real Dolt (CALL dolt_x) and diff the normalized output. Invoke asbash test/vc_oracle_X_test.sh build/doltlite dolt. CI auto-runs anything matchingvc_oracle_*_test.sh/oracle_*_test.sh, so a new suite with that name needs no wiring. Compare semantics, not vtable shape.- Regression buckets —
test/regression-buckets/*.txtlist the inherited and ported upstream*.testfiles gated viatestfixture.testfixtureis built withSQLITE_TEST(the CLI is not), so don't#ifdef SQLITE_TEST- guard a correctness fix, and verify fixes intestfixture, not only the CLI. Everydoltlite_*.testsuite must appear in a bucket ortest/lint_orphaned_suites.shfails. test/run_c_tests.sh— C unit tests (concurrency, crash recovery, serialize determinism, chunk-store locking, …). Locally it builds the tests it gates and flags binaries older thansrc/; under CI it runs the build phase's artifacts untouched. Either way not built and stale are counted separately from failed, so a skipped or out-of-date binary can never read as a pass.test/run_sqllogictest.sh— at full pass / 100% parity.test/known_sqllogictest_divergences.txtis empty and must stay empty: a new divergence means fix the engine, never add a known-divergence entry.
Rules that override convenience:
- Never delete a test or disable a check. If a test or assertion fails, fix the change, not the guardrail.
- For an observable bug, prove the fix with a fail-before / pass-after test (fails on the unfixed engine, passes with the fix). Latent/defensive fixes may legitimately have no such test.
- When local validation would take more than ~15 min, push a PR and let CI run the buckets/corpus/suites in parallel; spot-check locally.
- Branch off a freshly pulled
master; open the PR againstmaster. Do not stack PRs — a stacked branch leaves commits dangling when its base merges first; rebase ontomasterinstead. - Stage by explicit path (
git add src/foo.c test/bar.sh).git add -A/git add .sweep in hundreds of untracked build artifacts (*.o,tsrc/*.c,./doltlite,./sqlite3,testfixture). - Commit or push only when asked. End commit messages and PR bodies with the trailers the harness expects.
- Conflicts are never persisted. A conflicted merge lives only in the
transaction that produced it:
COMMITis refused while any conflict remains, an autocommit merge that conflicts is rolled back whole, and the persist path declines to make a conflicted working set durable. Nothing conflicted reaches disk, so no later connection inherits a merge to finish. Dolt allows this behind@@dolt_allow_commit_conflicts, which DoltLite does not implement, so conflict persistence is a deliberate divergence — compare in-transaction behaviour against Dolt, not what survives a commit. Constraint violations are unaffected and still persist.
Subtle rules that are easy to break silently — hold them when touching VC code:
- Re-confirm under the lock. Multi-step ops (merge / cherry-pick / revert / pull) must re-read HEAD under the lock immediately before advancing a ref, or they clobber a concurrent peer.
- Scoped ref installs. A pushed ref update must go through the scoped-refs validator (only the declared branch may change); never install a pushed blob wholesale. Clone install is unscoped by design.
- Canonical catalog. Schema commits adopt the canonical catalog; the live
catalog must always equal the persisted one. Constructed catalog arrays
(staging / merge /
-am) adopt the single master root paired byiTable==1, never by name. - Ref resolution for commit / branch /
HEAD/WORKING/STAGEDgoes throughdoltliteResolveCatalogHashForRef— reuse it, don't re-derive.
User-facing concurrency guarantees and the multiproc/multi-connection oracles
that pin them live in the README Concurrency section and
test/concurrency_contract.tsv. test/concurrency_contract_test.sh fails if a
claim loses its evidence needle. When changing graph lock, snapshot pin or
write upgrade, locked ref expectation/CAS paths, multiproc harnesses, or
conflict durability, update the TSV and keep the multiproc C suites green
(multi_process_*, concurrent_* via test/run_c_tests.sh).
The version-control correctness invariants above remain load-bearing for implementors even when a claim is also listed in the contract.
The user-facing SQLite compatibility contract lives in the README SQLite
Compatibility section; claims and evidence live in
test/sqlite_compatibility_contract.tsv. Update the README, contract row, and
its evidence together when observable compatibility behavior changes.
Do not infer poor SQLite compatibility from an intentional storage-engine
adaptation. DoltLite uses chunks rather than pages, has no SQLite WAL or
rollback journal, and cannot use an anonymous rowid as a version-controlled
key because identity must be history-independent. Classify inherited-suite
exceptions through test/known_testfixture_divergences.txt: engine-gap is a
bug to fix, while intentional, unsupported, and harness entries describe
known boundaries. SQLLogicTest remains a zero-divergence correctness gate.
Chunk-store version 12 is the beta format freeze. User-facing rules live in
the README Storage Format section; claims and evidence live in
test/storage_format_contract.tsv, and the golden file lives under
test/format-corpus/v12/.
Bumping CHUNK_STORE_VERSION requires:
- README Storage Format update
- A new
test/format-corpus/entry and MANIFEST storage_format_contract.tsvevidence update- Explicit open/upgrade policy for version 12 (open-only, migrate, or refuse)
Any incompatible change to a nested write format, including working sets,
catalogs, refs, commits, prolly nodes, or key encoding, must bump
CHUNK_STORE_VERSION. Do not silently reinterpret another version.
The release workflow also runs test/doltlite_compat_test.sh against prior
releases. Run it when changing format recognition, readers, writers, or version
handling; version-12 databases already created by Beta users are compatibility
fixtures, not disposable test data.
DoltLite is public Beta. Move quickly on engine fixes, but treat documented
behavior, version-12 data, the compatibility and concurrency contracts, public
C headers and exports, release packages, and binding inputs as shipped
surfaces. A change under packaging/, the install/export rules, amalgamation
generation, or .github/workflows/release.yml must run the relevant package or
artifact smoke test; core engine tests alone do not validate a distributed
artifact.
External integration and documentation work is no longer a pre-Beta deprioritized category. Scope validation to the surface being changed, preserve the published contracts, and fix the engine with fail-before/pass-after evidence when behavior is wrong.