Add support for the cgo-free modernc.org/sqlite driver - #37
Open
peczenyj wants to merge 8 commits into
Open
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Driver-specific error types whose fields are unexported (such as
modernc.org/sqlite's Error type) cannot be reconstructed during playback.
Preserve their message and numeric code via a new errorWithCodeType
valueType, so tests can assert error codes identically in recording and
playback modes using errors.As with an interface{ Code() int } target.
The package lives in its own Go module (like pqtestold) so that the large modernc.org/sqlite dependency stays out of the root module. No docker is required: recording mode runs against a local temporary database file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add TestError which inserts a duplicate primary key and checks that the resulting constraint-violation error code (SQLITE_CONSTRAINT_PRIMARYKEY=1555) is preserved through copyist's errorWithCodeType (valueType 12) in both recording and playback modes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #40.
This PR adds support for the cgo-free SQLite driver
modernc.org/sqlite, addressing the README's invitation to extend copyist beyond the Postgrespq/pgxdrivers. It consists of one small, dependency-free core change plus a fully self-contained test module — the heavy SQLite dependency never touches the root module.The core change: generic error-code round-tripping (
values.go)SQLite values themselves (
int64,float64,string,[]byte,time.Time) already round-trip through the existingvalueTypes, so the proxy layer needed no changes at all. The only gap was error fidelity:modernc.org/sqlite'sErrortype has unexported fields and no exported constructor, so copyist cannot reconstruct it during playback the way it reconstructs*pq.Error/*pgconn.PgErrorvia the wire protocol.Instead of importing the driver (see "Why a separate module" below), this PR adds one generic, dependency-free mechanism:
A new
errorWithCodeType(valueType = 12): at record time, any error implementinginterface{ Code() int }is captured as12:<code> <quoted message>— no import of the driver needed.At playback time, copyist returns an unexported
errorWithCodetype preserving the original message and numeric code.Test code asserts codes through an interface, which works identically in recording and playback modes:
The new type-switch case is placed after the concrete
*pq.Error/*pgconn.PgErrorcases (which expose codes as fields, not methods, so they cannot match it) and before the genericcase error:. Any future driver whose errors exposeCode() intgets this behavior for free. What is not preserved is the concrete driver error type —errors.As(err, &sqliteErr)will not match during playback; this limitation is documented in the README.A real recorded line from the test suite, produced by a PRIMARY KEY violation:
Why a separate test module (
drivertest/sqlitetest)modernc.org/sqliteis SQLite transpiled to Go — a very large dependency that requires a much newer Go than the root module'sgo 1.16. Following the existingdrivertest/pqtestoldprecedent, the test package has its owngo.mod(go 1.23,replace github.com/cockroachdb/copyist => ./../..), so:Unlike the Postgres packages, no Docker is needed: recording mode runs against a local file database (
file:copyist_test.db, gitignored via*.db). A file rather than:memory:is deliberate — copyist re-opens connections per session for determinism, and each new connection to:memory:would see a fresh empty database.The suite mirrors the commontest coverage:
TestQuery,TestInsert,TestMultiStatement,TestTxns,TestDataTypes(all four SQLite storage classes +time.Time),TestSqlx, andTestError(the end-to-end proof that error codes survive playback). The committedtestdata/sqlitetest_test.copyistrecording allows playback-only runs with no setup.Supporting changes
copyist.go— comment-only: documents that sqlx doesn't know the"sqlite"driver name (only"sqlite3"), soBindTypereturns UNKNOWN, which sqlx treats like the default?— exactly SQLite's native placeholder style. Verified against sqlx'sbind.go;TestSqlxexercises the path.Makefile—make testnow records + plays backdrivertest/sqlitetestalongsidepqtestold(no Docker required for this step).go.yml) — newtest-sqlitejob on Go 1.23 running playback-only; the existing Go 1.18 job is untouched, so the root module's compatibility floor remains verified.README.md— supported-drivers update, a short SQLite usage paragraph, and the error-fidelity limitation with theerrors.Aspattern..gitignore—*.dbscratch files (and a localdocs/working directory).Test Plan
go test ./...at root: all packages pass on Go 1.18-compatible code (core change is backward-compatible with all existing committed Postgres recordings)TestRoundtrip+TestErrorWithCode(foreign coder type → playback type)drivertest/sqlitetest: 7 tests pass in recording mode (real driver, local file DB) and playback mode (recordings only, no DB file present)12:valueType for the constraint-violation errorgofmt/go vetclean on both modulesNotes for reviewers
TestErrorisSQLITE_CONSTRAINT_PRIMARYKEY(1555); SQLite reports the message as "UNIQUE constraint failed" becauseINTEGER PRIMARY KEYis enforced via a unique index — the code, not the message, identifies the constraint kind.*mysql.MySQLErrorhas exported fields and fits the existing in-core reconstruction pattern), and stable ordering of recordings inWriteRecording(file ordering currently varies between from-scratch re-records due to map iteration; playback is unaffected).🤖 Generated with Claude Code