Skip to content

evalengine: float and integer conversion of UTF-16/UCS-2/UTF-32 text reads raw bytes instead of decoded characters #20751

Description

@arthurschreiber

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions