From 78164fe1f69516a39fc60da2300daee996d33122 Mon Sep 17 00:00:00 2001 From: Anway Durge Date: Fri, 23 Oct 2026 23:08:55 +0530 Subject: [PATCH] MDEV-41208: stringify non-VARCHAR DuckDB values for MariaDB string fields Mixed COALESCE is VARCHAR in MariaDB and INTEGER in DuckDB, so GetValueUnsafe() asserted PhysicalType::VARCHAR. Convert with ToString() unless DuckDB actually stored a string. --- storage/duckdb/convertor/duckdb_select.cc | 28 +++++++++++------ .../mysql-test/duckdb/r/mdev_41208.result | 31 +++++++++++++++++++ .../mysql-test/duckdb/t/mdev_41208.test | 31 +++++++++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/mdev_41208.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_41208.test diff --git a/storage/duckdb/convertor/duckdb_select.cc b/storage/duckdb/convertor/duckdb_select.cc index b42303f42fe7f..98dbe9b81ac1e 100644 --- a/storage/duckdb/convertor/duckdb_select.cc +++ b/storage/duckdb/convertor/duckdb_select.cc @@ -41,6 +41,16 @@ static void store_field_temporal_value(Field *field, MYSQL_TIME *ltime) field->store_time(ltime); } +/** + Convert a DuckDB value to a string for MariaDB string-like fields. +*/ +static duckdb::string duckdb_value_as_string(const duckdb::Value &value) +{ + if (value.type().InternalType() == duckdb::PhysicalType::VARCHAR) + return value.GetValueUnsafe(); + return value.ToString(); +} + /** Store a duckdb value in a field of mysql format. @param field the field to store value @@ -67,7 +77,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, case MYSQL_TYPE_BLOB: case MYSQL_TYPE_GEOMETRY: case MYSQL_TYPE_BIT: { - auto str= value.GetValueUnsafe(); + auto str= duckdb_value_as_string(value); auto varchar= str.c_str(); auto varchar_len= str.size(); field->store(varchar, varchar_len, &my_charset_bin); @@ -78,14 +88,14 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, case MYSQL_TYPE_VAR_STRING: { if (is_uuid_field(field)) { - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); field->store(str.c_str(), str.size(), system_charset_info); break; } if (field->has_charset()) { DBUG_ASSERT(field->charset() != &my_charset_bin); - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); auto varchar= str.c_str(); auto varchar_len= str.size(); field->store(varchar, varchar_len, field->charset()); @@ -93,7 +103,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, } else { - auto str= value.GetValueUnsafe(); + auto str= duckdb_value_as_string(value); auto varchar= str.c_str(); auto varchar_len= str.size(); field->store(varchar, varchar_len, &my_charset_bin); @@ -105,7 +115,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: case MYSQL_TYPE_NEWDECIMAL: { - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); auto varchar= str.c_str(); auto varchar_len= str.size(); field->store(varchar, varchar_len, system_charset_info); @@ -157,7 +167,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, DuckDB date_t stores days since 1970-01-01. Convert to MYSQL_TIME via val_str and store_time. */ - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); MYSQL_TIME tm; MYSQL_TIME_STATUS status; my_time_status_init(&status); @@ -167,7 +177,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, } case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_DATETIME2: { - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); MYSQL_TIME tm; MYSQL_TIME_STATUS status; my_time_status_init(&status); @@ -177,7 +187,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, } case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_TIMESTAMP2: { - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); MYSQL_TIME tm; MYSQL_TIME_STATUS status; my_time_status_init(&status); @@ -187,7 +197,7 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value, } case MYSQL_TYPE_TIME: case MYSQL_TYPE_TIME2: { - auto str= value.GetValue(); + auto str= duckdb_value_as_string(value); MYSQL_TIME tm; MYSQL_TIME_STATUS status; my_time_status_init(&status); diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_41208.result b/storage/duckdb/mysql-test/duckdb/r/mdev_41208.result new file mode 100644 index 0000000000000..26c5ff1b13472 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_41208.result @@ -0,0 +1,31 @@ +CREATE TABLE t1 (c1 INT PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO t1 VALUES (1); +SELECT COALESCE(1,'t') FROM t1; +COALESCE(1,'t') +1 +SELECT COALESCE(c1,'t') FROM t1; +COALESCE(c1,'t') +1 +SELECT IFNULL(1,'t') FROM t1; +IFNULL(1,'t') +1 +SET NAMES BINARY; +SELECT COALESCE(1,'t') FROM t1; +COALESCE(1,'t') +1 +SELECT COALESCE(c1,'t') FROM t1; +COALESCE(c1,'t') +1 +SELECT IFNULL(1,'t') FROM t1; +IFNULL(1,'t') +1 +SET NAMES utf8mb4; +CREATE TABLE t2 (c1 VARCHAR(10) CHARACTER SET utf8mb4 PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO t2 VALUES ('ab'); +SELECT c1 FROM t2; +c1 +ab +SELECT COALESCE(c1, 'z') FROM t2; +COALESCE(c1, 'z') +ab +DROP TABLE t1, t2; diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_41208.test b/storage/duckdb/mysql-test/duckdb/t/mdev_41208.test new file mode 100644 index 0000000000000..691149d8620ce --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_41208.test @@ -0,0 +1,31 @@ +--source ../include/have_duckdb.inc +--source include/not_msan.inc +--disable_warnings + +# +# MDEV-41208 SIGABRT in StringValue::Get after SET NAMES BINARY +# +# MariaDB types mixed numeric/string COALESCE as VARCHAR. DuckDB types it as +# INTEGER because untyped string literals take the other argument's type. +# Fetching that integer with GetValueUnsafe() asserts VARCHAR. +# + +CREATE TABLE t1 (c1 INT PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO t1 VALUES (1); + +SELECT COALESCE(1,'t') FROM t1; +SELECT COALESCE(c1,'t') FROM t1; +SELECT IFNULL(1,'t') FROM t1; + +SET NAMES BINARY; +SELECT COALESCE(1,'t') FROM t1; +SELECT COALESCE(c1,'t') FROM t1; +SELECT IFNULL(1,'t') FROM t1; +SET NAMES utf8mb4; + +CREATE TABLE t2 (c1 VARCHAR(10) CHARACTER SET utf8mb4 PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO t2 VALUES ('ab'); +SELECT c1 FROM t2; +SELECT COALESCE(c1, 'z') FROM t2; + +DROP TABLE t1, t2;