Files
cloud/sqlpool/sqlpool.go
hanzo-dev 6b5262349c cek opens the file; the driver sets the pragmas; cloud says neither twice
basedb existed for exactly one reason: cek.Open chose a path and did not create
its parent, and on the pure-Go codec that does not fail the open — the database
is written back at CLOSE, so a missing directory loses every write of the session
after the caller has been told it had a store. cek v0.2.1 does the MkdirAll
itself, pinned by a round-trip test, so the wrapper is now a synonym. It is
deleted and its 70 call sites name cek directly.

The larger duplication was underneath it. Sixty stores each set
SetMaxOpenConns(1) and re-applied busy_timeout, journal_mode=WAL and
foreign_keys=ON by hand. hanzoai/sqlite already applies those, on EVERY
connection — which the hand-rolled db.Exec did not: a one-shot Exec lands on
whichever connection happens to serve it and is gone the moment that connection
is recycled. So the fifty-two copies were not merely a fact restated fifty-two
times, they were the weaker of the two mechanisms shadowing the stronger one.
They say nothing now, and sqlpool_test.go asserts the driver still delivers each
default, so the deletion goes red in one place instead of rotting in fifty.

What is NOT already universal is the pool cap. sqlite sets it on the envelope
path and not on the live-libsqlcipher path, which is the one the shipped image
builds — so the cap is real, and it is stated once, in sqlpool.Single. The
package imports nothing but database/sql, so every store can reach it.

Two databases were outside all of this and are not any more. team keeps its
pragmas, because a dynamic journal_mode and foreign_keys=OFF are an override
rather than a restatement. git's ssh-key registry was a bare sql.Open on a
hand-joined path — no namespace, no key, and consequently the only store in the
binary written to disk in plaintext. It opens through cek like everything else.

hanzoai/sqlite stays at v0.4.0. v0.5.0 deletes the DEK/principal API
(PrincipalType, NewDEK, WrapDEK, UnwrapDEK, PrincipalAAD, DeriveKey) that
hanzoai/commerce and hanzoai/tasks still compile against, and no published
version of either has migrated, so taking it breaks the build. cek v0.2.1 does
not want it either — it requires v0.4.0.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-08-01 22:05:54 -07:00

33 lines
1.7 KiB
Go

// Package sqlpool states, once, how cloud pools connections to a SQLite file.
//
// It is one sentence — one connection — and it is a package so that the sentence
// has somewhere to be true. It used to be written at every store that opened a
// database, together with a block of PRAGMAs restating the driver's own defaults;
// fifty-odd copies of a fact is fifty-odd chances for one of them to drift.
//
// The PRAGMAs are gone because hanzoai/sqlite already applies them, on every
// connection, on every backend — see sqlpool_test.go, which asserts exactly that
// and fails if it ever stops being true. What is NOT already applied everywhere
// is the pool cap, so that is what remains here.
package sqlpool
import "database/sql"
// Single caps db at one connection.
//
// These databases are single-writer by construction, and two facts depend on the
// cap rather than merely benefiting from it:
//
// - A read-modify-write spanning two statements (tracker's per-project issue
// number, agents' MAX(seq)+1 event allocation) is atomic ONLY because no
// second connection can interleave. Widen the pool and those become races
// that a UNIQUE index turns into errors instead of corruption — on a good day.
// - On the pure-Go codec the file is an envelope decrypted to one plaintext
// copy; a checkpoint quiesces writers by taking that single connection.
//
// hanzoai/sqlite already caps the pool on the envelope path (envelope.go, at the
// sql.OpenDB) but NOT on the live-libsqlcipher path, which is the one the shipped
// image builds. Until that asymmetry is fixed upstream the cap has to be stated
// by the caller, and this is where cloud states it.
func Single(db *sql.DB) { db.SetMaxOpenConns(1) }