Fixes GO-2026-6629 (golang.org/x/text precis panic). Bumps bun and bun/dialect/pgdialect together to v1.3.0; go directive now 1.26.0.
12 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this package is
modernc.org/sqlite is a pure-Go database/sql/driver for SQLite — CGo-free. The SQLite C amalgamation is transpiled to Go via modernc.org/ccgo; the generated code lives in this repo as per-GOOS/GOARCH files under lib/ (SQLite itself), vec/ (the sqlite-vec extension), and vfs/ (the C side of the Go-fs VFS bridge). Runtime support — malloc, pthread, syscalls, etc. — is provided by modernc.org/libc.
The hand-written Go on top of that transpiled core implements the database/sql/driver shim and additional Go-facing APIs (virtual tables, VFS, hooks, UDFs).
Repository layout (the parts that aren't self-evident)
sqlite.go,conn.go,driver.go,stmt.go,rows.go,tx.go,backup.go,error.go,result.go,convert.go— hand-writtendatabase/sql/driverimplementation calling intolib/.vtab.go,pre_update_hook.go,fcntl.go,mutex.go,ofd.go— Go-facing extensions wired to SQLite hooks/trampolines (ofd.go: the process-wide opt-in switch to Linux OFD locks, backed bymodernc_ofd_locking()in the transpiled library; the C side lives in../libsqlite3/internal/sqlite_issue255.patch{,2}).lib/— transpiled SQLite 3.53.4. Onesqlite_<goos>_<goarch>.goper supported triple plus build-taggedsqlite_g_*.gofiles holding declarationsmodernc.org/undupdeduplicated across triples (so to check what code a target compiles, resolve its full GoFiles viago list, not by filename);defs.go,hooks.go,hooks_linux_arm64.go,mutex.go, pluslibsqlite3_freebsd.go/libsqlite3_windows.gohold hand-written patches that augment the generated code. Import assqlite3 "modernc.org/sqlite/lib".vec/— transpiledsqlite-vecv0.1.9, auto-registers viasqlite3_auto_extensioninpatches.goon package init. Activate by blank-importing:_ "modernc.org/sqlite/vec". Covers the same 20 targetslib/does (20 is the count inbuilder.jsonandmake build_all_targets; the 18 per-target files are fewer becausewindows/amd64andwindows/arm64sharesqlite_windows.go);vec_test.go's//go:buildconstrains by GOOS only.vfs/— exposes a Gofs.FSas a read-only SQLite VFS.vfs.New(fsys)returns a registered VFS name; open with?vfs=<name>. C side is transpiled per platform fromvfs/c/vfs.cvia thevfs/Makefile.vtab/— Go-facing virtual-table API (no dependency on the transpiled C).vtab.RegisterModule(db, name, module)registers modules on new connections only; a nildbtargets the driver registered assqlite, a non-nildbthe driver backing it (viavtab.ModuleRegisterer). The bridge to C lives in the top-levelvtab.go. Seevtab/doc.gofor the contract (Updater/Renamer/Transactional optional interfaces, re-entrancy rules, ArgIndex/Omit semantics).licensegen/(own module, build tagnone, built withgo build -tags none .) — generatesLICENSE-3RD-PARTY.md,SBOM.md,sbom.cdx.json(CycloneDX 1.6) andsbom.spdx.json(SPDX 2.3) fromgo list -m all, the license files in the module cache, the notice files dependencies carry (modernc.org/libc'sLICENSE-3RD-PARTY.md, which is how musl reaches us), and the vendored C. Classifies every component as linked / test-only / module-graph-only. Deterministic: no timestamps anywhere, SPDX's requiredcreatedpinned to the epoch and itsdocumentNamespacederived from the document's own content, so regenerating an unchanged tree is byte-identical and./licgen -checkis meaningful. Both JSON documents validate against the published CycloneDX 1.6 and SPDX 2.3 schemas.sbom.goholds the SBOM writers,main.gothe inventory and the Markdown. Invoked bymake licenses, and wired into one of the repository's two CI jobs:.gitlab-ci.ymlrunslicgen -checkwhengo.mod,go.sum,licensegen/,lib/sqlite.go,vec/vec.goor any of the four documents change; the other runsgo test ./internal/vendorstamp/. Nothing else runs in GitLab CI -- tests and cross-builds live on the builder farm. TheLICENSEname prefix is load-bearing:go mod vendormatches metadata files by case-sensitive prefix, so3RD_PARTY_LICENSES.mdwould never reach downstreamvendor/trees -- the same trap as the v1.57.0SQLITE-LICENSErename. Downloads into a scratch module so the repo'sgo.sumis never touched, and forcesGOWORK=offso amake workworkspace cannot leak into the document.vendor_libs/main.go(build tagnone) — regeneration tool. Reads transpiledccgo_<goos>_<goarch>.gofrom sibling repos../libsqlite3and../libsqlite_vec, rewrites package names and imports, and writeslib/sqlite_*.go/vec/vec_*.go. Invoked bymake vendor.vendor_libs/stamp.gois its first and last step:-preflightrefuses dirty or mismatched sibling checkouts before anything is touched,-stampwritesvendor.json(sibling commits, Go toolchain, undup pin, digest of the output) once the cross-builds have passed.internal/vendorstampchecks that file;vendorstamp_test.goruns the check in the suite, so the builders catch a stale or dirty stamp.examples/— runnable samples:example1,connector,vtab_basic,vtab_csv,vtab_match,vtab_regexp.addport.go,issue198/,issue120.diff— porting/regression scaffolding kept around for reference; not built.
Commands
make editor # quick local check: go test -c + go build ./... + vendor_libs build
make test # go test -v -timeout 24h (the full suite is long)
make build_all_targets # cross-build every supported GOOS/GOARCH
make vendor # regenerate lib/ and vec/ from sibling ../libsqlite3 + ../libsqlite_vec
make sbom # regenerate LICENSE-3RD-PARTY.md, SBOM.md, sbom.cdx.json, sbom.spdx.json
make licenses # alias for make sbom
make all # editor + golint + staticcheck
make work # set up go.work pointing at sibling cc/ccgo/libc/libtcl8.6/libsqlite3/libz repos
make clean # removes log-*, *.test, *.out, go.work*
Single test: go test -v -run TestScalar (pattern is a regexp; tests live in all_test.go, module_test.go, func_test.go, pre_update_hook_test.go, vec_test.go, leak_test.go, fcntl_test.go, backup_test.go, null_test.go). VFS tests: go test ./vfs/....
Build/debug tags:
-tags=sqlite.dmesg— enables this package'sdmesg(...)(writes to/tmp/libc.log); seedmesg.go/nodmesg.go.-tags=libc.dmesg— enables debug logs frommodernc.org/libc(must be combined with patchinglibcitself — see the worked example indoc.go).
There is no go generate in this repo — generator.go lives in ../libsqlite3, which owns the transpilation and its SQLite compile-time options. To produce a debug-instrumented transpilation, change the options there, make generate in that repo, then make vendor here.
Fragile modernc.org/libc coupling
Downstream go.mod files must pin the exact modernc.org/libc version that this repo's go.mod pins — the transpiled code in lib/ is closely tied to that specific libc. This is documented in doc.go and tracked in issue #177. Bumping libc here without re-transpiling (or vice-versa) breaks consumers; that's why v1.33.0, v1.34.3, and v1.42.0 are retracted in go.mod.
When debugging into libc, use make work (or a manual go work init && go work use . <path-to-libc>) — doc.go has a worked example showing how to enable Xwrite dmesg logging in a local libc checkout.
Security policy
SECURITY.md is the published policy; keep answers consistent with it. In short: reports go
through GitHub private vulnerability reporting (enabled on the mirror), a confidential
GitLab issue, or the project's GitLab Service Desk address (tickets on a public project are
always confidential, verified in the UI 2026-09-20), never a public issue; acknowledgement is aimed at 7 days with no fix deadline
promised; only the latest release is supported -- no maintenance branches; transpilation
faults in lib/, vec/ and vfs/ are explicitly in scope and are this project's own bug
class, so is the pluggable page cache when registered -- the binding in pagecache.go/pagecache_trampolines.go plus pcache/ (a fault there is silent corruption; unregistered, none of it runs), an attacker-controlled DSN is explicitly out of scope (it is untrusted SQL plus file system access) while a bypass of StrictPragmas or of the rejected-DSN-applies-nothing rule is in, while flaws in SQLite's C are reported here and upstream. On a confirmed report the
fix ships in a new release, a GitHub advisory is published, the advisory is filed with the
Go vulnerability database (https://go.dev/s/vulndb-report-new) so govulncheck sees it,
and the CHANGELOG says what was wrong and who found it.
IRP.md is the incident response plan and the procedural companion to SECURITY.md: triage,
scoping by layer (hand-written Go / generated Go / SQLite's own C), the fix paths -- including
the fixed four-release order when a fix moves libc (libc -> libsqlite3 -> libsqlite_vec -> here)
-- and the four rules that do not bend (never merge on the mirror, never bump libc alone, tag only on green
builders, make sbom after dependency changes), disclosure, and Phase 5 -- a published Go
module version cannot be recalled, so retract plus a new release is the only remedy.
Repository / release workflow
- The canonical repo is GitLab
cznic/sqlite. The GitHubmodernc-org/sqlitemirror does accept issues and PRs, but PRs land via a manual cross-merge into GitLab — there can be a delay. The PRs listed inCHANGELOG.md(e.g. "merge request #113") are GitLab MR numbers, not GitHub PRs. - Per
HACKING.md: this repo is not auto-tagged —builder.jsonhas"autotag": "<none>"because too many projects depend onmodernc.org/sqliteto risk bot tagging. Releases are tagged manually by the maintainer; don't tag unless asked, and only once the builder dashboard is green for all platforms listed inbuilder.json. go.modretractdirectives encode known-broken versions; treat them as load-bearing — don't remove entries when bumping the module.
Driver registration model
init() in sqlite.go calls sql.Register("sqlite", defaultDriver()) with a single package-level *Driver (var d in driver.go). The package-level registration functions (RegisterFunction, RegisterScalarFunction, RegisterDeterministicScalarFunction, RegisterCollationUtf8, RegisterConnectionHook) attach to that driver alone; its vtab modules — vtab.RegisterModule with a nil db — are the one category held process-globally and reach every driver's connections. A caller-constructed Driver carries its own registrations through the mirroring *Driver methods (RegisterFunction, RegisterScalarFunction, RegisterDeterministicScalarFunction, RegisterCollationUtf8, RegisterConnectionHook, RegisterModule, plus Must* variants), and vtab.RegisterModule with a non-nil db lands on the driver backing that db. Same-name module on both: the package-level implementation wins on that driver's connections. All registration applies to connections opened afterwards; registrations made after a connection is open do not affect it — open a new one. See the Driver type doc in driver.go and vtab/doc.go.
DSN query params are parsed in conn.go/driver.go: _pragma, _time_format, _time_integer_format, _inttotime, _texttotime, _timezone, _txlock, plus vfs=<name> to select a VFS registered via vfs.New. A _pragma value runs as SQL text, not as one PRAGMA -- anything after a ; runs too; StrictPragmas(true) (strictpragma.go, process-wide, off by default, deliberately not a DSN key) rejects multi-statement values in the validation phase with a lexical check that compiles nothing, held to SQLite's parser by FuzzSingleStatement.
The pluggable page cache (RegisterPageCache, pagecache*.go, reference impl pcache/) is opt-in and cannot be combined with modernc.org/sqlite/vec (vec's init initializes SQLite first -> SQLITE_MISUSE). The binding panics if a Cache drops or replaces a pinned page. End-to-end coverage: make test_pcache (-tags pcachepool: drops vec_test.go, registers pcache.New() in pcachepool_test.go).