Loading
Commits on Source 78
-
cznic authored
-
cznic authored
Documents the transpilation-based architecture (lib/, vec/, vfs/), the fragile modernc.org/libc version coupling, Makefile targets, debug build tags, the GitLab-canonical / GitHub-mirror workflow, and the singleton Driver registration model. Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
cznic authored
-
Closes #219. Mirrors the existing FileControlPersistWAL pattern: takes the schema name, allocates a 4-byte slot via the TLS allocator, invokes Xsqlite3_file_control with the SQLITE_FCNTL_DATA_VERSION opcode, and returns the resulting uint32. The returned data version changes whenever the contents of the database file change, so the typical caller polls it to invalidate application-level caches. Adds a regression test that opens a fresh file-backed database, queries the version, performs a commit on the same connection, performs another commit from a separate connection, and asserts the version moves both times.
-
cznic authored
-
Following the invitation in !115 review thread.
-
The []driver.Value slice passed to user-defined-function scalar and aggregate callbacks, and to vtab Filter / Insert / Update, was allocated fresh on every invocation. For queries that fan out a UDF over many rows this is the dominant per-row allocation, identified as a hot spot in #226 with profile data showing ~13.5% of allocations attributed to functionArgs. The driver's contract on FunctionImpl.Scalar and AggregateFunction.Step / WindowInverse already states the argument values are not valid past the return of the user function, so the slice itself can be reused safely. Add a sync.Pool of *[]driver.Value and route the five existing call sites through acquireUDFArgs / releaseUDFArgs: - funcTrampoline (scalar UDFs) - stepTrampoline (aggregate Step) - inverseTrampoline (aggregate WindowInverse) - vtabFilterTrampoline (vtab Filter) - vtabUpdateTrampoline (vtab Insert / Update) releaseUDFArgs zeroes each entry before returning the slice so any heap references held in the previous invocation can be reclaimed. Benchmark on 1000-row query with a 3-arg noop scalar UDF (Apple M3, Go 1.25, -benchtime 3s -count 3): name old time/op new time/op delta UDFArgsAllocation-8 213000 ns/op 205000 ns/op -4% name old alloc/op new alloc/op delta UDFArgsAllocation-8 118331 B/op 70376 B/op -40% name old allocs/op new allocs/op delta UDFArgsAllocation-8 6754 allocs/op 5754 allocs/op -15% The 1000 saved allocations per iteration match the expected savings: one slice header per UDF invocation, times 1000 invocations per query. Updates #226.
-
After !114 pools the []driver.Value slice across UDF and vtab callbacks, vtab Cursor.Filter and Updater.Insert/Update share the same "not valid past return" contract as FunctionImpl.Scalar / AggregateFunction.Step / WindowInverse. Document it explicitly so vtab implementations don't silently retain references and corrupt later invocations. Per cznic review on !114.
-
cznic authored
Co-Authored-By:Claude Opus 4.7 <noreply@anthropic.com>
-
The fix for #198 made (*conn).usable() return false whenever sqlite3_is_interrupted reports the connection is interrupted, so database/sql discards the connection after a context-cancelled query. For file-backed databases that is fine, the data is on disk and a new connection re-opens the same database. For in-memory databases the connection IS the database, so dropping it loses the entire store - re-introducing the regression originally fixed by !74 (issue #196). Detect at open time whether the database is in-memory by checking the output of sqlite3_db_filename and cache the result on the conn. usable() short-circuits to true for those connections so the post-interrupt one stays in the pool. File-backed connections keep the existing behaviour - they are still reported unusable after an interrupt. Adds TestInMemoryDBSurvivesContextCancel with two subtests: one that exercises the regression (insert rows, run a pre-cancelled query, re-query and assert the rows are still there), and one that asserts the file-backed path is still discarded after an explicit sqlite3_interrupt. Closes #196.
-
Per cznic's review on !116, the previous in-memory subtest used ExecContext with a pre-cancelled context, which short-circuits in stmt.exec before Xsqlite3_interrupt is ever called, so the table-still- present check passed even without the fix. Rewrite the subtest to obtain a *conn via db.Conn().Raw(), call sqlite3.Xsqlite3_interrupt directly, and assert c.usable() returns true (matches the file-backed subtest's shape). Retain the end-to-end QueryRow on the shared in-memory cache as a regression sanity check. Verified locally that the subtest fails when the c.inMemory short- circuit in (*conn).usable() is removed.
-
cznic authored
Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
Follow-up to !114 (#226). !114 pooled the []driver.Value slice header but explicitly left the per-row TEXT/BLOB body copies in place because a default-on zero-copy path would silently corrupt user code that retains an argument across rows -- undetectable by -race (UDF execution is sequential on one goroutine). This commit adds VolatileArgs bool to FunctionImpl as a strict opt-in. When true: - TEXT arguments are unsafe.String views into the SQLite-owned sqlite3_value_text buffer - BLOB arguments are unsafe.Slice views into the sqlite3_value_blob buffer When false (the default for all existing call sites), behavior is byte-for-byte identical to current master. Plumbing: the flag is captured at registration into small wrapper structs keyed in xFuncs.m / xAggregateFactories.m / xAggregateContext.m, so the hot path is one extra field read rather than a second map lookup. The five trampolines (funcTrampoline, stepTrampoline, inverse...
-
Ian Chechin authored
Per @cznic on !120: 1. The volatile branch of functionArgs returned []byte(nil) for empty BLOB args, while the non-volatile branch returned make([]byte, 0). A user comparing args[i] == nil would see different results depending on the flag, which is orthogonal to the volatility contract. Switch the volatile branch to make([]byte, 0) so the empty-BLOB shape is identical across both modes. 2. Document the within-callback re-entrancy hazard in the VolatileArgs docstring: a nested Query/Exec on the same connection during a volatile-args callback can cause SQLite to reuse the underlying value buffers, so a volatile string/[]byte read before the nested call may alias different bytes after it returns. Rare in practice, but useful to spell out alongside the cross-row retention rule. TestVolatileArgsScalar and TestVolatileArgsAggregate stay green.
-
cznic authored
Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
cznic authored
Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
Follow-up to !120 (#226). !120 added the FunctionImpl.VolatileArgs opt-in for zero-copy TEXT/BLOB access on scalar and aggregate UDF callbacks but left vtab Filter/Update on the non-volatile path, flagged in the MR description as a candidate for a follow-up "if there's demand". This commit extends the same opt-in to: - Cursor.Filter (xFilter) - Updater.Insert / Updater.Update (xUpdate) The contract on those callbacks already says "the vals/cols slice and its entries are not valid past the return of this method; implementations must copy any value they wish to retain", so the API surface is unchanged for users who do not opt in; only the body-allocation strategy differs when opt-in is set. Opt-in is exposed as a new optional interface in package vtab: type VolatileArgsOpter interface { VolatileArgs() bool } A Module that implements it and returns true gets zero-copy TEXT/BLOB views for every Filter, Insert, and Update call routed to tables created from that module. The bool is read once at registration into goModule and shared by every table, so the hot path is one extra field read per trampoline, not a type assertion. Safety contract documented on VolatileArgsOpter cross-references FunctionImpl.VolatileArgs: same retention rule, same deterministic corruption failure mode invisible to -race, same safe-copy idioms, same within-callback re-entrancy hazard. When false (the default for all existing modules), behavior is byte-for-byte identical to current master. Benchmark (darwin/arm64 Apple M3, vtab with one TEXT + one BLOB arg per call): BenchmarkVTabFilterArgs-8 1014 ns/op 616 B/op 20 allocs/op BenchmarkVTabFilterArgsVolatile-8 982 ns/op 608 B/op 18 allocs/op BenchmarkVTabUpdateArgs-8 712 ns/op 256 B/op 13 allocs/op BenchmarkVTabUpdateArgsVolatile-8 686 ns/op 248 B/op 11 allocs/op Two allocations and eight bytes saved per call: one libc.GoString for the TEXT arg, one make([]byte) for the BLOB. Same shape as the UDF case in !120. Empty-BLOB shape parity (make([]byte, 0) for both modes) and the within-callback re-entrancy note carried over from !120 patchset 2; no separate follow-up needed. Test suite (go test -count=1 ./...) stays green; vtab Filter and Updater paths covered by new TestVTabVolatileFilter and TestVTabVolatileUpdate.
-
cznic authored
-
cznic authored
Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
cznic authored
-
Two thin wrappers around the existing sqlite3_backup_remaining and sqlite3_backup_pagecount C symbols. They expose the underlying backup progress counters that the database/sql layer already keeps but that Go callers cannot currently read without dropping to lib/* directly. The motivation is the standard progress-UI use case for online backups: for { more, err := bck.Step(pagesPerTick) if err != nil { return err } ui.Update(bck.PageCount()-bck.Remaining(), bck.PageCount()) if !more { break } } Without these wrappers a caller has to either skip the progress display or fall back to unsafe per-call SQL queries against pragma_page_count. API shape mirrors !115 (FileControlDataVersion): named after the SQLite C function with the s/sqlite3_// prefix stripped and CamelCase applied, documented inline with a link to the official C API page, and added on the existing public... -
cznic authored
Co-Authored-By:Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
(*conn).columnText currently allocates twice per TEXT column per row: once for the make([]byte, len) buffer that receives the SQLite-owned UTF-8 bytes, and once again inside the string(b) conversion that runtime.slicebytetostring performs because the compiler must assume the caller could mutate b. Here b is local to columnText and is never touched again after the copy from the C buffer, so the second copy is redundant. Replacing string(b) with unsafe.String(unsafe.SliceData(b), len) builds the returned string directly on top of b. The string is immutable from Go's perspective, the GC keeps b alive for as long as the string is reachable, and no aliasing is possible because b becomes unreachable as []byte the moment the function returns. The same pattern is already used in sqlite.go (!120) for the volatile-args path. Benchmark on darwin/arm64 (Apple M3), 1000-row SELECT of a single TEXT column, -benchtime=2s, before -> after: Short (16-byte TEXT): 4009 -> 4009 allocs/op (Go runtime already short-circuits string(b) for slices below the inline threshold; no regression either) 52348 -> 52348 B/op 157342 -> 155746 ns/op Medium (256-byte TEXT): 5009 -> 4009 allocs/op (-1000 allocs/op = -1 per row) 548351 -> 292350 B/op (-256 KB/op = the second 256-byte copy) 226863 -> 204730 ns/op (-10%) Long (4096-byte TEXT): 5009 -> 4009 allocs/op (-1000 allocs/op = -1 per row) 8228510 -> 4132413 B/op (-4 MB/op = the second 4 KB copy) 1605640 -> 1135113 ns/op (-29%) The saving scales linearly with TEXT column length, since the eliminated work is exactly one memcpy of the column bytes. No change to (*conn). columnBlob, which already returns its make([]byte, len) buffer directly and pays only one alloc + memcpy per row. TestColumnTextScan exercises the path under -race over the three branches of columnText: empty (short-circuit), short (Go-fast-path) and long (allocating) TEXT, including a multi-byte / emoji payload to confirm UTF-8 is preserved bit-for-bit. Full go test -count=1 ./... stays green.
-
cznic authored
Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
The Next() hot path calls (*rows).ColumnTypeDatabaseTypeName(i) for every TEXT column on every row when _texttotime=1, and for every INTEGER column on every row when intToTime is set. Each call ran: return strings.ToUpper(r.c.columnDeclType(r.pstmt, index)) which is one libc.GoString to materialise the C decltype string into Go memory, plus a (cheap, allocation-free for already-uppercase inputs) strings.ToUpper. The declared type of a result column is fixed for the lifetime of a prepared statement, so the libc.GoString cost is paid N_text_cols * N_rows times for nothing. Move the lookup to newRows() and cache the uppercased decltype into a new rows.decltypes []string. ColumnTypeDatabaseTypeName, the Next() DATETIME branch (which goes through it), and ColumnTypeScanType now read from the cache instead of redoing the C round-trip per row. The case-sensitive switch in ColumnTypeScanType is rewritten against the cached uppercase values to drop a per-column strings.ToLower as well. Benchmark (darwin/arm64 Apple M3, _texttotime=1, 1000-row SELECT of all DATETIME columns, -benchtime=2s, before -> after): 1 column: 11010 -> 10012 allocs/op (-1000 = -1 per row, the libc.GoString) 400354 -> 392393 B/op (-8 KB = -8 bytes per row, "DATETIME" string body) 646068 -> 601121 ns/op (-7%) 5 columns: 55014 -> 50020 allocs/op (-5000 = -5 per row, -1 per col per row) 2000499 -> 1960654 B/op (-40 KB, scales linearly with columns) 2992839 -> 2908393 ns/op (-3%) The saving scales 1:1 with N_text_cols * N_rows for queries that hit the time-conversion path. Workloads using _texttotime, _time_format, or _intToTime DSN flags benefit; queries without those flags do not touch ColumnTypeDatabaseTypeName per row and see no behavior change. TestColumnTypeDatabaseTypeNameCache covers a mixed-case CREATE TABLE across all SQLite storage classes (INTEGER / TEXT / BLOB / DATETIME / DATE / BOOLEAN), reads the cache once at result-set start and again inside the Next loop for every row, and asserts the values never drift. The full go test -count=1 ./... suite stays green. -
Ian Chechin authored
Per @cznic on !124: the decltype cache rewrites the lowercase decltype switch in ColumnTypeScanType to a cached-uppercase switch, but the existing TestColumnTypeDatabaseTypeNameCache only exercises the DatabaseTypeName side. Add a table-driven TestColumnTypeScanTypeDecltypeCache that covers every arm of the cached switch: - INTEGER + BOOLEAN (any case) -> bool - INTEGER + DATE/DATETIME/TIME/TIMESTAMP -> time.Time - INTEGER + plain / unrecognised decltype -> int64 - TEXT (default) -> string - TEXT + DATETIME-shaped decltype (no flag) -> string - TEXT + DATE/DATETIME/TIME/TIMESTAMP under _texttotime=1 -> time.Time - TEXT + unrecognised decltype under _texttotime=1 -> string Each case uses a mixed-case declared type to keep the case-folding path covered, and inserts one row before SELECT so sqlite3_column_type sees the actual storage class instead of SQLITE_NULL (which would short- circuit ColumnTypeScanType to reflect.TypeOf(nil)). All 15 sub-cases pass under -race.
-
cznic authored
Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
(*conn).parseTime ran on every TEXT-stored DATETIME / DATE / TIMESTAMP column read in Next(). The function tried (*conn).parseTimeString first and then walked parseTimeFormats[0..6] sequentially until time.Parse matched the row's value. For the canonical SQLite TEXT datetime format ("2006-01-02 15:04:05.999999999", index 2) every row paid two failed time.Parse attempts in the warmup, plus the one successful match. Each failed Parse allocates a ParseError, so the per-row cost on a steady 1000-row scan was ~5 allocs per row from the format-search alone. Add a sticky per-column hint cache: - rows.parseFmtIdx []int8, sized once at newRows() to the column count, initialised to -1 (no match recorded). - (*conn).parseTime now takes hintIdx int and returns the index that actually matched (or -1 when parseTimeString matched / all formats failed). It tries hintIdx first if in range, then walks the list skipping the index it just tried. - rows.Next() records the first successful index per column and reuses it on subsequent rows. The cache is sticky: it is set once and not overwritten, so a mixed-format column still pays the original fallthrough cost on non-matching rows but a steady column wins on every row after the first. Benchmark (darwin/arm64 Apple M3, 1000-row SELECT of a DATETIME TEXT column in the canonical SQLite format, -benchtime=2s, before -> after): 10013 -> 5019 allocs/op (-50%, ~5 fewer per row) 392417 -> 168672 B/op (-57%, mostly ParseError structs) 633531 -> 397843 ns/op (-37%) TestParseTimeFormatCache covers correctness across the cache transitions: three steady-format rows followed by one ISO-T format row (different index) and one date-only row (yet another index), all returning the expected time.Time. The full go test -count=1 ./... suite stays green. No API change. The fall-through chain is preserved bit-for-bit so any row the old code would have parsed still parses to the same value. -
cznic authored
Tighten the parseFmtIdx doc comment: a mixed-format column pays at most one extra format probe (on rows whose matching format precedes the cached index), not just the original fall-through cost. Add the !125 CHANGELOG entry. No code/behavior change. Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
-
Ian Chechin authored
Adds the public PageCacheModule API, the SQLITE_CONFIG_PCACHE2 wiring, the lifecycle gate between RegisterPageCacheModule and the first Open, and tests for the API surface. The production pool-backed reference implementation and the memory-utilization benchmark that demonstrate the reduction in #204 are deferred to a follow-up MR so the API shape can be reviewed independently of the implementation. New files: - pagecache.go: public PageCacheModule struct, Page / PageEq interfaces, RegisterPageCacheModule / MustRegisterPageCacheModule entry points, ErrPageCacheTooLate / ErrPageCacheConflict sentinels. - pagecache_alias_new.go, pagecache_alias_old.go: per-arch type alias shims so pagecache.go can refer to a single pcacheMethods2 type. Most arches emit the SQLite struct as Tsqlite3_pcache_methods2; the three old-generator arches (freebsd_386, freebsd_arm, netbsd_amd64) emit it as Sqlite3_pcache_methods2. The two build-tagged shims keep the body of pagecache.go arch-agnostic. - pagecache_test.go: TestPCacheMethods2Layout pins the struct shape against regeneration drift, TestRegisterPageCacheModuleValidation covers nil / missing-required-field rejection, TestRegisterPageCacheModuleLifecycle exercises the gate (conflict, too-late, same-pointer idempotency), and TestOpenGateConcurrentReaders runs the openGate RWMutex under load. Modified files: - conn.go: newConn now wraps c.openV2 in withOpenGate so an in-flight Open holds pcacheState.openGate.RLock for the duration of sqlite3_open_v2. A concurrent RegisterPageCacheModule call takes the write lock, drains all readers, and then either installs the methods table or returns ErrPageCacheTooLate. Design notes: - SQLITE_CONFIG_PCACHE2 is global and one-shot. The first successful install commits pcacheState.registered = m and pcacheState.cMethods; any subsequent call with a different pointer returns ErrPageCacheConflict, with the same pointer returns nil. Reload is not supported in this MR. - The configOnce.Do body uses a defer / recover guard so a panic during Xsqlite3_config or populateCMethods leaves configErr set to a panic message and rolls back the half-set state. - populateCMethods uses named-field writes (FiVersion, FpArg, FxInit, ...) rather than hardcoded byte offsets, so the wiring is portable across all supported GOOS / GOARCH pairs. - Page / PageEq interfaces are exported as a stable surface for follow-up impl authors; the binding itself does not use them in this MR. Tested locally on darwin/arm64 with go test -race -short. Cross-build clean for linux/amd64, linux/386, linux/arm64, darwin/arm64, darwin/amd64, windows/amd64, openbsd/amd64; existing pre-MR upstream build failures on freebsd/386, freebsd/arm, netbsd/amd64 are unrelated to this change. Updates #204 Signed-off-by:
Ian Chechin <ian00chechin@gmail.com>
-
Ian Chechin authored
sqlite: pcache2 rework per !126 review (idiomatic Go API, internal trampolines, binding-owned stubs) Replaces the raw-ccgo-ABI surface from the prior commit with an idiomatic Go interface set, per the maintainer's review at gitlab.com/cznic/sqlite/-/merge_requests/126#note. The wiring, lifecycle gate, and tests are kept and extended; the user-facing surface is rebuilt. Public API changes: - PageCache (factory) replaces PageCacheModule. Single method: Create(pageSize, extraSize int, purgeable bool) (Cache, error). - Cache (per-database instance) replaces the prior PageCacheModule struct of function fields. All methods are required, which eliminates the SIGSEGV class the maintainer reproduced: a module with nil Rekey or Shrink. Methods: SetSize, PageCount, Fetch, Unpin, Rekey, Truncate, Destroy, Shrink. - Page (unchanged) is the raw-memory boundary the binding cannot hide: SQLite reads pBuf/pExtra directly and the addresses must stay put. - FetchMode enum (FetchLookup / FetchCreateEasy / Fet...
-
Ian Chechin authored
sqlite: pcache2 round-2 fixes per !126 review (always-call-Fetch, doc tightening, CHANGELOG + doc.go) Addresses cznic's second-round review at gitlab.com/cznic/sqlite/-/merge_requests/126#note_3434353434. Three contract changes plus housekeeping. 1. Always-call-Fetch design (pagecache_trampolines.go) pcacheTrampolineFetch now invokes Cache.Fetch on every SQLite request and reuses the cached sqlite3_pcache_page stub only when the returned Page value equals the previously-stored Page for that key. When the implementation evicted the entry and returned either nil or a different Page, the binding retires the stale stub via libc.Xfree and either returns NullStub (impl reports miss) or mints a fresh stub (impl returned a new Page). This unblocks the canonical use case for plugging in a custom page cache: a bounded purgeable cache that evicts on Unpin(discard=false) to honour cache_size. The previous design's stub-caching shortcut silently leaked a stale stub to SQLite the nex...
-
Ian Chechin authored
Implements the production page cache deferred to MR-B from !126. pool.go: PageCache factory minting per-database caches backed by libc.Xmalloc / libc.Xcalloc pages with a strict cache_size cap and LRU-tail eviction. Page identity is *page (comparable). Pool aggregates hit/miss/alloc/eviction counters across every cache it creates. pool_test.go: 16 unit tests covering empty state, retain/replace across Fetch cycles, FetchCreateEasy refusing at cap, FetchCreateForce evicting LRU tail, SetSize shrink + overcommit-on-pinned, Rekey with colliding eviction, Truncate pinned eviction, Shrink, Destroy no-panic. e2e_test.go: real-DB harness mirroring the !126 validation workload: cache_size=16, 4000 BLOB rows + DELETE + incremental_vacuum, integrity_check=ok under -race. Multi-DB test asserts one Cache per opened database. bench_test.go: BenchmarkPoolBoundedCache reports per-insert allocs + evictions + go-heap-inuse delta for #204 memory-utilization measurement. BenchmarkPoolEvictionChurn drives steady-state 1:1 alloc/eviction churn at cache_size=16.
-
An opt-in `_dqs` DSN query parameter disables SQLite's double-quoted string literal compatibility quirk on a per-connection basis. When set to a false value (`_dqs=0` or any `strconv.ParseBool` false), the driver calls `sqlite3_db_config` with both `SQLITE_DBCONFIG_DQS_DDL` and `SQLITE_DBCONFIG_DQS_DML` set to off in `newConn`, after `sqlite3_open_v2` and before any statement is prepared. Default (absent or `_dqs=1`) leaves SQLite's built-in behavior unchanged so existing DSNs continue to work byte-for-byte. The config call goes through a new `(*conn).dbConfigBool` helper that hand-lays the mixed `(int onoff, int *pRes)` vararg form for the cgo-free transpilation: two pointer-sized slots, the second a NULL pointer because callers in this driver only need the side effect. Tests cover the FFI shape directly (`TestDQSConfigCallVaList`), end-to-end behavior through `database/sql` for the default, explicit-on, and off cases (`TestDQSOptIn`), and the unparseable-val...
-
cznic authored
Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
# Conflicts: # CHANGELOG.md
-
cznic authored
dbConfigBool sized its va_list buffer as 2*unsafe.Sizeof(uintptr(0)), i.e. 8 bytes on 32-bit targets. libc.VaList packs every argument into a fixed 8-byte slot regardless of pointer width (an int is widened to 8 bytes), so the (int onoff, int *pRes) pair writes 12 bytes on 32-bit (int64 at [0,8), pointer at [8,12)) and overruns the 8-byte allocation by 4 bytes on linux/386 and linux/arm. The overflow is currently masked by modernc.org/memory allocator over-allocation and is caught by neither go vet nor cross-builds; -race is unavailable on 386. Size the buffer for two 8-byte VaList slots. Verified: a fill-and-probe shows VaList writes [0,12) within a 16-byte buffer on 386; TestDQS* pass on amd64 and 386; full -short suite green. Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
The _dqs changelog entry added by the original !128 commit was lost when master was merged into the dqs-opt-in branch and the CHANGELOG.md conflict was resolved in master's favor. Re-add it under the v1.53.0 section. Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
An opt-in `_error_rc` DSN query parameter switches the connection into a stricter error-string reporting mode: when set to a true value (`_error_rc=1` or any `strconv.ParseBool` true), the synthesised `*Error.Error()` only appends `sqlite3_errmsg(db)` when `sqlite3_extended_errcode(db)` is consistent with the operation rc. The match check tries the full extended code first and falls back to the primary code (`rc & 0xff`); on mismatch the canonical `sqlite3_errstr(rc)` is used alone. This stops the legacy `errstr: errmsg` form from carrying a stale errmsg from the temporary db handle on open-time failures: a SQLITE_CANTOPEN no longer reads as "unable to open database file: out of memory" when the temporary handle was last touched by an unrelated initialisation path. Absence of the parameter or `_error_rc=0` preserves the legacy form byte-for-byte so callers parsing error strings remain unaffected. `*Error.Code()` returns the same SQLite result code in both modes; the cznic review refinement was to change the message only, never the code. The parameter is parsed before `sqlite3_open_v2` because the failure path that motivated the issue runs during open itself, on a temporary db handle. A new `getErrorRcMode` parser sits next to `getVFSName` in sqlite.go and is consulted before the `conn` struct is created. The flag is then stored on `conn.errorRcMode` and threaded through `errstrForDB`'s new `errorRcMode bool` parameter to the three call sites (`(*conn).openV2`, `(*conn).errstr`, and the backup-init error path which uses the destination connection's mode). Tests cover the open-time SQLITE_CANTOPEN reproducer in all three modes (default, explicit-off, on) with structural assertions that hold across platforms; the non-regression case where syntax errors must preserve the helpful "no such table" detail in every mode; and the invalid-value error path. Documented next to `_pragma`, `_txlock`, and `_dqs` in driver.go. CHANGELOG entry under TBC vNEXT. Resolves #230.
-
Three non-blocking follow-ups raised by cznic on the !127 merge, collected into a single polish pass: (1) Stats.Evictions documentation tightened to match actual behavior. The field counts LRU eviction, Unpin(discard=true), and Shrink releases; bulk frees performed by Truncate, Rekey collisions, and Destroy are not counted. The old "LRU-driven page releases" docstring read narrower than the implementation. (2) New Stats.EasyRefusals counter. FetchCreateEasy refuses at cap even when there are recyclable unpinned pages, while pcache1 would recycle one without spilling; the counter records each refusal so the I/O pressure of the strict Easy contract is observable. SQLite reacts to a refusal by spilling dirty pages and retrying with FetchCreateForce, so EasyRefusals/op is a direct proxy for that spill rate. The two existing benchmarks now report easy-refusals/op alongside the page-allocs and page-evictions metrics, and TestEasyHonoursCacheSize asserts the counter increments on each Easy refusal. (3) TestPoolRoundTripIntegrity comment fix. The previous wording claimed the DELETE + incremental_vacuum workload exercised xRekey ~15 times, which the actual run does not confirm (Rekeys reports 0 on every platform). The corrected comment notes that the SQL surface does not reliably emit xRekey here and that the code path is covered by the unit tests (TestRekey, TestRekeyEvictsCollider) instead. Open question for !127 follow-up I/O comparison: a direct side-by-side vs the in-engine pcache1 (e.g. SQLITE_DBSTATUS_CACHE_SPILL) would require either exposing sqlite3_db_status through the parent driver or running two separate test binaries with and without sqlite.MustRegisterPageCache. Asking which approach you prefer before extending the benchmark in that direction; for now EasyRefusals/op is the in-package proxy. Builds clean across the 8 supported GOOS/GOARCH pairs, gofmt clean, go vet only the two pre-existing unsafe.Pointer notices on Buf/Extra. go test -race -short ./pcache/ ok (16 unit + 2 e2e + new TestEasyHonoursCacheSize assertions).
-
Adds an empty sharing.go that captures the three open design questions for MR-C. The file compiles but introduces no behavior; it exists so cznic can react to the directional choices in a focused diff before any concurrency primitive, wrapper type, or PageCache contract change is committed. Q1. Concurrency primitive — sync.Mutex (current lean), sync.RWMutex, or finer-grained per-bucket. Q2. Locking surface — lock on the existing cache struct, a separate sharedCache wrapper, or an opt-in ConcurrentSafe hook on the sqlite.Cache contract. Q3. Discovery — extend PageCache.Create, add PageCache.CreateShared, or detect at the binding level from the parent conn URI. A canonical TestSharedCacheTwoConns_Integrity (two connections with cache=shared, concurrent writes, PRAGMA integrity_check) is reserved in the file but not added until the locking shape is settled. Builds clean, gofmt clean, go vet introduces no new warnings beyond the pre-existing pool.go unsafe.Pointer notices on Buf/Extra. Existing pcache tests unchanged and green. References !127 review note "the assumption MR-C will need to revisit" and the original !126 description's deferred-to-MR-C scope. -
Ian Chechin authored
(1) Rework BenchmarkPoolEvictionChurn to use a rotating-residue DELETE (k % 3 = i % 3) with a matching-batch re-insert per cycle. Cycle i removes batchPerCycle rows from residue (i % 3) and re-inserts batchPerCycle rows back into the same residue in a fresh disjoint key range, so the next visit (three cycles later) finds rows to scan. Per-cycle work is constant from cycle 0 onward: 200 deletes + 200 inserts + 1 incremental_vacuum, with the seed pre-populated as three 200-row partitions so cycle 0 is already in steady state. easy-refusals/op and page-evictions/op are now rates that hold flat across benchtime values rather than a fixed first-cycle cost divided by b.N (was: 5.36, 2.68, 1.34, 0.67 at 25x/50x/100x/200x; now: ~60.32 throughout). (2) Tighten Stats.Evictions docstring to mention Unpin(discard =false) trimming back to target after a FetchCreateForce overcommit, per cznic's optional nit on round 1. (3) CHANGELOG: replace "merge request #N" placeholder with #130.
-
Ian Chechin authored
Round-1 review follow-ups by cznic on !129 (#230): (1) Fix inverted boolean in the getErrorRcMode docstring. The "Absent parameter or true value preserves..." sentence said the opposite of what the code does and contradicted both the conn.errorRcMode field comment and the driver.go paragraph. Swap the true/false words so the doc agrees with the code. (2) Add TestErrstrForDBSuppressOnMismatch, a deterministic unit test that calls errstrForDB directly with a healthy db handle (sqlite3_extended_errcode = SQLITE_OK, sqlite3_errmsg = "not an error") and a deliberately mismatched rc = SQLITE_CANTOPEN. In legacy mode the formatter appends the stale "not an error" as the helpful detail; under errorRcMode=true the conditional suppress branch fires and the canonical errstr(rc) is used alone. Code() returns SQLITE_CANTOPEN in both modes. This pins the new behavior across SQLite versions and platforms independently of the host-specific ope...
-
Ian Chechin authored
Resolves the question raised by cznic on !127 about what MR-C would need to revisit: the pool is already correct under SQLite's shared-cache mode, because every callback into a given Cache is serialised internally by sqlite3BtreeEnter on the BtShared mutex (verified empirically with a lock-free in-flight probe: max-in-flight = 1 on the canonical two-connection workload, 4 on a positive control with goroutines hitting the cache directly). The Go race detector, however, does not recognise SQLite's libc mutex as a happens-before edge and reports false-positive races on Fetch vs Unpin reads/writes of the per-cache state, which surfaces as DATA RACE failures for any user who registers the pool and runs their suite under -race. Take a sync.Mutex on the cache type on every public method (SetSize, PageCount, Fetch, Unpin, Rekey, Truncate, Destroy, Shrink), always. On the common non-shared-cache path the lock is uncontended (one atomic CAS per Lock/Unlock pair, negligible next to the SQLite work it bookends); on the shared-cache path it just rubber-stamps the order SQLite's BtShared mutex already established. Drop sharedCacheStub from sharing.go (cznic noted it triggers staticcheck U1000 and breaks make all). Rewrite sharing.go as a design record describing why the lock is always taken and the alternatives considered (always-taken vs conditional, document-as-unsupported vs reject-at-Create). The design-questions RFC scaffold is gone. Add TestSharedCacheTwoConns_Integrity in e2e_test.go: two sql.Conn against the same cache=shared URI with concurrent writers + PRAGMA integrity_check, runs cleanly under -race. CHANGELOG entry under TBC vNEXT.
-
Ian Chechin authored
The round-2 rotating-residue rework reliably triggers the b-tree rebalance paths that emit xRekey through the SQL surface (~13 Rekeys per cycle, 325 over 25 cycles, scaling linearly with b.N), so the benchmark now complements the dedicated xRekey unit tests (TestRekey, TestRekeyEvictsCollider) rather than deferring to them, contrary to what the existing comment claimed. Comment-only change per cznic's round-3 review on !130; TestPoolRoundTripIntegrity exercises a different (unchanged) workload and its comment stays accurate as-is.
-
cznic authored
The committed lib/sqlite_netbsd_amd64.go was a stale old-generator transpile that no longer built (the mu.enter/mu.leave undefined break in issue #246) and was out of sync with the new-generator code every other platform uses. Re-transpile SQLite 3.53.2 for netbsd/amd64 (generated on NetBSD 10.1 / Go 1.26.3) and re-vendor: - vendor_libs/main.go: add netbsd/amd64 to the libsqlite3 and libsqlite_vec target lists. Also skip emitting an un-prefixed type alias when it would collide with a const of the same name — netbsd's transpile emits spurious `const <typename> = 0` macro-eval artifacts (off_t, gid_t, ...) that otherwise redeclare the generated alias. No effect on the other platforms' output. - lib/sqlite_netbsd_amd64.go: regenerated (replaces the stale file). - vec/vec_netbsd_amd64.go: vendor sqlite-vec for netbsd; vec/patches.go and vec_test.go: include netbsd so the extension registers and is tested. - Makefile, builder.json: add netbsd/amd64...
-
cznic authored
Picks up the netbsd/amd64 Xmmap PAD-ABI fix. modernc.org/sqlite#246. Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
Document the issue #246 NetBSD/amd64 support status: Tier-2 done, the libc mmap PAD SIGBUS fix (v1.73.1), the cc/ccgo Tier-1 toolchain gate, the cascade landed on master, and the remaining maintainer tag cascade. Co-Authored-By:
Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
Completes the netbsd/amd64 cascade — picks up the race-free netbsd Xabort (and the earlier mmap PAD fix). ABI-preserving for the vendored lib/vec and a no-op for non-netbsd targets (v1.73.1..v1.73.3 touch only libc_netbsd.go). Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
Regenerate the vendored transpiles from the now-released siblings (was generated from in-development trees during the netbsd/amd64 work). vec/* update to the v0.3.0 transpile; lib/sqlite_netbsd_amd64.go picks up the v1.14.0 netbsd diff (other targets unchanged — same SQLite version). build_all_targets passes; TestVec passes. Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
The revived port has green CI across the chain but zero production mileage, so it stays out of the supported-platforms list in doc.go pending broader real-world testing (~a month). Expand the note with the libc mmap-PAD and abort(3) fixes and the call for users to evaluate it and report via #246. Co-Authored-By:
Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
The per-target transpiles in lib/ (SQLite) and vec/ (sqlite-vec) ship one generated Go file per GOOS/GOARCH. Declarations byte-identical across targets are now folded into build-tagged shared files -- lib/sqlite.go + lib/sqlite_g_<hex>.go and vec/vec.go + vec/vec_g_<hex>.go -- by modernc.org/undup, wired into `make vendor`. This is a packaging change only. Go's build constraints make every target compile exactly the same declarations as before; the public API and behavior are unchanged, `make build_all_targets` is green on all ~20 platforms, and the suite (incl. TestVec) passes. Hand-written platform files (libsqlite3_*.go, hooks_*.go, ...) carry no generated-code marker and are untouched. 421 files changed, 1,523,713 insertions(+), 4,437,082 deletions(-) lib 164.8 -> 62.2 MB (2.65x) vec 8.6 -> 2.1 MB (4.19x) Net ~2.9M fewer lines of vendored generated code -- good news for clone, fetch, and build times. The motivation... -
cznic authored
The latest real tag is v1.52.0; v1.53.0 and v1.54.0 existed only as CHANGELOG sections, splitting one pending release across two version numbers. Renumber the premature v1.54.0 header to v1.53.0 and fuse the two sections so the next tag is honestly v1.53.0. No released (tagged) section is touched. Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
Vendor fresh SQLite 3.53.2 + sqlite-vec transpiles for freebsd/386 and freebsd/arm (replacing the stale 3.41 freebsd_386 file) via make vendor, and enable them in vendor_libs, build_all_targets, and builder.json test. Requires modernc.org/libc v1.73.4: with the previous libc, freebsd/arm's WAL shared-memory mmap faulted (SIGBUS) because the 64-bit off_t was mis-encoded for 32-bit; v1.73.4 fixes the per-arch mmap off_t encoding. Runtime-tested on both freebsd/386 and freebsd/arm (core + WAL/concurrency + vec). doc.go's supported-targets list is left for a follow-up after broader testing, mirroring how netbsd/amd64 was introduced. Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
The freebsd/arm sqlite-vec transpile was generated against the older libsqlite3 (SQLite 3.53.1) while the rest of the tree is 3.53.2; re-vendored from libsqlite_vec regenerated at 3.53.2 so all targets are consistent. Functionally unchanged (3.53.1->3.53.2 is a patch, no ABI change; freebsd/386+arm runtime tests already passed). Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
cznic authored
Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
Ian Chechin authored
Adds a Go binding for sqlite3_db_status, the per-connection runtime counters (cache hit/miss/write/spill, schema/statement memory, lookaside usage, deferred FKs), as discussed on the !130 review. dbstatus.go: DBStatus interface implemented by *conn and reached via (*sql.Conn).Raw(), mirroring the FileControl surface cznic pointed at. DBStatusOp is a distinct typed enum of the SQLITE_DBSTATUS_* verbs so a constant from another op family will not compile in its place; all 14 ops the transpiled lib defines are exposed. Status(op, reset) returns the (current, high) pair via the tls.Alloc(8) two-int32 pattern from cznic's skeleton and surfaces an out-of-range op as an error. dbstatus_test.go: exercises the three counter families through Raw() - SchemaUsed (memory high-water) grows after DDL; CacheHit (running counter) resets; LookasideHit reports its value in high not current; an out-of-range op errors. pcache/bench_test.go: Ben...
-
cznic authored
- CHANGELOG.md: re-add the "See merge request #131" line that the MR diff dropped, so the pcache -race-clean entry keeps its attribution instead of reading as part of !132. - dbstatus.go: make the op-family doc match the transpiled sqlite3_db_status64 behavior — only DBStatusLookasideUsed maintains a high-water mark; CacheUsed/SchemaUsed/StmtUsed/CacheUsedShared report high==0 with the reset flag ignored; DBStatusDeferredFKs is a 0/1 flag (reset ignored); DBStatusTempbufSpill is a running byte counter (was undocumented). No code/behavior change. Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
Pre-v1.53.0 release prep; documentation and module hygiene only, no change to any compiled source: - CHANGELOG.md: add an experimental-status entry for freebsd/386 and freebsd/arm (MR #119, Olivier Cochard-Labbé / @ocochard), mirroring the netbsd/amd64 framing -- shipped and CI-tested but intentionally not yet in doc.go's supported-platforms list, pending broader real-world testing. Also bump the v1.53.0 entry date to the tag date. - CLAUDE.md: correct the stale "transpiled SQLite 3.53.1" to 3.53.2. - go.sum: go mod tidy, pruning orphan toolchain hashes; go.mod and all selected module versions are unchanged. - Remove issue246-tracker.md, an internal status tracker not meant to ship in the released module. Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>