From a23bdae5bcd4c5ecc81fcf25698f249c12f66638 Mon Sep 17 00:00:00 2001 From: Alex Kasko Date: Sun, 7 Sep 2025 12:01:18 +0100 Subject: [PATCH] Parameters in mysql_query and mysql_execute This PR adds new optional `params` argument to `mysql_query` and `mysql_execute` functions that allows to pass query parameters to an underlying MySQL prepared statement (that was added in #156). Parameters are specified as a `STRUCT`, that can be created inline using `row()` function: ```sql SELECT * from mysql_query('s', 'SELECT CONCAT(?, ?) a', params=row('foo', 'bar')) ---- foobar ``` When the whole DuckDB SQL statement, that contains `mysql_query()` function, is used with `PREPARE` + `EXECUTE` (for example, from Python or Java client), then the external client-provided parameters will be forwarded to MySQL: ```sql PREPARE p1 as SELECT * from mysql_query('s', 'SELECT CONCAT(?, ?) a', params=row(?, ?)) EXECUTE p1('foo', 'bar') ---- foobar EXECUTE p1('baz', 'boo') ---- bazboo DEALLOCATE p1 ``` In current implementation the statement is re-prepared (re-sent to MySQL server) every time when `EXECUTE` is called. The following parameter types are supported: integers, `FLOAT`, `DOUBLE`, `DATE`, `TIME`, `TIMESTAMP`, `TIMESTAMP_TZ`, `VARCHAR`. `DECIMAL` parameters are currently not supported, they can only be specified as strings on API level, so casts to/from `VARCHAR` can be used instead. Only positional parameters are supported - `STRUCT` field names are ignored. Testing: new tests added for `mysql_query` and `mysql_execute` fuctions. --- src/CMakeLists.txt | 1 + src/include/mysql_connection.hpp | 8 +- src/include/mysql_parameter.hpp | 29 +++++ src/mysql_connection.cpp | 51 +++++++-- src/mysql_execute.cpp | 22 +++- src/mysql_parameter.cpp | 175 +++++++++++++++++++++++++++++++ src/mysql_scanner.cpp | 19 +++- test/sql/execute_params.test | 48 +++++++++ test/sql/query_params.test | 166 +++++++++++++++++++++++++++++ 9 files changed, 503 insertions(+), 16 deletions(-) create mode 100644 src/include/mysql_parameter.hpp create mode 100644 src/mysql_parameter.cpp create mode 100644 test/sql/execute_params.test create mode 100644 test/sql/query_params.test diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba978b4..de1c7d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ add_library( mysql_execute.cpp mysql_extension.cpp mysql_filter_pushdown.cpp + mysql_parameter.cpp mysql_result.cpp mysql_scanner.cpp mysql_storage.cpp diff --git a/src/include/mysql_connection.hpp b/src/include/mysql_connection.hpp index ad5b4ee..47c4c41 100644 --- a/src/include/mysql_connection.hpp +++ b/src/include/mysql_connection.hpp @@ -53,8 +53,10 @@ class MySQLConnection { public: static MySQLConnection Open(MySQLTypeConfig type_config, const string &connection_string); - void Execute(const string &query, MySQLConnectorInterface con_interface = MySQLConnectorInterface::BASIC); + void Execute(const string &query); + void Execute(const string &query, vector params); unique_ptr Query(const string &query, MySQLResultStreaming streaming); + unique_ptr Query(const string &query, vector params, MySQLResultStreaming streaming); vector GetIndexInfo(const string &table_name); @@ -79,9 +81,9 @@ class MySQLConnection { static bool DebugPrintQueries(); private: - unique_ptr QueryInternal(const string &query, MySQLResultStreaming streaming, + unique_ptr QueryInternal(const string &query, vector params, MySQLResultStreaming streaming, MySQLConnectorInterface con_interface); - idx_t MySQLExecute(MYSQL_STMT *stmt, const string &query, bool streaming); + idx_t MySQLExecute(MYSQL_STMT *stmt, const string &query, vector params, bool streaming); mutex query_lock; shared_ptr connection; diff --git a/src/include/mysql_parameter.hpp b/src/include/mysql_parameter.hpp new file mode 100644 index 0000000..f7a6e99 --- /dev/null +++ b/src/include/mysql_parameter.hpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// mysql_parameter.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb.hpp" +#include "mysql.h" + +namespace duckdb { + +struct MySQLParameter { + Value value; + enum_field_types buffer_type = MYSQL_TYPE_INVALID; + bool is_unsigned = false; + + vector bind_buffer; + unsigned long bind_length = 0; + + MySQLParameter(const string &query, Value value_p); + + MYSQL_BIND CreateBind(); +}; + +} // namespace duckdb diff --git a/src/mysql_connection.cpp b/src/mysql_connection.cpp index 197f20a..fa0e324 100644 --- a/src/mysql_connection.cpp +++ b/src/mysql_connection.cpp @@ -5,6 +5,7 @@ #include "duckdb/parser/parser.hpp" #include "duckdb/storage/table_storage_info.hpp" +#include "mysql_parameter.hpp" #include "mysql_types.hpp" namespace duckdb { @@ -38,7 +39,7 @@ MySQLConnection MySQLConnection::Open(MySQLTypeConfig type_config, const string return MySQLConnection(std::move(connection), connection_string, std::move(type_config)); } -idx_t MySQLConnection::MySQLExecute(MYSQL_STMT *stmt, const string &query, bool streaming) { +idx_t MySQLConnection::MySQLExecute(MYSQL_STMT *stmt, const string &query, vector params, bool streaming) { if (MySQLConnection::DebugPrintQueries()) { Printer::Print(query + "\n"); } @@ -62,6 +63,30 @@ idx_t MySQLConnection::MySQLExecute(MYSQL_STMT *stmt, const string &query, bool throw IOException("Failed to prepare MySQL query \"%s\": %s\n", query.c_str(), mysql_stmt_error(stmt)); } + vector mysql_params; + vector binds; + if (params.size() > 0) { + size_t expected_count = mysql_stmt_param_count(stmt); + if (expected_count != params.size()) { + throw IOException( + "Incorrect query parameters count specified, expected: %zu, actual: %zu, MySQL query \"%s\": %s\n", + expected_count, params.size(), query.c_str(), mysql_stmt_error(stmt)); + } + mysql_params.reserve(params.size()); + binds.reserve(params.size()); + for (Value &dp : params) { + MySQLParameter mp(query, std::move(dp)); + mysql_params.emplace_back(std::move(mp)); + MySQLParameter &mp_ref = mysql_params.back(); + binds.push_back(mp_ref.CreateBind()); + } + auto res_bind = mysql_stmt_bind_param(stmt, binds.data()); + if (res_bind != 0) { + throw IOException("Failed to bind parameters, count: %zu, MySQL query \"%s\": %s\n", binds.size(), + query.c_str(), mysql_stmt_error(stmt)); + } + } + int res_exec = mysql_stmt_execute(stmt); if (res_exec != 0) { throw IOException("Failed to execute MySQL query \"%s\": %s\n", query.c_str(), mysql_stmt_error(stmt)); @@ -87,14 +112,15 @@ idx_t MySQLConnection::MySQLExecute(MYSQL_STMT *stmt, const string &query, bool return affected_rows; } -unique_ptr MySQLConnection::QueryInternal(const string &query, MySQLResultStreaming streaming, +unique_ptr MySQLConnection::QueryInternal(const string &query, vector params, + MySQLResultStreaming streaming, MySQLConnectorInterface con_interface) { auto con = GetConn(); bool result_streaming = streaming == MySQLResultStreaming::ALLOW_STREAMING; bool basic_interface = con_interface == MySQLConnectorInterface::BASIC; if (basic_interface) { - MySQLExecute(nullptr, query, result_streaming); + MySQLExecute(nullptr, query, params, result_streaming); return unique_ptr(nullptr); } @@ -102,16 +128,27 @@ unique_ptr MySQLConnection::QueryInternal(const string &query, MySQ if (!stmt) { throw IOException("Failed to initialize MySQL query \"%s\": %s\n", query.c_str(), mysql_error(con)); } - idx_t affected_rows = MySQLExecute(stmt.get(), query, result_streaming); + idx_t affected_rows = MySQLExecute(stmt.get(), query, params, result_streaming); return make_uniq(query, std::move(stmt), type_config, affected_rows); } unique_ptr MySQLConnection::Query(const string &query, MySQLResultStreaming streaming) { - return QueryInternal(query, streaming, MySQLConnectorInterface::PREPARED_STATEMENT); + return Query(query, vector(), streaming); +} + +unique_ptr MySQLConnection::Query(const string &query, vector params, + MySQLResultStreaming streaming) { + return QueryInternal(query, params, streaming, MySQLConnectorInterface::PREPARED_STATEMENT); +} + +void MySQLConnection::Execute(const string &query) { + Execute(query, vector()); } -void MySQLConnection::Execute(const string &query, MySQLConnectorInterface con_interface) { - QueryInternal(query, MySQLResultStreaming::FORCE_MATERIALIZATION, con_interface); +void MySQLConnection::Execute(const string &query, vector params) { + MySQLConnectorInterface con_interface = + params.size() > 0 ? MySQLConnectorInterface::PREPARED_STATEMENT : MySQLConnectorInterface::BASIC; + QueryInternal(query, std::move(params), MySQLResultStreaming::FORCE_MATERIALIZATION, con_interface); } bool MySQLConnection::IsOpen() { diff --git a/src/mysql_execute.cpp b/src/mysql_execute.cpp index 3139479..55b3355 100644 --- a/src/mysql_execute.cpp +++ b/src/mysql_execute.cpp @@ -11,13 +11,14 @@ namespace duckdb { struct MySQLExecuteBindData : public TableFunctionData { - explicit MySQLExecuteBindData(MySQLCatalog &mysql_catalog, string query_p) - : mysql_catalog(mysql_catalog), query(std::move(query_p)) { + explicit MySQLExecuteBindData(MySQLCatalog &mysql_catalog, string query_p, vector params_p) + : mysql_catalog(mysql_catalog), query(std::move(query_p)), params(std::move(params_p)) { } bool finished = false; MySQLCatalog &mysql_catalog; string query; + vector params; }; static duckdb::unique_ptr MySQLExecuteBind(ClientContext &context, TableFunctionBindInput &input, @@ -37,7 +38,19 @@ static duckdb::unique_ptr MySQLExecuteBind(ClientContext &context, throw BinderException("Attached database \"%s\" does not refer to a MySQL database", db_name); } auto &mysql_catalog = catalog.Cast(); - return make_uniq(mysql_catalog, input.inputs[1].GetValue()); + vector params; + auto params_it = input.named_parameters.find("params"); + if (params_it != input.named_parameters.end()) { + Value &struct_val = params_it->second; + if (struct_val.IsNull()) { + throw BinderException("Parameters to mysql_execute cannot be NULL"); + } + if (struct_val.type().id() != LogicalTypeId::STRUCT) { + throw BinderException("Query parameters must be specified in a STRUCT"); + } + params = StructValue::GetChildren(struct_val); + } + return make_uniq(mysql_catalog, input.inputs[1].GetValue(), std::move(params)); } static void MySQLExecuteFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { @@ -49,12 +62,13 @@ static void MySQLExecuteFunc(ClientContext &context, TableFunctionInput &data_p, if (transaction.GetAccessMode() == AccessMode::READ_ONLY) { throw PermissionException("mysql_execute cannot be run in a read-only connection"); } - transaction.GetConnection().Execute(data.query); + transaction.GetConnection().Execute(data.query, std::move(data.params)); data.finished = true; } MySQLExecuteFunction::MySQLExecuteFunction() : TableFunction("mysql_execute", {LogicalType::VARCHAR, LogicalType::VARCHAR}, MySQLExecuteFunc, MySQLExecuteBind) { + named_parameters["params"] = LogicalType::ANY; } } // namespace duckdb diff --git a/src/mysql_parameter.cpp b/src/mysql_parameter.cpp new file mode 100644 index 0000000..0b14973 --- /dev/null +++ b/src/mysql_parameter.cpp @@ -0,0 +1,175 @@ +#include "mysql_parameter.hpp" + +#include "duckdb/common/types/datetime.hpp" +#include "duckdb/common/types/time.hpp" +#include "duckdb/common/types/timestamp.hpp" + +namespace duckdb { + +template +static void FillNumberBuffer(Value &value, vector &bind_buffer) { + bind_buffer.resize(sizeof(NUM_TYPE)); + NUM_TYPE val = value.GetValueUnsafe(); + std::memcpy(bind_buffer.data(), &val, sizeof(NUM_TYPE)); +} + +static void FillDateBuffer(Value &value, vector &bind_buffer) { + MYSQL_TIME mt; + std::memset(&mt, '\0', sizeof(MYSQL_TIME)); + date_t dd = DateValue::Get(value); + int32_t year, month, day; + Date::Convert(dd, year, month, day); + + mt.year = static_cast(std::abs(year)); + mt.month = static_cast(std::abs(month)); + mt.day = static_cast(std::abs(day)); + + bind_buffer.resize(sizeof(MYSQL_TIME)); + std::memcpy(bind_buffer.data(), &mt, sizeof(MYSQL_TIME)); +} + +static void FillTimeBuffer(Value &value, vector &bind_buffer) { + MYSQL_TIME mt; + std::memset(&mt, '\0', sizeof(MYSQL_TIME)); + dtime_t dt = TimeValue::Get(value); + int32_t hour, minute, second, micros; + Time::Convert(dt, hour, minute, second, micros); + + mt.hour = static_cast(std::abs(hour)); + mt.minute = static_cast(std::abs(minute)); + mt.second = static_cast(std::abs(second)); + mt.second_part = static_cast(std::abs(micros)); + + bind_buffer.resize(sizeof(MYSQL_TIME)); + std::memcpy(bind_buffer.data(), &mt, sizeof(MYSQL_TIME)); +} + +static void FillTimestampBuffer(Value &value, vector &bind_buffer) { + MYSQL_TIME mt; + std::memset(&mt, '\0', sizeof(MYSQL_TIME)); + timestamp_t ts = TimestampValue::Get(value); + date_t dd; + dtime_t dt; + Timestamp::Convert(ts, dd, dt); + int32_t year, month, day; + Date::Convert(dd, year, month, day); + int32_t hour, minute, second, micros; + Time::Convert(dt, hour, minute, second, micros); + + mt.year = static_cast(std::abs(year)); + mt.month = static_cast(std::abs(month)); + mt.day = static_cast(std::abs(day)); + mt.hour = static_cast(std::abs(hour)); + mt.minute = static_cast(std::abs(minute)); + mt.second = static_cast(std::abs(second)); + mt.second_part = static_cast(std::abs(micros)); + + bind_buffer.resize(sizeof(MYSQL_TIME)); + std::memcpy(bind_buffer.data(), &mt, sizeof(MYSQL_TIME)); +} + +MySQLParameter::MySQLParameter(const string &query, Value value_p) : value(std::move(value_p)) { + if (value.IsNull()) { + return; + } + + switch (value.type().id()) { + case LogicalTypeId::BOOLEAN: + this->buffer_type = MYSQL_TYPE_TINY; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::TINYINT: + this->buffer_type = MYSQL_TYPE_TINY; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::UTINYINT: + this->buffer_type = MYSQL_TYPE_TINY; + this->is_unsigned = true; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::SMALLINT: + this->buffer_type = MYSQL_TYPE_SHORT; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::USMALLINT: + this->buffer_type = MYSQL_TYPE_SHORT; + this->is_unsigned = true; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::INTEGER: + this->buffer_type = MYSQL_TYPE_LONG; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::UINTEGER: + this->buffer_type = MYSQL_TYPE_LONG; + this->is_unsigned = true; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::BIGINT: + this->buffer_type = MYSQL_TYPE_LONGLONG; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::UBIGINT: + this->buffer_type = MYSQL_TYPE_LONGLONG; + this->is_unsigned = true; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::FLOAT: + this->buffer_type = MYSQL_TYPE_FLOAT; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::DOUBLE: + this->buffer_type = MYSQL_TYPE_DOUBLE; + FillNumberBuffer(value, bind_buffer); + break; + case LogicalTypeId::DATE: + this->buffer_type = MYSQL_TYPE_DATE; + FillDateBuffer(value, bind_buffer); + break; + case LogicalTypeId::TIME: + this->buffer_type = MYSQL_TYPE_TIME; + FillTimeBuffer(value, bind_buffer); + break; + case LogicalTypeId::TIMESTAMP: + this->buffer_type = MYSQL_TYPE_DATETIME; + FillTimestampBuffer(value, bind_buffer); + break; + case LogicalTypeId::TIMESTAMP_TZ: + this->buffer_type = MYSQL_TYPE_TIMESTAMP; + FillTimestampBuffer(value, bind_buffer); + break; + case LogicalTypeId::VARCHAR: + // use string ref from the value + break; + default: + throw IOException("Unsupported parameters type: \"%s\", MySQL query \"%s\"", value.type(), query.c_str()); + } +} + +MYSQL_BIND MySQLParameter::CreateBind() { + MYSQL_BIND bind; + std::memset(&bind, '\0', sizeof(MYSQL_BIND)); + + if (value.IsNull()) { + bind.buffer_type = MYSQL_TYPE_NULL; + bind.length = &bind_length; + } else if (value.type().id() == LogicalTypeId::VARCHAR) { + const string &str = StringValue::Get(value); + bind.buffer_type = MYSQL_TYPE_VARCHAR; + bind.buffer = const_cast(str.c_str()); + bind.buffer_length = str.length(); + bind_length = str.length(); + bind.length = &bind_length; + } else { + bind.buffer_type = buffer_type; + bind.is_unsigned = is_unsigned; + bind.buffer = bind_buffer.data(); + bind.buffer_length = bind_buffer.size(); + bind_length = bind_buffer.size(); + bind.length = &bind_length; + } + + return bind; +} + +} // namespace duckdb diff --git a/src/mysql_scanner.cpp b/src/mysql_scanner.cpp index fece308..94efee5 100644 --- a/src/mysql_scanner.cpp +++ b/src/mysql_scanner.cpp @@ -177,7 +177,22 @@ static unique_ptr MySQLQueryBind(ClientContext &context, TableFunc } auto &transaction = MySQLTransaction::Get(context, catalog); auto sql = input.inputs[1].GetValue(); - auto result = transaction.GetConnection().Query(sql, MySQLResultStreaming::FORCE_MATERIALIZATION); + + vector params; + auto params_it = input.named_parameters.find("params"); + if (params_it != input.named_parameters.end()) { + Value &struct_val = params_it->second; + if (struct_val.IsNull()) { + throw BinderException("Parameters to mysql_query cannot be NULL"); + } + if (struct_val.type().id() != LogicalTypeId::STRUCT) { + throw BinderException("Query parameters must be specified in a STRUCT"); + } + params = StructValue::GetChildren(struct_val); + } + + auto result = + transaction.GetConnection().Query(sql, std::move(params), MySQLResultStreaming::FORCE_MATERIALIZATION); for (auto &field : result->Fields()) { names.push_back(field.name); return_types.push_back(field.duckdb_type); @@ -207,7 +222,7 @@ MySQLQueryFunction::MySQLQueryFunction() MySQLQueryInitGlobalState, MySQLInitLocalState) { serialize = MySQLScanSerialize; deserialize = MySQLScanDeserialize; - // named_parameters["params"] = LogicalType::ANY; + named_parameters["params"] = LogicalType::ANY; } } // namespace duckdb diff --git a/test/sql/execute_params.test b/test/sql/execute_params.test new file mode 100644 index 0000000..5256fc7 --- /dev/null +++ b/test/sql/execute_params.test @@ -0,0 +1,48 @@ +# name: test/sql/execute_params.test +# description: Test mysql_execute with parameters +# group: [sql] + +require mysql_scanner + +require-env MYSQL_TEST_DATABASE_AVAILABLE + +statement ok +ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) + +statement ok +CALL mysql_execute('s', 'DROP TABLE IF EXISTS tab1') + +statement ok +CALL mysql_execute('s', 'CREATE TABLE tab1(col1 INTEGER, col2 DOUBLE, col3 DATETIME)') + +statement ok +CALL mysql_execute('s', 'INSERT INTO tab1 VALUES (?, ?, ?)', params=row(42, 42.123::DOUBLE, '2020-12-31 12:34:45')) + +statement ok +CALL mysql_execute('s', 'INSERT INTO tab1 VALUES (?, ?, ?)', params=row(43, 43.123::DOUBLE, '2020-12-31 12:35:45')) + +statement ok +CALL mysql_execute('s', 'UPDATE tab1 SET col2 = ?, col3 = ? WHERE col1 = ?', params=row(44.123::DOUBLE, '2020-12-31 12:36:45', 42)) + +query III +SELECT * FROM mysql_query('s', 'SELECT * FROM tab1 WHERE col1 = ?', params=row(42)) +---- +42 44.123 2020-12-31 12:36:45 + +statement ok +CALL mysql_execute('s', 'DROP TABLE tab1') + +statement error +CALL mysql_execute('s', 'SELECT ? col1', params=NULL) +---- +Binder Error: Parameters to mysql_execute cannot be NULL + +statement error +CALL mysql_execute('s', 'SELECT ? col1', params=42) +---- +Query parameters must be specified in a STRUCT + +statement error +CALL mysql_execute('s', 'SELECT ? col1', params=row('2020-12-31 12:34:56'::TIMESTAMP_NS)) +---- +IO Error: Unsupported parameters type: "TIMESTAMP_NS", MySQL query "SELECT ? col1" diff --git a/test/sql/query_params.test b/test/sql/query_params.test new file mode 100644 index 0000000..89fc647 --- /dev/null +++ b/test/sql/query_params.test @@ -0,0 +1,166 @@ +# name: test/sql/query_params.test +# description: Test mysql_query with parameters +# group: [sql] + +require mysql_scanner + +require-env MYSQL_TEST_DATABASE_AVAILABLE + +statement ok +ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row(NULL)) +---- +NULL VARCHAR + +# numbers + +query II +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2', params=row(TRUE, FALSE)) +---- +1 0 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::TINYINT, -127::TINYINT, 127::TINYINT)) +---- +42 -127 127 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::UTINYINT, 0::UTINYINT, 255::UTINYINT)) +---- +42 0 255 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::SMALLINT, -32767::SMALLINT, 32767::SMALLINT)) +---- +42 -32767 32767 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::USMALLINT, 0::USMALLINT, 65535::USMALLINT)) +---- +42 0 65535 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::INTEGER, -2147483647::INTEGER, 2147483647::INTEGER)) +---- +42 -2147483647 2147483647 + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::UINTEGER, 0::UINTEGER, 4294967295::UINTEGER)) +---- +42 0 4294967295 + +query IIII +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::BIGINT, -9223372036854775807::BIGINT, 9223372036854775807::BIGINT)) +---- +42 -9223372036854775807 9223372036854775807 BIGINT + +query IIII +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::UBIGINT, 0::UBIGINT, 18446744073709551615::UBIGINT)) +---- +42 0 18446744073709551615 UBIGINT + +query III +SELECT * FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::FLOAT, 0::FLOAT, 42.123::FLOAT)) +---- +42 0 42.123 + +query IIII +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1, ? col2, ? col3', params=row(42::DOUBLE, 0::DOUBLE, 42.123::DOUBLE)) +---- +42 0 42.123 DOUBLE + +# dates + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('2020-12-31'::DATE)) +---- +2020-12-31 DATE + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('1970-01-01'::DATE)) +---- +1970-01-01 DATE + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('0001-01-01'::DATE)) +---- +0001-01-01 DATE + +statement ok +SET mysql_time_as_time = TRUE + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('12:34:56.123'::TIME)) +---- +12:34:56.123 TIME + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('00:00:01'::TIME)) +---- +00:00:01 TIME + +statement ok +SET mysql_time_as_time = FALSE + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('2020-12-31 12:34:56.123'::TIMESTAMP)) +---- +2020-12-31 12:34:56.123 TIMESTAMP + +# require icu + +# statement ok +# SET TimeZone='UTC' + +# statement ok +# SET mysql_session_time_zone = '+01:00' + +# query II +# SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('2020-12-31 12:34:56.123-05:00'::TIMESTAMP WITH TIME ZONE)) +# ---- +# 2020-12-31 17:34:56.123+00 TIMESTAMP WITH TIME ZONE + +# varchar + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('foo'::VARCHAR)) +---- +foo VARCHAR + +query II +SELECT *, typeof(col1) FROM mysql_query('s', 'SELECT ? col1', params=row('01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'::VARCHAR)) +---- +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 VARCHAR + +statement ok +PREPARE p1 as SELECT * from mysql_query('s', 'SELECT CONCAT(?, ?) a', params=row(?, ?)) + +query I +EXECUTE p1('foo', 'bar') +---- +foobar + +query I +EXECUTE p1('baz', 'boo') +---- +bazboo + +statement ok +DEALLOCATE p1 + +statement error +SELECT * FROM mysql_query('s', 'SELECT ? col1', params=NULL) +---- +Binder Error: Parameters to mysql_query cannot be NULL + +statement error +SELECT * FROM mysql_query('s', 'SELECT ? col1', params=42) +---- +Query parameters must be specified in a STRUCT + +statement error +SELECT * FROM mysql_query('s', 'SELECT ? col1', params=row('2020-12-31 12:34:56'::TIMESTAMP_NS)) +---- +IO Error: Unsupported parameters type: "TIMESTAMP_NS", MySQL query "SELECT ? col1"