Skip to content

fix(mysql): one server version for the handshake, VERSION() and @@version - #30

Merged
luthermonson merged 1 commit into
mainfrom
fix/unify-server-version
Aug 14, 2026
Merged

fix(mysql): one server version for the handshake, VERSION() and @@version#30
luthermonson merged 1 commit into
mainfrom
fix/unify-server-version

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

A MySQL client can ask litewire what version it is three ways, and each was answered by its own unrelated string literal:

Path Read by Was
wire handshake mysqli_get_server_info(), PDO::ATTR_SERVER_VERSION 8.0.36-litewire
SELECT VERSION() $wpdb->db_version() and friends 8.0.0-litewire
@@version capability checks 8.0.0-litewire

All three now read litewire_translate::SERVER_VERSION.

Unified upward, on the handshake's value

The handshake is what every client reads at connect and may cache for the life of the connection, so changing it moves the answer for every client whether or not it ever calls VERSION(). Fixing the two emulated paths instead changes only what those functions return. Lower blast radius, and 8.0.36 is the more accurate string — 8.0.0 was a development milestone release, never GA.

The constant lives in litewire-translate because that crate owns two of the three paths and litewire-mysql (which owns the third) already depends on it. It is backed by a small server_version!() macro so the @@version fast path, which builds SQL literals at compile time with concat! and needs the value quoted, can reach the same source of truth rather than keeping a second copy.

Existing assertions updated — why this is a fixture correction

Three assertions encoded the old, divergent value. They are the behaviour this issue asks us to change, so updating them is not a weakening:

  • litewire-translate/src/common.rs:466 (version_rewrite)
  • litewire-translate/src/metadata.rs:641 (system_variables_sql)
  • litewire-translate/src/metadata.rs:739 (known_system_variables_produce_expected_values)

Each keeps asserting exactly what it asserted before, and the first two now additionally assert against crate::SERVER_VERSION rather than only a literal — so if a future change moves the constant without moving the emulation, the test fails instead of quietly passing. Coverage went up, not down. Every edit carries an inline comment naming the issue.

SELECT VERSION() had never worked

Writing an end-to-end test for the unification turned up a second defect. The statement did not return the wrong version — it returned an error:

SQLite error: wrong number of arguments to function coalesce()
in SELECT coalesce('8.0.36-litewire') at offset 7

The rewrite pass only ever sees the Function node, never its parent Expr, so a built-in with no SQLite analogue cannot become a bare literal — it has to stay a function call. The call chosen was coalesce(), which SQLite requires to have at least two arguments and rejects at prepare time with one. VERSION, DATABASE, SCHEMA, USER, CURRENT_USER, SESSION_USER, SYSTEM_USER and CONNECTION_ID were all emitted this way, so none of them has ever executed.

The constraint was already known in this exact file: the FOUND_ROWS arm sitting between the broken ones carries the comment "abs(0): single-argument scalar that SQLite accepts (coalesce and max need >= 2 args)". Its neighbours were never given the same treatment, and the unit tests did not catch it because they assert on the emitted SQL string and never run it.

Constant folding now goes through two helpers — constant_text (uses trim(), which returns a whitespace-free constant unchanged) and constant_number (uses abs(), matching the existing FOUND_ROWS precedent) — so the single-argument invariant is stated once and cannot be got wrong per-arm again. No existing test needed changing for this part; all 307 translate tests pass unmodified.

This is in scope rather than a separate PR because unifying the answer of a statement that cannot execute is not a fix, and the test that proves the unification is the same test that exposed it.

Testing

  • cargo test --workspace and cargo test --workspace --all-features pass.
  • cargo clippy --workspace --all-targets --all-features -- -D warnings clean; cargo +nightly fmt --all -- --check clean.
  • New crates/litewire/tests/mysql_server_version.rs, running against the rusqlite backend over the real wire protocol:
    • handshake_version_function_and_system_variable_all_agree — asserts SELECT VERSION() and SELECT @@version both equal SERVER_VERSION, that mysql_async's handshake-parsed triple is (8, 0, 36), and that the triple is a prefix of the constant. That last assertion is what ties the wire handshake to the emulated paths, so a fourth path or a re-hardcoding gets caught.
    • session_identity_builtins_execute — runs DATABASE(), SCHEMA(), USER(), CURRENT_USER(), SESSION_USER(), SYSTEM_USER(), CONNECTION_ID() and a VERSION() inside a larger expression, and checks the values. Every one of these fails on main with the coalesce() arity error.

Fixes #21

…sion

A client can ask litewire its version three ways -- the wire handshake,
SELECT VERSION(), and @@Version -- and each was answered by its own
string literal. The handshake said 8.0.36-litewire; the other two said
8.0.0-litewire. Clients that branch on version got different answers
depending on which one they asked.

All three now read litewire_translate::SERVER_VERSION. Unified upward on
the handshake's value: the handshake is what every client reads at
connect and may cache, so changing it has the wider blast radius, and
8.0.36 is the more accurate string.

Three existing assertions encoded the old divergent value and are
updated to the new one. This is a deliberate behaviour change, not a
test weakening: each one now asserts against the shared constant as
well as the literal, so a future drift fails the test rather than
silently passing.

Fixing this surfaced that SELECT VERSION() never worked at all. The
session-identity built-ins were rewritten to a one-argument coalesce(),
which SQLite rejects at prepare time -- "wrong number of arguments to
function coalesce()". VERSION, DATABASE, SCHEMA, USER, CURRENT_USER,
SESSION_USER, SYSTEM_USER and CONNECTION_ID were all affected. The unit
tests missed it because they only inspected the emitted SQL string.
Constants now go through constant_text/constant_number, which use the
single-argument scalars SQLite does accept, and the new end-to-end tests
execute the statements rather than reading the SQL back.

Fixes #21
@luthermonson
luthermonson merged commit 1cd51ce into main Aug 14, 2026
3 checks passed
@luthermonson
luthermonson deleted the fix/unify-server-version branch August 14, 2026 07:54
luthermonson added a commit that referenced this pull request Sep 1, 2026
…sion (#30)

A client can ask litewire its version three ways -- the wire handshake,
SELECT VERSION(), and @@Version -- and each was answered by its own
string literal. The handshake said 8.0.36-litewire; the other two said
8.0.0-litewire. Clients that branch on version got different answers
depending on which one they asked.

All three now read litewire_translate::SERVER_VERSION. Unified upward on
the handshake's value: the handshake is what every client reads at
connect and may cache, so changing it has the wider blast radius, and
8.0.36 is the more accurate string.

Three existing assertions encoded the old divergent value and are
updated to the new one. This is a deliberate behaviour change, not a
test weakening: each one now asserts against the shared constant as
well as the literal, so a future drift fails the test rather than
silently passing.

Fixing this surfaced that SELECT VERSION() never worked at all. The
session-identity built-ins were rewritten to a one-argument coalesce(),
which SQLite rejects at prepare time -- "wrong number of arguments to
function coalesce()". VERSION, DATABASE, SCHEMA, USER, CURRENT_USER,
SESSION_USER, SYSTEM_USER and CONNECTION_ID were all affected. The unit
tests missed it because they only inspected the emitted SQL string.
Constants now go through constant_text/constant_number, which use the
single-argument scalars SQLite does accept, and the new end-to-end tests
execute the statements rather than reading the SQL back.

Fixes #21
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.

SELECT VERSION() reports 8.0.0-litewire but the handshake advertises 8.0.36-litewire

1 participant