1
0
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:
Anway Durge
2026-10-15 22:54:08 +05:30
committed by drrtuy
parent 82964737c0
commit e782b38509
8 changed files with 200 additions and 15 deletions

View File

@@ -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("'"));

View File

@@ -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);

View 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;

View File

@@ -3,3 +3,4 @@
--duckdb_allow_run_in_duckdb=ON
--character-set-server=utf8mb4
--character-set-collations=utf8mb4=utf8mb4_uca1400_ai_ci
--timezone=GMT

View 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

View File

@@ -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;

View File

@@ -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:

View File

@@ -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");