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_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.
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_docs,dolt_query_catalog— DoltHub-specific storage, not engine behavior:dolt_docsholdsREADME.md/LICENSE.mdanddolt_query_catalogholds saved queries, both so DoltHub can display them. Nothing local reads either. 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.
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.
External integration tests and remaining pre-beta docs freezes are deprioritized when they are not on the beta path. Prefer fixing the engine and proving it with tests; format and concurrency contracts that already ship must stay accurate.