sqlite: add _dqs opt-in DSN parameter (#61)

Motivation

Issue #61 (closed) has been open since 2022 asking for a way to opt out of SQLite's double-quoted string literal compatibility quirk. The default SQLite behavior silently re-interprets a double-quoted identifier that fails to resolve as a string literal, which masks typos in column references and is documented as one of the legacy "quirks" at https://www.sqlite.org/quirks.html#dblquote. The 2025-09 discussion settled the shape as a per-connection opt-in: a _dqs DSN parameter that, when set to a false value, calls `sqlite3_db_config(SQLITE_DBCONFIG_DQS_DDL/_DML, 0)` on the new connection, with the SQLite default left untouched when the parameter is absent.

What this MR delivers

  • `driver.go` (+12 lines): `_dqs` documentation paragraph next to `_pragma` and `_txlock`. Describes the `strconv.ParseBool`-shaped surface and the combined DDL+DML toggle.

  • `conn.go` (+34 lines):

    • The DQS configuration is applied in `newConn` after the inMemory cache and before `applyQueryParams`. `applyQueryParams` runs user-supplied PRAGMA statements, and the `SQLITE_DBCONFIG_DQS_*` flags are required to be set before any statement is prepared on the connection, so the ordering is spelled out in a comment at the call site.
    • A new `(*conn).dbConfigBool` helper hand-lays the `(int onoff, int *pRes)` vararg form for the cgo-free transpilation. Two pointer-sized slots are allocated through `libc.Xmalloc`; the second slot is a NULL pointer because callers in this driver only need the side effect, not the post-set value. Returns the underlying SQLite result code.
  • `sqlite.go` (+41 lines): `applyDQSConfig` parses the `_dqs` query value via `strconv.ParseBool` and, on a false value, calls `c.dbConfigBool` once for `SQLITE_DBCONFIG_DQS_DDL` and once for `SQLITE_DBCONFIG_DQS_DML`. Absence or a true value returns without touching the connection so the default SQLite behavior is preserved byte-for-byte. An invalid value returns a descriptive error mentioning `_dqs`.

  • `dqs_test.go` (+86 lines, new):

    • `TestDQSConfigCallVaList` pins the mixed-vararg FFI shape directly. The hand-laid VaList carries one int and one pointer; mixing widths is the easy thing to get subtly wrong in the cgo-free transpilation, so the test asserts the call returns SQLITE_OK for every boolean-toggle DQS op in both directions.
    • `TestDQSOptIn` exercises the end-to-end behavior through `database/sql`. With DQS enabled (default or `_dqs=1`) a non-resolving double-quoted identifier silently falls back to a string literal; with `_dqs=0` it must fail to parse instead.
    • `TestDQSInvalid` verifies an unparseable `_dqs` value surfaces an error from `newConn` rather than being silently ignored.
  • `CHANGELOG.md` (+2 lines): TBC vNEXT entry summarising the new parameter, the off-by-default semantics, and the resolved issue link. MR reference will be updated to the actual number once the MR is open.

Verification

  • `go build ./...` and `go test -short -count=1 -run "TestDQS"` clean on darwin/arm64 (3 tests, 0.6s).
  • `go test -race -short -count=1 -run "TestDQS"` clean (1.7s).
  • Cross-build clean for linux/amd64, linux/386, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, freebsd/amd64, openbsd/amd64.
  • `gofmt -l` clean.
  • `go vet ./.` produces only the pre-existing warnings on conn.go's other unsafe.Pointer call sites; no new warnings introduced.

What this MR explicitly does NOT do

  • The DDL/DML split is not exposed separately. Per the issue discussion, `_dqs=0` turns both off and `_dqs=1` leaves the SQLite default in place; a finer split can be added later if anyone asks.
  • The default is not flipped. The opt-in shape is the intended end state, not a stepping stone to a future major-version default change.

Resolves #61 (closed).

Merge request reports

Loading