Overview of the Issue
MySQL reads numeric text through charset-aware conversion for every numeric target type: character sets whose smallest character is wider than one byte (UTF-16, UTF-16LE, UCS-2, UTF-32) are decoded before the number is read (my_strntod / my_strntoull10rnd have mb2/mb4 variants, and str2my_decimal converts through latin1). The Vitess evalengine only does this for the DECIMAL target (fixed in #20721); evalToFloat/evalToInt64/evalToUint64 in go/vt/vtgate/evalengine/eval_numeric.go still parse the raw encoded bytes, so the interleaved NUL bytes of fixed-width text stop the parse immediately.
Verified against MySQL 8.4.11 (_utf16le X'2B003100' is '+1', X'310035002E003500' is '15.5'):
| Expression |
MySQL 8.4 |
Vitess |
CAST(_utf16le X'2B003100' AS DOUBLE) |
1 |
0 |
CAST(_utf16le X'2B003100' AS SIGNED) |
1 |
0 |
CAST(_utf16 X'002D0032' AS SIGNED) |
-2 |
0 |
CAST(_utf16le X'310035002E003500' AS DOUBLE) |
15.5 |
1 |
_utf16le X'2B003100' + 0 |
1 |
0 |
The last row shows this is not limited to explicit CAST: any arithmetic coercion of a fixed-width string hits the same path. The introducer literals are just the simplest reproduction; the same applies to CONVERT(expr USING utf16) results flowing into numeric context.
The fix mirrors #20721's decimal change: normalize UTF-16/UTF-16LE/UCS-2/UTF-32 bytes to latin1 (unconvertible characters become ?, ending the number) before handing them to fastparse, in the float and integer bridges — both the AST and compiled paths funnel through the same evalTo* helpers. The int64/uint64 rounding semantics of MySQL's strntoull10rnd (e.g. '1.6' → 2 for integer targets) should be checked while in there, since differential coverage for fixed-width text into non-decimal targets is currently absent from the testcases.
Reproduction Steps
SELECT CAST(_utf16le X'2B003100' AS DOUBLE); -- Vitess: 0, MySQL: 1
SELECT CAST(_utf16le X'2B003100' AS SIGNED); -- Vitess: 0, MySQL: 1
SELECT _utf16le X'2B003100' + 0; -- Vitess: 0, MySQL: 1
Binary Version
main (v25.0.0-SNAPSHOT), pre-existing on earlier releases.
Operating System and Environment details
Any.
Log Fragments
n/a
Overview of the Issue
MySQL reads numeric text through charset-aware conversion for every numeric target type: character sets whose smallest character is wider than one byte (UTF-16, UTF-16LE, UCS-2, UTF-32) are decoded before the number is read (
my_strntod/my_strntoull10rndhavemb2/mb4variants, andstr2my_decimalconverts through latin1). The Vitess evalengine only does this for the DECIMAL target (fixed in #20721);evalToFloat/evalToInt64/evalToUint64ingo/vt/vtgate/evalengine/eval_numeric.gostill parse the raw encoded bytes, so the interleaved NUL bytes of fixed-width text stop the parse immediately.Verified against MySQL 8.4.11 (
_utf16le X'2B003100'is'+1',X'310035002E003500'is'15.5'):CAST(_utf16le X'2B003100' AS DOUBLE)10CAST(_utf16le X'2B003100' AS SIGNED)10CAST(_utf16 X'002D0032' AS SIGNED)-20CAST(_utf16le X'310035002E003500' AS DOUBLE)15.51_utf16le X'2B003100' + 010The last row shows this is not limited to explicit CAST: any arithmetic coercion of a fixed-width string hits the same path. The introducer literals are just the simplest reproduction; the same applies to
CONVERT(expr USING utf16)results flowing into numeric context.The fix mirrors #20721's decimal change: normalize UTF-16/UTF-16LE/UCS-2/UTF-32 bytes to latin1 (unconvertible characters become
?, ending the number) before handing them tofastparse, in the float and integer bridges — both the AST and compiled paths funnel through the sameevalTo*helpers. The int64/uint64 rounding semantics of MySQL'sstrntoull10rnd(e.g.'1.6'→ 2 for integer targets) should be checked while in there, since differential coverage for fixed-width text into non-decimal targets is currently absent from the testcases.Reproduction Steps
Binary Version
main (v25.0.0-SNAPSHOT), pre-existing on earlier releases.
Operating System and Environment details
Any.
Log Fragments
n/a