commit 1841585a4b76e0d10550d97cee622b81d125dc04 Author: Kaiwang Chen Date: Wed Jul 22 23:43:27 2026 +0800 Bug#87654321: Let table functions read their call-time arguments The initial table_function_registration service (see previous commit) gave components a describe/fill/cleanup contract but no way to read the values of the arguments passed at the call site. Without that a component could only emit a fixed result, so useful argument-driven functions such as generate_series(start, stop) were not expressible. This patch adds a third mysql_server service and wires it through the adapter, then ships generate_series as a worked example. New service ----------- - table_function_args arg_count(h, out_count) get_longlong(h, idx, out_value, out_is_null) get_double(h, idx, out_value, out_is_null) get_string(h, idx, out_str, out_length, out_is_null) The describe_cb and fill_cb callbacks now each receive an opaque Tf_args_handle. The service implementation maps that handle back to the running Table_function_dynamic and evaluates the corresponding argument Item (val_int / val_real / val_str). Because evaluation happens after do_init_args() has fix_fields()'d the arguments, callers may pass arbitrary expressions (1+1, 2*3) or ? placeholders, and the values are re-evaluated on every execution so PREPARE/EXECUTE and correlated uses observe the current values. Callback signatures change accordingly: describe_cb(thd, args, out_columns, out_n_columns, out_state) fill_cb(state, args, row_writer) The existing demo component (demo_tf_all_types) is updated to the new signatures; it simply ignores the args handle. Adapter ------- Table_function_dynamic exposes arg_count() and arg_at(); the args service is implemented in table_function_registration_imp.cc and registered with mysql_server in server_component.cc next to the existing two services. generate_series demo -------------------- components/test/generate_series.cc registers a PostgreSQL-style generate_series(start, stop [, step]) returning a single BIGINT column "value", so that SELECT * FROM generate_series(1, 5) AS t; returns 1..5. Argument count, a zero step and row-count/overflow are validated by the component. The new mysql-test/suite/test_table_function/generate_series test covers constant / expression / ? placeholder arguments, custom and negative step, empty and single-element ranges, aggregation over a large range, LEFT JOIN with NULL-complement, PREPARE/EXECUTE re-execution with different bound values, and the two argument error paths (ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, ER_WRONG_ARGUMENTS). No new reserved keyword, error code, sysvar, privilege, data-dictionary or replication change is introduced. Change-Id: I8765432187654321876543218765432187654322 diff --git a/components/test/CMakeLists.txt b/components/test/CMakeLists.txt index 5b18d9e65e7..cb5940393a4 100644 --- a/components/test/CMakeLists.txt +++ b/components/test/CMakeLists.txt @@ -35,6 +35,11 @@ MYSQL_ADD_COMPONENT(test_table_function MODULE_ONLY TEST_ONLY ) +MYSQL_ADD_COMPONENT(generate_series + generate_series.cc + MODULE_ONLY + TEST_ONLY + ) MYSQL_ADD_COMPONENT(udf_reg_3_func udf_reg_3_func.cc MODULE_ONLY diff --git a/components/test/generate_series.cc b/components/test/generate_series.cc new file mode 100644 index 00000000000..c1c8770d42b --- /dev/null +++ b/components/test/generate_series.cc @@ -0,0 +1,179 @@ +/* Copyright (c) 2025, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 2.0, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License, version 2.0, for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + Component that registers a generate_series() table function, similar + to the PostgreSQL set-returning function of the same name. + + Usage: + + INSTALL COMPONENT "file://component_generate_series"; + + SELECT * FROM generate_series(1, 5) AS t; -- 1..5 step 1 + SELECT * FROM generate_series(1, 10, 2) AS t; -- 1,3,5,7,9 + SELECT * FROM generate_series(5, 1, -1) AS t; -- 5,4,3,2,1 + + It exposes a single BIGINT column named "value". +*/ + +#include +#include +#include +#include + +#include + +#include "field_types.h" +#include "mysqld_error.h" + +REQUIRES_SERVICE_PLACEHOLDER(table_function_registration); +REQUIRES_SERVICE_PLACEHOLDER(table_function_row_writer); +REQUIRES_SERVICE_PLACEHOLDER(table_function_args); +REQUIRES_SERVICE_PLACEHOLDER(mysql_runtime_error); + +/* Single output column: value BIGINT. */ +static const Tf_column_def k_columns[] = { + {"value", MYSQL_TYPE_LONGLONG, 0, 0, /*not_null=*/1, /*is_unsigned=*/0}, +}; + +namespace { + +int gs_describe(void * /*thd_handle*/, Tf_args_handle *args, + const Tf_column_def **out_columns, unsigned int *out_n_columns, + void ** /*out_state*/) { + /* + Validate arity here, at parse time, so a wrong call is rejected + before execution. Accept generate_series(start, stop) or + generate_series(start, stop, step). + */ + unsigned int n = 0; + if (mysql_service_table_function_args->arg_count(args, &n)) return 1; + if (n != 2 && n != 3) { + mysql_error_service_emit_printf( + mysql_service_mysql_runtime_error, ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, 0, + "generate_series"); + return 1; + } + *out_columns = k_columns; + *out_n_columns = 1; + return 0; +} + +int gs_fill(void * /*state*/, Tf_args_handle *args, Tf_row_writer_handle *w) { + long long start = 0, stop = 0, step = 1; + int is_null = 0; + + if (mysql_service_table_function_args->get_longlong(args, 0, &start, + &is_null) || + is_null) + return 1; + if (mysql_service_table_function_args->get_longlong(args, 1, &stop, + &is_null) || + is_null) + return 1; + + unsigned int n = 0; + if (mysql_service_table_function_args->arg_count(args, &n)) return 1; + if (n == 3) { + if (mysql_service_table_function_args->get_longlong(args, 2, &step, + &is_null) || + is_null) + return 1; + } + + if (step == 0) { + mysql_error_service_emit_printf(mysql_service_mysql_runtime_error, + ER_WRONG_ARGUMENTS, 0, "generate_series"); + return 1; + } + + /* + Produce the arithmetic series [start, stop] with the given step. + Guard the iteration count so a pathological call cannot spin + forever or fill the disk without bound. + */ + const unsigned long long kMaxRows = 100000000ULL; // 100M safety cap + unsigned long long produced = 0; + + if (step > 0) { + for (long long v = start; v <= stop; v += step) { + if (mysql_service_table_function_row_writer->set_longlong(w, 0, v, 0)) + return 1; + if (mysql_service_table_function_row_writer->emit_row(w)) return 1; + if (++produced > kMaxRows) break; + /* Overflow guard: stop if the next add would wrap past LLONG_MAX. */ + if (v > 0 && step > 0 && v > (9223372036854775807LL - step)) break; + } + } else { + for (long long v = start; v >= stop; v += step) { + if (mysql_service_table_function_row_writer->set_longlong(w, 0, v, 0)) + return 1; + if (mysql_service_table_function_row_writer->emit_row(w)) return 1; + if (++produced > kMaxRows) break; + if (v < 0 && step < 0 && v < (-9223372036854775807LL - 1 - step)) break; + } + } + return 0; +} + +} // namespace + +/* ------------------------------------------------------------------------- */ +/* Component init / deinit */ +/* ------------------------------------------------------------------------- */ + +static const char *k_func_name = "generate_series"; +static bool g_registered = false; + +static mysql_service_status_t init() { + if (mysql_service_table_function_registration->register_table_function( + k_func_name, gs_describe, gs_fill, /*cleanup=*/nullptr)) { + return 1; + } + g_registered = true; + return 0; +} + +static mysql_service_status_t deinit() { + if (!g_registered) return 0; + int was_present = 0; + if (mysql_service_table_function_registration->unregister_table_function( + k_func_name, &was_present)) { + return 1; // in use - keep the component loaded + } + g_registered = false; + return 0; +} + +BEGIN_COMPONENT_PROVIDES(generate_series) +END_COMPONENT_PROVIDES(); + +BEGIN_COMPONENT_REQUIRES(generate_series) +REQUIRES_SERVICE(table_function_registration), + REQUIRES_SERVICE(table_function_row_writer), + REQUIRES_SERVICE(table_function_args), + REQUIRES_SERVICE(mysql_runtime_error), END_COMPONENT_REQUIRES(); + +BEGIN_COMPONENT_METADATA(generate_series) +METADATA("mysql.author", "Oracle Corporation"), + METADATA("mysql.license", "GPL"), + METADATA("generate_series", "1"), END_COMPONENT_METADATA(); + +DECLARE_COMPONENT(generate_series, "mysql:generate_series") +init, deinit END_DECLARE_COMPONENT(); + +DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF(generate_series) + END_DECLARE_LIBRARY_COMPONENTS diff --git a/components/test/test_table_function.cc b/components/test/test_table_function.cc index b006b9636b1..6407381bd17 100644 --- a/components/test/test_table_function.cc +++ b/components/test/test_table_function.cc @@ -64,7 +64,7 @@ static constexpr unsigned int k_n_columns = namespace { -int demo_describe(void * /*thd_handle*/, unsigned int /*n_args*/, +int demo_describe(void * /*thd_handle*/, Tf_args_handle * /*args*/, const Tf_column_def **out_columns, unsigned int *out_n_columns, void **out_state) { *out_columns = k_columns; @@ -73,7 +73,8 @@ int demo_describe(void * /*thd_handle*/, unsigned int /*n_args*/, return 0; } -int demo_fill(void * /*state*/, Tf_row_writer_handle *w) { +int demo_fill(void * /*state*/, Tf_args_handle * /*args*/, + Tf_row_writer_handle *w) { /* Produce exactly 3 rows. Row index i in [1..3]; values are derived from i so the test result is fully deterministic. diff --git a/include/mysql/components/services/table_function_registration.h b/include/mysql/components/services/table_function_registration.h index 7e2cd27a0d5..5bf3f79623e 100644 --- a/include/mysql/components/services/table_function_registration.h +++ b/include/mysql/components/services/table_function_registration.h @@ -63,6 +63,14 @@ typedef struct Tf_column_def Tf_column_def; */ typedef struct Tf_row_writer_handle Tf_row_writer_handle; +/** + Opaque argument-list handle. Passed to the describe and fill + callbacks. The component must use the @c table_function_args service + to read the call-time argument values (e.g. the 1 and 5 in + generate_series(1, 5)). +*/ +typedef struct Tf_args_handle Tf_args_handle; + /* ------------------------------------------------------------- */ /* Callback signatures */ /* ------------------------------------------------------------- */ @@ -80,7 +88,9 @@ typedef struct Tf_row_writer_handle Tf_row_writer_handle; it will be passed back to subsequent callbacks. @param thd_handle opaque THD pointer (currently unused by the demo) - @param n_args number of arguments passed to the table function + @param args opaque argument-list handle; use the + table_function_args service to read argument + values and count @param out_columns [out] pointer to component-owned column array @param out_n_columns [out] number of columns @param out_state [out] optional component-owned state pointer @@ -89,7 +99,7 @@ typedef struct Tf_row_writer_handle Tf_row_writer_handle; @retval !=0 failure (the component should raise an error via mysql_runtime_error service before returning) */ -typedef int (*Tf_describe_func)(void *thd_handle, unsigned int n_args, +typedef int (*Tf_describe_func)(void *thd_handle, Tf_args_handle *args, const Tf_column_def **out_columns, unsigned int *out_n_columns, void **out_state); @@ -98,16 +108,19 @@ typedef int (*Tf_describe_func)(void *thd_handle, unsigned int n_args, The callback is invoked exactly once per execution. It must call the @c table_function_row_writer service to set field values and emit - rows. + rows. Argument values may be re-read through @c args (they can change + between executions when they reference outer columns). @param state the state set by the describe callback + @param args opaque argument-list handle @param writer opaque row-writer handle to be passed to row-writer service methods @retval 0 success @retval !=0 failure */ -typedef int (*Tf_fill_func)(void *state, Tf_row_writer_handle *writer); +typedef int (*Tf_fill_func)(void *state, Tf_args_handle *args, + Tf_row_writer_handle *writer); /** Release per-statement state. Called after each query execution. @@ -177,6 +190,38 @@ DECLARE_BOOL_METHOD(emit_row, (Tf_row_writer_handle *w)); END_SERVICE_DEFINITION(table_function_row_writer) +/** + Helper service used by table-function components to read the values + of their call-time arguments, both in the describe callback (for + const arguments, to shape the schema) and in the fill callback. + + Argument indexes are zero-based. Values are coerced from the + underlying argument expression. +*/ +BEGIN_SERVICE_DEFINITION(table_function_args) + +/** Number of arguments passed to the table function. */ +DECLARE_BOOL_METHOD(arg_count, (Tf_args_handle *h, unsigned int *out_count)); + +/** Read argument @c idx as a signed integer. */ +DECLARE_BOOL_METHOD(get_longlong, (Tf_args_handle *h, unsigned int idx, + long long *out_value, int *out_is_null)); + +/** Read argument @c idx as a double. */ +DECLARE_BOOL_METHOD(get_double, (Tf_args_handle *h, unsigned int idx, + double *out_value, int *out_is_null)); + +/** + Read argument @c idx as a string. The returned pointer is owned by + the server and is valid only for the duration of the current + callback invocation; copy it if it must outlive the call. +*/ +DECLARE_BOOL_METHOD(get_string, (Tf_args_handle *h, unsigned int idx, + const char **out_str, unsigned int *out_length, + int *out_is_null)); + +END_SERVICE_DEFINITION(table_function_args) + #ifdef __cplusplus } // extern "C" #endif diff --git a/mysql-test/include/plugin.defs b/mysql-test/include/plugin.defs index a608efee1ce..d82b1e5a3ee 100644 --- a/mysql-test/include/plugin.defs +++ b/mysql-test/include/plugin.defs @@ -116,6 +116,7 @@ component_pfs_example_component_population plugin_output_directory no PFS_EX component_test_udf_registration plugin_output_directory no TEST_UDF_REGISTRATION component_test_table_function plugin_output_directory no TEST_TABLE_FUNCTION +component_generate_series plugin_output_directory no GENERATE_SERIES component_test_udf_services plugin_output_directory no TEST_UDF_SERVICES component_test_udf_services component_audit_api_message_emit plugin_output_directory no AUDIT_API_MESSAGE_EMIT component_audit_api_message_emit component_udf_reg_3_func plugin_output_directory no UDF_REG_3_FUNC component_udf_reg_3_func diff --git a/mysql-test/suite/test_table_function/inc/have_generate_series_component.inc b/mysql-test/suite/test_table_function/inc/have_generate_series_component.inc new file mode 100644 index 00000000000..bf2899ae029 --- /dev/null +++ b/mysql-test/suite/test_table_function/inc/have_generate_series_component.inc @@ -0,0 +1,11 @@ +disable_query_log; + +if (!$GENERATE_SERIES) { + --skip component requires the environment variable \$GENERATE_SERIES to be set (normally done by mtr), see the file plugin.defs +} + +if (`SELECT CONCAT('--plugin-dir=', REPLACE(@@plugin_dir, '\\\\', '/')) != '$GENERATE_SERIES_OPT/'`) { + --skip component_test requires that --plugin-dir is set to the component_test dir (the .opt file does not contain \$GENERATE_SERIES_OPT) +} + +enable_query_log; diff --git a/mysql-test/suite/test_table_function/r/generate_series.result b/mysql-test/suite/test_table_function/r/generate_series.result new file mode 100644 index 00000000000..e99574b4798 --- /dev/null +++ b/mysql-test/suite/test_table_function/r/generate_series.result @@ -0,0 +1,108 @@ +# ==================================================================== +# generate_series() table function (component-registered) +# ==================================================================== +# Not available before INSTALL. +SELECT * FROM generate_series(1, 5) AS t; +ERROR 42000: TABLE FUNCTION generate_series does not exist +INSTALL COMPONENT "file://component_generate_series"; +# +# 1. The canonical example: 1..5. +# +SELECT * FROM generate_series(1, 5) AS t; +value +1 +2 +3 +4 +5 +# +# 2. Custom positive step. +# +SELECT value FROM generate_series(1, 10, 2) AS t; +value +1 +3 +5 +7 +9 +# +# 3. Descending series with negative step. +# +SELECT value FROM generate_series(5, 1, -1) AS t; +value +5 +4 +3 +2 +1 +# +# 4. Single-element and empty series. +# +SELECT value FROM generate_series(7, 7) AS t; +value +7 +SELECT value FROM generate_series(5, 1) AS t; +value +# +# 5. Aggregation over the series. +# +SELECT COUNT(*) AS n, SUM(value) AS s, MIN(value) AS lo, MAX(value) AS hi +FROM generate_series(1, 100) AS t; +n s lo hi +100 5050 1 100 +# +# 6. JOIN a real table against the series. +# +CREATE TABLE t1 (id INT, label VARCHAR(16)); +INSERT INTO t1 VALUES (2, 'two'), (4, 'four'), (6, 'six'); +SELECT g.value, t1.label +FROM generate_series(1, 5) AS g LEFT JOIN t1 ON t1.id = g.value +ORDER BY g.value; +value label +1 NULL +2 two +3 NULL +4 four +5 NULL +DROP TABLE t1; +# +# 7. Series bounds coming from expressions. +# +SELECT value FROM generate_series(1 + 1, 2 * 3) AS t; +value +2 +3 +4 +5 +6 +# +# 8. Prepared statement with parameters. +# +PREPARE s FROM 'SELECT value FROM generate_series(?, ?) AS t'; +SET @a = 3, @b = 6; +EXECUTE s USING @a, @b; +value +3 +4 +5 +6 +SET @a = 10, @b = 12; +EXECUTE s USING @a, @b; +value +10 +11 +12 +DEALLOCATE PREPARE s; +# +# 9. Argument errors. +# +SELECT * FROM generate_series(1) AS t; +ERROR 42000: Incorrect parameter count in the call to native function 'generate_series' +SELECT * FROM generate_series(1, 2, 3, 4) AS t; +ERROR 42000: Incorrect parameter count in the call to native function 'generate_series' +SELECT * FROM generate_series(1, 10, 0) AS t; +ERROR HY000: Incorrect arguments to generate_series +UNINSTALL COMPONENT "file://component_generate_series"; +# Gone again after UNINSTALL. +SELECT * FROM generate_series(1, 5) AS t; +ERROR 42000: TABLE FUNCTION generate_series does not exist diff --git a/mysql-test/suite/test_table_function/t/generate_series-master.opt b/mysql-test/suite/test_table_function/t/generate_series-master.opt new file mode 100644 index 00000000000..c20edd01452 --- /dev/null +++ b/mysql-test/suite/test_table_function/t/generate_series-master.opt @@ -0,0 +1 @@ +$GENERATE_SERIES_OPT diff --git a/mysql-test/suite/test_table_function/t/generate_series.test b/mysql-test/suite/test_table_function/t/generate_series.test new file mode 100644 index 00000000000..a00a8e17660 --- /dev/null +++ b/mysql-test/suite/test_table_function/t/generate_series.test @@ -0,0 +1,79 @@ +--source ../inc/have_generate_series_component.inc + +--echo # ==================================================================== +--echo # generate_series() table function (component-registered) +--echo # ==================================================================== + +--echo # Not available before INSTALL. +--error ER_SP_DOES_NOT_EXIST +SELECT * FROM generate_series(1, 5) AS t; + +INSTALL COMPONENT "file://component_generate_series"; + +--echo # +--echo # 1. The canonical example: 1..5. +--echo # +SELECT * FROM generate_series(1, 5) AS t; + +--echo # +--echo # 2. Custom positive step. +--echo # +SELECT value FROM generate_series(1, 10, 2) AS t; + +--echo # +--echo # 3. Descending series with negative step. +--echo # +SELECT value FROM generate_series(5, 1, -1) AS t; + +--echo # +--echo # 4. Single-element and empty series. +--echo # +SELECT value FROM generate_series(7, 7) AS t; +SELECT value FROM generate_series(5, 1) AS t; + +--echo # +--echo # 5. Aggregation over the series. +--echo # +SELECT COUNT(*) AS n, SUM(value) AS s, MIN(value) AS lo, MAX(value) AS hi + FROM generate_series(1, 100) AS t; + +--echo # +--echo # 6. JOIN a real table against the series. +--echo # +CREATE TABLE t1 (id INT, label VARCHAR(16)); +INSERT INTO t1 VALUES (2, 'two'), (4, 'four'), (6, 'six'); +SELECT g.value, t1.label + FROM generate_series(1, 5) AS g LEFT JOIN t1 ON t1.id = g.value + ORDER BY g.value; +DROP TABLE t1; + +--echo # +--echo # 7. Series bounds coming from expressions. +--echo # +SELECT value FROM generate_series(1 + 1, 2 * 3) AS t; + +--echo # +--echo # 8. Prepared statement with parameters. +--echo # +PREPARE s FROM 'SELECT value FROM generate_series(?, ?) AS t'; +SET @a = 3, @b = 6; +EXECUTE s USING @a, @b; +SET @a = 10, @b = 12; +EXECUTE s USING @a, @b; +DEALLOCATE PREPARE s; + +--echo # +--echo # 9. Argument errors. +--echo # +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT * FROM generate_series(1) AS t; +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT * FROM generate_series(1, 2, 3, 4) AS t; +--error ER_WRONG_ARGUMENTS +SELECT * FROM generate_series(1, 10, 0) AS t; + +UNINSTALL COMPONENT "file://component_generate_series"; + +--echo # Gone again after UNINSTALL. +--error ER_SP_DOES_NOT_EXIST +SELECT * FROM generate_series(1, 5) AS t; diff --git a/sql/server_component/server_component.cc b/sql/server_component/server_component.cc index e7adf39179d..f12e3def45a 100644 --- a/sql/server_component/server_component.cc +++ b/sql/server_component/server_component.cc @@ -318,6 +318,12 @@ mysql_table_function_row_writer_imp::set_null, mysql_table_function_row_writer_imp::set_string, mysql_table_function_row_writer_imp::emit_row END_SERVICE_IMPLEMENTATION(); +BEGIN_SERVICE_IMPLEMENTATION(mysql_server, table_function_args) +mysql_table_function_args_imp::arg_count, + mysql_table_function_args_imp::get_longlong, + mysql_table_function_args_imp::get_double, + mysql_table_function_args_imp::get_string END_SERVICE_IMPLEMENTATION(); + /* Here the new mysql_service_mysql_system_variable_reader->get() service cannot be used instead of get_variable because the below code is for the @@ -964,6 +970,7 @@ PROVIDES_SERVICE(mysql_server_path_filter, dynamic_loader_scheme_file), PROVIDES_SERVICE(mysql_server, mysql_udf_metadata), PROVIDES_SERVICE(mysql_server, table_function_registration), PROVIDES_SERVICE(mysql_server, table_function_row_writer), + PROVIDES_SERVICE(mysql_server, table_function_args), PROVIDES_SERVICE(mysql_server, component_sys_variable_register), PROVIDES_SERVICE(mysql_server, component_sys_variable_unregister), PROVIDES_SERVICE(mysql_server, mysql_cond_v1), diff --git a/sql/server_component/table_function_registration_imp.cc b/sql/server_component/table_function_registration_imp.cc index b87ae1d7e54..ce90e28d53b 100644 --- a/sql/server_component/table_function_registration_imp.cc +++ b/sql/server_component/table_function_registration_imp.cc @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "field_types.h" #include "mysql/strings/m_ctype.h" #include "sql/field.h" +#include "sql/item.h" #include "sql/sql_class.h" #include "sql/table.h" #include "sql/table_function_dynamic.h" @@ -130,3 +131,69 @@ DEFINE_BOOL_METHOD(mysql_table_function_row_writer_imp::emit_row, reset_row(t); return false; } + +/* ------------------------------------------------------------------------- */ +/* Argument reader helper service */ +/* ------------------------------------------------------------------------- */ + +namespace { +inline Item *arg_item(Tf_args_handle *h, unsigned int idx) { + return reinterpret_cast(h)->arg_at(idx); +} +} // namespace + +DEFINE_BOOL_METHOD(mysql_table_function_args_imp::arg_count, + (Tf_args_handle * h, unsigned int *out_count)) { + if (h == nullptr || out_count == nullptr) return true; + *out_count = reinterpret_cast(h)->arg_count(); + return false; +} + +DEFINE_BOOL_METHOD(mysql_table_function_args_imp::get_longlong, + (Tf_args_handle * h, unsigned int idx, long long *out_value, + int *out_is_null)) { + Item *it = arg_item(h, idx); + if (it == nullptr || out_value == nullptr) return true; + const longlong v = it->val_int(); + if (current_thd->is_error()) return true; + if (out_is_null != nullptr) *out_is_null = it->null_value ? 1 : 0; + *out_value = static_cast(v); + return false; +} + +DEFINE_BOOL_METHOD(mysql_table_function_args_imp::get_double, + (Tf_args_handle * h, unsigned int idx, double *out_value, + int *out_is_null)) { + Item *it = arg_item(h, idx); + if (it == nullptr || out_value == nullptr) return true; + const double v = it->val_real(); + if (current_thd->is_error()) return true; + if (out_is_null != nullptr) *out_is_null = it->null_value ? 1 : 0; + *out_value = v; + return false; +} + +DEFINE_BOOL_METHOD(mysql_table_function_args_imp::get_string, + (Tf_args_handle * h, unsigned int idx, const char **out_str, + unsigned int *out_length, int *out_is_null)) { + Item *it = arg_item(h, idx); + if (it == nullptr || out_str == nullptr || out_length == nullptr) + return true; + StringBuffer buf; + String *s = it->val_str(&buf); + if (current_thd->is_error()) return true; + if (it->null_value || s == nullptr) { + if (out_is_null != nullptr) *out_is_null = 1; + *out_str = nullptr; + *out_length = 0; + return false; + } + if (out_is_null != nullptr) *out_is_null = 0; + /* + The returned pointer is valid for the duration of the current + callback invocation. We hand back the Item's own value buffer. + */ + *out_str = s->ptr(); + *out_length = static_cast(s->length()); + return false; +} diff --git a/sql/server_component/table_function_registration_imp.h b/sql/server_component/table_function_registration_imp.h index c23ec181bc7..2066f3418f6 100644 --- a/sql/server_component/table_function_registration_imp.h +++ b/sql/server_component/table_function_registration_imp.h @@ -59,4 +59,27 @@ class mysql_table_function_row_writer_imp { static DEFINE_BOOL_METHOD(emit_row, (Tf_row_writer_handle * w)); }; +/** + Implementation of the argument-reading helper service used by + table-function components to fetch call-time argument values. +*/ +class mysql_table_function_args_imp { + public: + static DEFINE_BOOL_METHOD(arg_count, + (Tf_args_handle * h, unsigned int *out_count)); + + static DEFINE_BOOL_METHOD(get_longlong, + (Tf_args_handle * h, unsigned int idx, + long long *out_value, int *out_is_null)); + + static DEFINE_BOOL_METHOD(get_double, + (Tf_args_handle * h, unsigned int idx, + double *out_value, int *out_is_null)); + + static DEFINE_BOOL_METHOD(get_string, + (Tf_args_handle * h, unsigned int idx, + const char **out_str, unsigned int *out_length, + int *out_is_null)); +}; + #endif // TABLE_FUNCTION_REGISTRATION_IMP_H diff --git a/sql/table_function_dynamic.cc b/sql/table_function_dynamic.cc index fcda902f7b9..74a55e5a8f1 100644 --- a/sql/table_function_dynamic.cc +++ b/sql/table_function_dynamic.cc @@ -247,8 +247,8 @@ bool Table_function_dynamic::init() { THD *thd = current_thd; const Tf_column_def *cols = nullptr; unsigned int n_cols = 0; - if (m_desc->describe(thd, static_cast(m_args->size()), &cols, - &n_cols, &m_state)) { + Tf_args_handle *args = reinterpret_cast(this); + if (m_desc->describe(thd, args, &cols, &n_cols, &m_state)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), m_desc->name.c_str()); return true; } @@ -377,8 +377,9 @@ bool Table_function_dynamic::fill_result_table() { */ Tf_row_writer_handle *writer = reinterpret_cast(this); + Tf_args_handle *args = reinterpret_cast(this); - if (m_desc->fill(m_state, writer)) { + if (m_desc->fill(m_state, args, writer)) { if (!current_thd->is_error()) my_error(ER_UNKNOWN_ERROR, MYF(0)); // best-effort return true; @@ -386,6 +387,11 @@ bool Table_function_dynamic::fill_result_table() { return false; } +Item *Table_function_dynamic::arg_at(unsigned int idx) const { + if (idx >= m_args->size()) return nullptr; + return (*m_args)[idx]; +} + table_map Table_function_dynamic::used_tables() const { table_map t = 0; for (Item *arg : *m_args) { diff --git a/sql/table_function_dynamic.h b/sql/table_function_dynamic.h index b41c1a9973f..8c0d517d165 100644 --- a/sql/table_function_dynamic.h +++ b/sql/table_function_dynamic.h @@ -162,6 +162,14 @@ class Table_function_dynamic final : public Table_function { /// in-memory -> on-disk overflow via Table_function::write_row(). bool emit_current_row() { return write_row(); } + /// Number of call-time arguments (for the table_function_args service). + unsigned int arg_count() const { + return static_cast(m_args->size()); + } + + /// Return the argument Item at @p idx, or nullptr if out of range. + Item *arg_at(unsigned int idx) const; + private: List *get_field_list() override { return &m_field_list; } bool do_init_args() override;