mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-40819: Do not crash on DuckDB TIME values beyond 24 hours
MariaDB TIME is a duration up to 838:59:59, but SELECT converted DuckDB TIME through ToString(), which asserts clock-time validity in debug builds. Convert microseconds to MYSQL_TIME directly instead. SQL DML quoted TIME strings, so '25:00:00' failed conversion. Store microseconds through mariadb_time() instead, matching the appender path.
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "sql_table.h" /* primary_key_name */
|
||||
#include "sql_time.h"
|
||||
#include "my_decimal.h"
|
||||
|
||||
namespace myduck { extern my_bool use_double_for_decimal; }
|
||||
@@ -117,15 +118,28 @@ void append_field_value_to_sql(String &target_str, Field *field)
|
||||
case MYSQL_TYPE_NEWDATE:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_DATETIME2:
|
||||
case MYSQL_TYPE_YEAR:
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_TIME2: {
|
||||
case MYSQL_TYPE_YEAR: {
|
||||
target_str.append(STRING_WITH_LEN("'"));
|
||||
field->val_str(&field_value);
|
||||
target_str.append(field_value);
|
||||
target_str.append(STRING_WITH_LEN("'"));
|
||||
break;
|
||||
}
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_TIME2: {
|
||||
MYSQL_TIME tm;
|
||||
field->get_date(&tm, date_mode_t(0));
|
||||
int64_t micros=
|
||||
(tm.hour * 3600LL + tm.minute * 60LL + tm.second) * 1000000LL +
|
||||
tm.second_part;
|
||||
if (tm.neg)
|
||||
micros= -micros;
|
||||
char buf[64];
|
||||
int len= snprintf(buf, sizeof(buf), "mariadb_time(%lld)",
|
||||
(long long) micros);
|
||||
target_str.append(buf, len);
|
||||
break;
|
||||
}
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
case MYSQL_TYPE_TIMESTAMP2: {
|
||||
target_str.append(STRING_WITH_LEN("'"));
|
||||
|
||||
@@ -41,6 +41,45 @@ static void store_field_temporal_value(Field *field, MYSQL_TIME *ltime)
|
||||
field->store_time(ltime);
|
||||
}
|
||||
|
||||
/**
|
||||
Convert a DuckDB TIME value to MYSQL_TIME without DuckDB's string cast.
|
||||
physical INT64 and build MYSQL_TIME here instead.
|
||||
*/
|
||||
static void store_duckdb_time_in_mysql_format(Field *field, duckdb::Value &value)
|
||||
{
|
||||
MYSQL_TIME tm;
|
||||
set_zero_time(&tm, MYSQL_TIMESTAMP_TIME);
|
||||
|
||||
const duckdb::LogicalTypeId type_id= value.type().id();
|
||||
if (type_id == duckdb::LogicalTypeId::TIME ||
|
||||
type_id == duckdb::LogicalTypeId::TIME_NS)
|
||||
{
|
||||
int64_t micros;
|
||||
if (type_id == duckdb::LogicalTypeId::TIME_NS)
|
||||
micros= value.GetValueUnsafe<duckdb::dtime_ns_t>().time().micros;
|
||||
else
|
||||
micros= value.GetValueUnsafe<duckdb::dtime_t>().micros;
|
||||
if (micros < 0)
|
||||
{
|
||||
tm.neg= 1;
|
||||
micros= (micros == LONGLONG_MIN) ? LONGLONG_MAX : -micros;
|
||||
}
|
||||
const int64_t max_micros=
|
||||
(int64_t) TIME_MAX_VALUE_SECONDS * 1000000LL + TIME_MAX_SECOND_PART;
|
||||
if (micros > max_micros)
|
||||
micros= max_micros;
|
||||
calc_time_from_sec(&tm, (ulong) (micros / 1000000LL), (ulong) (micros % 1000000LL));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto str= value.GetValue<duckdb::string>();
|
||||
MYSQL_TIME_STATUS status;
|
||||
my_time_status_init(&status);
|
||||
str_to_DDhhmmssff(str.c_str(), str.size(), &tm, TIME_MAX_HOUR, &status);
|
||||
}
|
||||
store_field_temporal_value(field, &tm);
|
||||
}
|
||||
|
||||
/**
|
||||
Store a duckdb value in a field of mysql format.
|
||||
@param field the field to store value
|
||||
@@ -186,15 +225,9 @@ void store_duckdb_field_in_mysql_format(Field *field, duckdb::Value &value,
|
||||
break;
|
||||
}
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_TIME2: {
|
||||
auto str= value.GetValue<duckdb::string>();
|
||||
MYSQL_TIME tm;
|
||||
MYSQL_TIME_STATUS status;
|
||||
my_time_status_init(&status);
|
||||
str_to_DDhhmmssff(str.c_str(), str.size(), &tm, TIME_MAX_HOUR, &status);
|
||||
store_field_temporal_value(field, &tm);
|
||||
case MYSQL_TYPE_TIME2:
|
||||
store_duckdb_time_in_mysql_format(field, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* TODO: no support */
|
||||
DBUG_ASSERT(0);
|
||||
|
||||
50
storage/duckdb/mysql-test/duckdb/r/mdev_40819.result
Normal file
50
storage/duckdb/mysql-test/duckdb/r/mdev_40819.result
Normal file
@@ -0,0 +1,50 @@
|
||||
# SQL insert path (dml_in_batch=OFF)
|
||||
SET GLOBAL duckdb_dml_in_batch = OFF;
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES (1, '25:00:00');
|
||||
SELECT * FROM t1;
|
||||
id t
|
||||
1 25:00:00
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME, t6 TIME(6)) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES
|
||||
(1, '00:00:00', '00:00:00.000000'),
|
||||
(2, '23:59:59', '23:59:59.123456'),
|
||||
(3, '24:00:00', '24:00:00.000000'),
|
||||
(4, '25:00:00', '25:00:00.123456'),
|
||||
(5, '838:59:59', '838:59:59.000000'),
|
||||
(6, '-25:00:00', '-25:00:00.123456');
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
id t t6
|
||||
1 00:00:00 00:00:00.000000
|
||||
2 23:59:59 23:59:59.123456
|
||||
3 24:00:00 24:00:00.000000
|
||||
4 25:00:00 25:00:00.123456
|
||||
5 838:59:59 838:59:59.000000
|
||||
6 -25:00:00 -25:00:00.123456
|
||||
DROP TABLE t1;
|
||||
# Appender path (dml_in_batch=ON)
|
||||
SET GLOBAL duckdb_dml_in_batch = ON;
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES (1, '25:00:00');
|
||||
SELECT * FROM t1;
|
||||
id t
|
||||
1 25:00:00
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME, t6 TIME(6)) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES
|
||||
(1, '00:00:00', '00:00:00.000000'),
|
||||
(2, '23:59:59', '23:59:59.123456'),
|
||||
(3, '24:00:00', '24:00:00.000000'),
|
||||
(4, '25:00:00', '25:00:00.123456'),
|
||||
(5, '838:59:59', '838:59:59.000000'),
|
||||
(6, '-25:00:00', '-25:00:00.123456');
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
id t t6
|
||||
1 00:00:00 00:00:00.000000
|
||||
2 23:59:59 23:59:59.123456
|
||||
3 24:00:00 24:00:00.000000
|
||||
4 25:00:00 25:00:00.123456
|
||||
5 838:59:59 838:59:59.000000
|
||||
6 -25:00:00 -25:00:00.123456
|
||||
DROP TABLE t1;
|
||||
@@ -3,3 +3,4 @@
|
||||
--duckdb_allow_run_in_duckdb=ON
|
||||
--character-set-server=utf8mb4
|
||||
--character-set-collations=utf8mb4=utf8mb4_uca1400_ai_ci
|
||||
--timezone=GMT
|
||||
|
||||
53
storage/duckdb/mysql-test/duckdb/t/mdev_40819.test
Normal file
53
storage/duckdb/mysql-test/duckdb/t/mdev_40819.test
Normal file
@@ -0,0 +1,53 @@
|
||||
--source ../include/have_duckdb.inc
|
||||
--source include/not_msan.inc
|
||||
--disable_warnings
|
||||
|
||||
#
|
||||
# MDEV-40819 DuckDB TIME values beyond 24 hours
|
||||
#
|
||||
|
||||
--disable_query_log
|
||||
SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch;
|
||||
--enable_query_log
|
||||
|
||||
--echo # SQL insert path (dml_in_batch=OFF)
|
||||
SET GLOBAL duckdb_dml_in_batch = OFF;
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES (1, '25:00:00');
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME, t6 TIME(6)) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES
|
||||
(1, '00:00:00', '00:00:00.000000'),
|
||||
(2, '23:59:59', '23:59:59.123456'),
|
||||
(3, '24:00:00', '24:00:00.000000'),
|
||||
(4, '25:00:00', '25:00:00.123456'),
|
||||
(5, '838:59:59', '838:59:59.000000'),
|
||||
(6, '-25:00:00', '-25:00:00.123456');
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # Appender path (dml_in_batch=ON)
|
||||
SET GLOBAL duckdb_dml_in_batch = ON;
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES (1, '25:00:00');
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, t TIME, t6 TIME(6)) ENGINE=DuckDB;
|
||||
INSERT INTO t1 VALUES
|
||||
(1, '00:00:00', '00:00:00.000000'),
|
||||
(2, '23:59:59', '23:59:59.123456'),
|
||||
(3, '24:00:00', '24:00:00.000000'),
|
||||
(4, '25:00:00', '25:00:00.123456'),
|
||||
(5, '838:59:59', '838:59:59.000000'),
|
||||
(6, '-25:00:00', '-25:00:00.123456');
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
DROP TABLE t1;
|
||||
|
||||
--disable_query_log
|
||||
SET GLOBAL duckdb_dml_in_batch = @saved_duckdb_dml_in_batch;
|
||||
--enable_query_log
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "field.h"
|
||||
#include "handler.h"
|
||||
#include "mysqld.h"
|
||||
#include "sql_time.h"
|
||||
#include "log.h"
|
||||
|
||||
#undef UNKNOWN
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "duckdb_log.h"
|
||||
|
||||
#include "duckdb/main/database.hpp"
|
||||
#include "duckdb/common/types/datetime.hpp"
|
||||
#include "duckdb/catalog/catalog.hpp"
|
||||
#include "duckdb/catalog/catalog_transaction.hpp"
|
||||
#include "duckdb/function/table_function.hpp"
|
||||
@@ -197,8 +199,6 @@ static duckdb::Value field_to_duckdb_value(Field *field)
|
||||
}
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_NEWDATE:
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_TIME2:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_DATETIME2:
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
@@ -207,6 +207,17 @@ static duckdb::Value field_to_duckdb_value(Field *field)
|
||||
field->val_str(&buf);
|
||||
return duckdb::Value(std::string(buf.ptr(), buf.length()));
|
||||
}
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_TIME2: {
|
||||
MYSQL_TIME tm;
|
||||
field->get_date(&tm, date_mode_t(0));
|
||||
int64_t micros=
|
||||
(tm.hour * 3600LL + tm.minute * 60LL + tm.second) * 1000000LL +
|
||||
tm.second_part;
|
||||
if (tm.neg)
|
||||
micros= -micros;
|
||||
return duckdb::Value::TIME(duckdb::dtime_t(micros));
|
||||
}
|
||||
case MYSQL_TYPE_BIT:
|
||||
case MYSQL_TYPE_GEOMETRY: {
|
||||
String buf;
|
||||
|
||||
@@ -412,9 +412,12 @@ int DeltaAppender::append_mysql_field(const Field *field_arg,
|
||||
case MYSQL_TYPE_TIME2: {
|
||||
MYSQL_TIME tm;
|
||||
field->get_date(&tm, date_mode_t(0));
|
||||
appender->Append<duckdb::dtime_t>(static_cast<duckdb::dtime_t>(
|
||||
int64_t micros=
|
||||
(tm.hour * 3600LL + tm.minute * 60LL + tm.second) * 1000000LL +
|
||||
tm.second_part));
|
||||
tm.second_part;
|
||||
if (tm.neg)
|
||||
micros= -micros;
|
||||
appender->Append<duckdb::dtime_t>(static_cast<duckdb::dtime_t>(micros));
|
||||
break;
|
||||
}
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/datetime.hpp"
|
||||
#include "duckdb/main/database.hpp"
|
||||
#include "duckdb/main/connection.hpp"
|
||||
|
||||
@@ -1667,9 +1668,28 @@ static void register_json_unquote_function(duckdb::Catalog &catalog,
|
||||
}
|
||||
}
|
||||
|
||||
static void mariadb_time_from_micros_func(duckdb::DataChunk &args,
|
||||
duckdb::ExpressionState &,
|
||||
duckdb::Vector &result)
|
||||
{
|
||||
duckdb::UnaryExecutor::Execute<int64_t, duckdb::dtime_t>(
|
||||
args.data[0], result, args.size(),
|
||||
[](int64_t micros) { return duckdb::dtime_t(micros); });
|
||||
}
|
||||
|
||||
static void register_time_arith_functions(duckdb::Catalog &catalog,
|
||||
duckdb::CatalogTransaction transaction)
|
||||
{
|
||||
{
|
||||
duckdb::ScalarFunctionSet set("mariadb_time");
|
||||
set.AddFunction(duckdb::ScalarFunction(
|
||||
{duckdb::LogicalType::BIGINT}, duckdb::LogicalType::TIME,
|
||||
mariadb_time_from_micros_func));
|
||||
duckdb::CreateScalarFunctionInfo info(std::move(set));
|
||||
info.on_conflict= duckdb::OnCreateConflict::ALTER_ON_CONFLICT;
|
||||
catalog.CreateFunction(transaction, info);
|
||||
}
|
||||
|
||||
/* addtime(TIMESTAMP/TIME, VARCHAR) → TIMESTAMP/TIME */
|
||||
{
|
||||
duckdb::ScalarFunctionSet set("addtime");
|
||||
|
||||
Reference in New Issue
Block a user