Skip to content

fix(translate): re-escape single-quoted literals for SQLite (mysqldump fidelity) - #62

Merged
luthermonson merged 1 commit into
mainfrom
fix/mysql-string-escape-and-index-hints
Oct 29, 2026
Merged

luthermonson merged 1 commit into
mainfrom
fix/mysql-string-escape-and-index-hints

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

A mysqldump replay over the wire silently dropped rows whose text contained a backslash immediately before a single quote (the SQL source shape 'a\\\'b'). In the wun Sendy load this lost exactly 45 campaigns rows, which had to be backfilled via bound params.

Root cause (emit-side, not the lexer)

sqlparser stores the fully-decoded value in the AST (the source 'a\\\'b' becomes the four bytes a \ ' b) — that part was already correct. But sqlparser's Display for Value::SingleQuotedString re-serializes for a backslash-escaping dialect: when a ' is immediately preceded by a \, it emits the quote bare. SQLite/Turso never interpret backslash escapes, so they read the emitted literal as a string ending early followed by stray tokens — a syntax error standalone, or a silently dropped/garbled row inside a multi-row INSERT.

Fix

A VisitorMut (SqliteStringEscaper) doubles every embedded ' → '' in each SingleQuotedString before Display. Once every quote sits in a '' pair, Display has no lone quote to mishandle and emits the SQLite-correct literal verbatim; backslashes stay literal bytes. Applied in emit_statement (cold path, behind TranslateCache).

Round-trip for the data-loss shape: source 'a\\\'b' now emits a literal SQLite decodes back to exactly a \ ' b.

USING BTREE / HASH

These already parse-and-drop at main (ef896fb) in every position tested (inline KEY/UNIQUE/PRIMARY, standalone CREATE INDEX, ALTER ADD KEY) — no code change needed; added a regression test to lock it in.

Tests

New crates/litewire/tests/mysql_dump_fidelity.rs (over the wire, rusqlite + turso): asserts exact stored bytes and no dropped row for 7 escape shapes including the data-loss shape and a multi-row INSERT; and that a CREATE TABLE with USING BTREE/USING HASH loads and a UNIQUE KEY ... USING HASH enforces uniqueness. clippy --all-targets -D warnings and nightly fmt are clean.

…p fidelity)

sqlparser's Display leaves a single quote bare when a backslash precedes it
(MySQL backslash-escaping), which SQLite and Turso misparse since they never
interpret backslash escapes (NO_BACKSLASH_ESCAPES is effectively always on).
A mysqldump replay silently dropped the rows whose text had a backslash
immediately before a quote -- 45 Sendy campaigns rows in the wun load. Double
every embedded quote in a SingleQuotedString before handing the AST to
Display, so the emitted literal is the SQLite-correct form.

Adds a mysqldump-fidelity regression test over the wire on both backends
covering the escape shapes (incl. 'a\\'b'), and locking in that USING
BTREE/HASH index hints parse-and-drop in CREATE TABLE.
@luthermonson
luthermonson merged commit 84e1393 into main Oct 29, 2026
5 checks passed
@luthermonson
luthermonson deleted the fix/mysql-string-escape-and-index-hints branch October 29, 2026 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant