Environment
- mysql2 version: 0.5.7
- Ruby version: 3.4.8
Summary
When LC_NUMERIC is set to a locale that uses a comma as the decimal separator
(e.g. de_DE, fr_FR), DECIMAL values between -1 and 1 exclusive are incorrectly
cast to 0 instead of their actual value.
Values outside this range (e.g. 1.5, -2.3) are returned correctly.
Steps to reproduce
require 'fiddle'
libc = Fiddle.dlopen(nil)
setlocale = Fiddle::Function.new(
libc['setlocale'],
[Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP],
Fiddle::TYPE_VOIDP
)
setlocale.call(1, "de_DE.UTF-8") # LC_NUMERIC = 1
client = Mysql2::Client.new(host: "localhost", username: "root")
client.query("SELECT CAST(0.5 AS DECIMAL(10,2)) AS val").first["val"] # => 0.0 (wrong)
client.query("SELECT CAST(0.15 AS DECIMAL(10,2)) AS val").first["val"] # => 0.0 (wrong)
client.query("SELECT CAST(1.5 AS DECIMAL(10,2)) AS val").first["val"] # => 1.5 (correct)
Expected behavior
All DECIMAL values should be returned with their correct value regardless of
the process locale. CAST(0.5 AS DECIMAL(10,2)) should return BigDecimal("0.5").
Actual behavior
CAST(0.5 AS DECIMAL(10,2)) returns BigDecimal("0.0").
Only values whose integer part is 0 are affected (i.e. -1 < x < 1).
Root cause
In ext/mysql2/result.c, the MYSQL_TYPE_NEWDECIMAL branch in
rb_mysql_result_fetch_row uses strtod() to check whether the value is zero:
case MYSQL_TYPE_NEWDECIMAL:
if (fields[i].decimals == 0) {
val = rb_cstr2inum(row[i], 10);
} else if (strtod(row[i], NULL) == 0.000000) {
val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero);
} else {
val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i]));
}
break;
strtod() is locale-sensitive. With LC_NUMERIC=de_DE, it does not recognize
. as a decimal separator, so strtod("0.5") returns 0.0. This triggers the
zero-branch, and opt_decimal_zero ("0.0") is passed to BigDecimal() instead
of the actual string "0.5".
Values like "1.5" are unaffected because strtod("1.5") returns 1.0 in
any locale (the integer part is read correctly), so the zero-check is false and
the raw string "1.5" is passed to BigDecimal(), which is locale-independent.
Environment
Summary
When
LC_NUMERICis set to a locale that uses a comma as the decimal separator(e.g.
de_DE,fr_FR), DECIMAL values between -1 and 1 exclusive are incorrectlycast to
0instead of their actual value.Values outside this range (e.g.
1.5,-2.3) are returned correctly.Steps to reproduce
Expected behavior
All DECIMAL values should be returned with their correct value regardless of
the process locale.
CAST(0.5 AS DECIMAL(10,2))should returnBigDecimal("0.5").Actual behavior
CAST(0.5 AS DECIMAL(10,2))returnsBigDecimal("0.0").Only values whose integer part is 0 are affected (i.e. -1 < x < 1).
Root cause
In
ext/mysql2/result.c, theMYSQL_TYPE_NEWDECIMALbranch inrb_mysql_result_fetch_rowusesstrtod()to check whether the value is zero:strtod()is locale-sensitive. WithLC_NUMERIC=de_DE, it does not recognize.as a decimal separator, sostrtod("0.5")returns0.0. This triggers thezero-branch, and
opt_decimal_zero("0.0") is passed toBigDecimal()insteadof the actual string
"0.5".Values like
"1.5"are unaffected becausestrtod("1.5")returns1.0inany locale (the integer part is read correctly), so the zero-check is false and
the raw string
"1.5"is passed toBigDecimal(), which is locale-independent.