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).