From 66d0210ad7140d0d90382abe99899ba76729e323 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Sun, 9 Aug 2026 15:27:45 +0100 Subject: [PATCH] feat: MDEV-40672 implement basic support for the pluggable aggregate functions --- include/mysql/plugin_function.h | 12 + include/mysql/plugin_function.h.pp | 1 + .../func_test/function_plugin.result | 203 +++++++++++ .../mysql-test/func_test/function_plugin.test | 151 +++++++++ .../func_test/function_plugin_extra.result | 282 +++++++++++++++ .../func_test/function_plugin_extra.test | 204 +++++++++++ .../func_test/function_plugin_negative.result | 221 ++++++++++++ .../func_test/function_plugin_negative.test | 228 +++++++++++++ plugin/func_test/plugin.cc | 320 ++++++++++++++++++ sql/item_create.cc | 25 +- sql/item_create.h | 12 +- sql/item_sum.cc | 184 +++++++++- sql/item_sum.h | 40 ++- sql/sql_schema.cc | 12 +- sql/sql_schema.h | 6 +- sql/sql_window.cc | 6 + sql/sql_yacc.yy | 132 +++++--- 17 files changed, 1969 insertions(+), 70 deletions(-) create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin.result create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin.test create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin_extra.result create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin_extra.test create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin_negative.result create mode 100644 plugin/func_test/mysql-test/func_test/function_plugin_negative.test diff --git a/include/mysql/plugin_function.h b/include/mysql/plugin_function.h index 117acd43222ef..22932dc2839cf 100644 --- a/include/mysql/plugin_function.h +++ b/include/mysql/plugin_function.h @@ -27,8 +27,20 @@ #include +class Create_func; + /* API for function plugins. (MariaDB_FUNCTION_PLUGIN) + + An aggregate function's Create_func must create an Item_sum_plugin descendant. + Aggregate implementations use the normal Item_sum lifecycle and + aggregation_arg() to read values, including values replayed by DISTINCT. They + can implement supports_removal() and remove() for moving window frames, and + val_native() to preserve a pluggable result type. + + The server keeps the function plugin and data type plugins referenced by an + Item_sum_plugin loaded for the lifetime of the prepared Item tree and its + execution copies. */ #define MariaDB_FUNCTION_INTERFACE_VERSION (MYSQL_VERSION_ID << 8) diff --git a/include/mysql/plugin_function.h.pp b/include/mysql/plugin_function.h.pp index dc231c9147154..ffc89631516d7 100644 --- a/include/mysql/plugin_function.h.pp +++ b/include/mysql/plugin_function.h.pp @@ -714,6 +714,7 @@ const void *ha_data); void thd_wakeup_subsequent_commits(THD* thd, int wakeup_error); } +class Create_func; class Plugin_function { int m_interface_version; diff --git a/plugin/func_test/mysql-test/func_test/function_plugin.result b/plugin/func_test/mysql-test/func_test/function_plugin.result new file mode 100644 index 0000000000000..88b4d5a7b8ccf --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin.result @@ -0,0 +1,203 @@ +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (1, 1), (2, 1), (NULL, 1), (3, 2); +SELECT test_plugin_count(a) FROM t1; +test_plugin_count(a) +3 +SELECT b, test_plugin_count(a) FROM t1 GROUP BY b ORDER BY b; +b test_plugin_count(a) +1 2 +2 1 +SELECT test_plugin_count(DISTINCT a) FROM +(SELECT 1 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT NULL) dt; +test_plugin_count(DISTINCT a) +2 +SELECT test_plugin_count(a) FROM t1 WHERE FALSE; +test_plugin_count(a) +0 +SELECT test_plugin_count(a) FROM t1 WHERE a IS NULL; +test_plugin_count(a) +0 +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT b, test_plugin_count(a) +FROM (SELECT a, b FROM t1) AS dt +GROUP BY b ORDER BY b; +b test_plugin_count(a) +1 2 +2 1 +SET optimizer_switch= @save_optimizer_switch; +PREPARE stmt FROM +'SELECT test_plugin_count(a) FROM t1 WHERE b = ?'; +SET @b= 1; +EXECUTE stmt USING @b; +test_plugin_count(a) +2 +SET @b= 2; +EXECUTE stmt USING @b; +test_plugin_count(a) +1 +SET @b= 3; +EXECUTE stmt USING @b; +test_plugin_count(a) +0 +DEALLOCATE PREPARE stmt; +SELECT a, test_plugin_count(a) OVER (ORDER BY a) FROM t1 ORDER BY a; +a test_plugin_count(a) OVER (ORDER BY a) +NULL 0 +1 1 +2 2 +3 3 +CREATE TABLE t_window (id INT, part INT, value INT); +INSERT INTO t_window VALUES +(1, 1, 10), (2, 1, NULL), (3, 1, 30), (4, 1, 40), +(5, 2, NULL), (6, 2, 60); +SELECT id, part, value, +test_plugin_count(value) OVER ( +PARTITION BY part ORDER BY id +ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS cnt +FROM t_window ORDER BY id; +id part value cnt +1 1 10 1 +2 1 NULL 2 +3 1 30 2 +4 1 40 2 +5 2 NULL 1 +6 2 60 1 +SELECT test_plugin_count(DISTINCT a) OVER () FROM t1; +ERROR 42000: This version of MariaDB doesn't yet support 'plugin aggregate with DISTINCT as window function' +SELECT sysconst_test() OVER (); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'OVER ()' at line 1 +SELECT strnxfrm(DISTINCT 'a', 1, 1, 0); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 +CREATE TABLE t_uuid (id INT, grp INT, value UUID); +INSERT INTO t_uuid VALUES +(1, 1, NULL), +(2, 1, '00000000-0000-0000-0000-000000000001'), +(3, 2, '00000000-0000-0000-0000-000000000002'), +(4, 2, NULL), +(5, 3, NULL); +SELECT test_plugin_first(value) FROM t_uuid WHERE grp = 1; +test_plugin_first(value) +00000000-0000-0000-0000-000000000001 +SELECT grp, test_plugin_first(value) FROM t_uuid GROUP BY grp ORDER BY grp; +grp test_plugin_first(value) +1 00000000-0000-0000-0000-000000000001 +2 00000000-0000-0000-0000-000000000002 +3 NULL +SELECT id, grp, test_plugin_first(value) OVER ( +PARTITION BY grp ORDER BY id +ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS value +FROM t_uuid ORDER BY id; +id grp value +1 1 00000000-0000-0000-0000-000000000001 +2 1 00000000-0000-0000-0000-000000000001 +3 2 00000000-0000-0000-0000-000000000002 +4 2 NULL +5 3 NULL +SELECT test_plugin_count(DISTINCT value) FROM t_uuid; +test_plugin_count(DISTINCT value) +2 +SELECT test_plugin_first(DISTINCT value) FROM +(SELECT value FROM t_uuid WHERE id = 2 +UNION ALL +SELECT value FROM t_uuid WHERE id = 2) AS dt; +test_plugin_first(DISTINCT value) +00000000-0000-0000-0000-000000000001 +SELECT test_plugin_first(DISTINCT value) = +(SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid WHERE grp = 1; +test_plugin_first(DISTINCT value) = +(SELECT value FROM t_uuid WHERE id = 2) +1 +SELECT test_plugin_first(value) IS NULL FROM t_uuid WHERE FALSE; +test_plugin_first(value) IS NULL +1 +SELECT test_plugin_first(value) IS NULL FROM t_uuid WHERE grp = 3; +test_plugin_first(value) IS NULL +1 +SELECT test_plugin_first(value) = +(SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid WHERE grp = 1; +test_plugin_first(value) = +(SELECT value FROM t_uuid WHERE id = 2) +1 +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT value, value = (SELECT value FROM t_uuid WHERE id = 3) +FROM +(SELECT test_plugin_first(value) AS value +FROM t_uuid WHERE grp = 2) AS dt; +value value = (SELECT value FROM t_uuid WHERE id = 3) +00000000-0000-0000-0000-000000000002 1 +SET optimizer_switch= @save_optimizer_switch; +CREATE TABLE t_uuid_aggregate AS +SELECT grp, test_plugin_first(value) AS value +FROM t_uuid GROUP BY grp; +SHOW CREATE TABLE t_uuid_aggregate; +Table Create Table +t_uuid_aggregate CREATE TABLE `t_uuid_aggregate` ( + `grp` int(11) DEFAULT NULL, + `value` uuid DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT * FROM t_uuid_aggregate ORDER BY grp; +grp value +1 00000000-0000-0000-0000-000000000001 +2 00000000-0000-0000-0000-000000000002 +3 NULL +CREATE TABLE t_uuid_result (value UUID); +INSERT INTO t_uuid_result +SELECT test_plugin_first(value) FROM t_uuid WHERE grp = 1; +SELECT value, value = (SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid_result; +value value = (SELECT value FROM t_uuid WHERE id = 2) +00000000-0000-0000-0000-000000000001 1 +CREATE TABLE t_blob (value TEXT); +INSERT INTO t_blob VALUES ('a'), ('a'), ('b'), (NULL); +SELECT test_plugin_count(DISTINCT value) FROM t_blob; +test_plugin_count(DISTINCT value) +2 +SELECT test_plugin_first(DISTINCT value) IS NOT NULL FROM t_blob; +test_plugin_first(DISTINCT value) IS NOT NULL +1 +INSTALL SONAME 'type_test'; +PREPARE type_stmt FROM +'SELECT test_plugin_first(CAST(1 AS test_int8))'; +UNINSTALL PLUGIN test_double; +UNINSTALL PLUGIN test_int8; +EXECUTE type_stmt; +test_plugin_first(CAST(1 AS test_int8)) +1 +DEALLOCATE PREPARE type_stmt; +INSTALL SONAME 'type_test'; +UNINSTALL SONAME 'type_test'; +DROP TABLE t_blob, t_uuid_result, t_uuid_aggregate, t_uuid, t_window; +SELECT GET_LOCK('function_plugin_lock', 10); +GET_LOCK('function_plugin_lock', 10) +1 +connect con1,localhost,root,,; +connection con1; +SELECT test_plugin_count(IF(GET_LOCK('function_plugin_lock', 10), a, a)) FROM t1; +connection default; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +SELECT RELEASE_LOCK('function_plugin_lock'); +RELEASE_LOCK('function_plugin_lock') +1 +connection con1; +test_plugin_count(IF(GET_LOCK('function_plugin_lock', 10), a, a)) +3 +disconnect con1; +connection default; +INSTALL SONAME 'func_test'; +PREPARE function_stmt FROM +'SELECT test_plugin_count(a) FROM t1'; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +EXECUTE function_stmt; +test_plugin_count(a) +3 +DEALLOCATE PREPARE function_stmt; +INSTALL SONAME 'func_test'; +DROP TABLE t1; diff --git a/plugin/func_test/mysql-test/func_test/function_plugin.test b/plugin/func_test/mysql-test/func_test/function_plugin.test new file mode 100644 index 0000000000000..4b010cdee4125 --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin.test @@ -0,0 +1,151 @@ +--source include/have_udf.inc + +--disable_query_log +if (`SELECT COUNT(*) FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME='test_plugin_count'`) +{ + UNINSTALL SONAME 'func_test'; +} +INSTALL SONAME 'func_test'; +--enable_query_log + +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (1, 1), (2, 1), (NULL, 1), (3, 2); + +SELECT test_plugin_count(a) FROM t1; +SELECT b, test_plugin_count(a) FROM t1 GROUP BY b ORDER BY b; +SELECT test_plugin_count(DISTINCT a) FROM + (SELECT 1 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT NULL) dt; +SELECT test_plugin_count(a) FROM t1 WHERE FALSE; +SELECT test_plugin_count(a) FROM t1 WHERE a IS NULL; + +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT b, test_plugin_count(a) +FROM (SELECT a, b FROM t1) AS dt +GROUP BY b ORDER BY b; +SET optimizer_switch= @save_optimizer_switch; + +PREPARE stmt FROM + 'SELECT test_plugin_count(a) FROM t1 WHERE b = ?'; +SET @b= 1; +EXECUTE stmt USING @b; +SET @b= 2; +EXECUTE stmt USING @b; +SET @b= 3; +EXECUTE stmt USING @b; +DEALLOCATE PREPARE stmt; + +SELECT a, test_plugin_count(a) OVER (ORDER BY a) FROM t1 ORDER BY a; + +CREATE TABLE t_window (id INT, part INT, value INT); +INSERT INTO t_window VALUES + (1, 1, 10), (2, 1, NULL), (3, 1, 30), (4, 1, 40), + (5, 2, NULL), (6, 2, 60); +SELECT id, part, value, + test_plugin_count(value) OVER ( + PARTITION BY part ORDER BY id + ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS cnt +FROM t_window ORDER BY id; + +--error ER_NOT_SUPPORTED_YET +SELECT test_plugin_count(DISTINCT a) OVER () FROM t1; + +--error ER_PARSE_ERROR +SELECT sysconst_test() OVER (); +--error ER_PARSE_ERROR +SELECT strnxfrm(DISTINCT 'a', 1, 1, 0); + +CREATE TABLE t_uuid (id INT, grp INT, value UUID); +INSERT INTO t_uuid VALUES + (1, 1, NULL), + (2, 1, '00000000-0000-0000-0000-000000000001'), + (3, 2, '00000000-0000-0000-0000-000000000002'), + (4, 2, NULL), + (5, 3, NULL); + +SELECT test_plugin_first(value) FROM t_uuid WHERE grp = 1; +SELECT grp, test_plugin_first(value) FROM t_uuid GROUP BY grp ORDER BY grp; +SELECT id, grp, test_plugin_first(value) OVER ( + PARTITION BY grp ORDER BY id + ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS value +FROM t_uuid ORDER BY id; +SELECT test_plugin_count(DISTINCT value) FROM t_uuid; +SELECT test_plugin_first(DISTINCT value) FROM + (SELECT value FROM t_uuid WHERE id = 2 + UNION ALL + SELECT value FROM t_uuid WHERE id = 2) AS dt; +SELECT test_plugin_first(DISTINCT value) = + (SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid WHERE grp = 1; +SELECT test_plugin_first(value) IS NULL FROM t_uuid WHERE FALSE; +SELECT test_plugin_first(value) IS NULL FROM t_uuid WHERE grp = 3; +SELECT test_plugin_first(value) = + (SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid WHERE grp = 1; + +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT value, value = (SELECT value FROM t_uuid WHERE id = 3) +FROM + (SELECT test_plugin_first(value) AS value + FROM t_uuid WHERE grp = 2) AS dt; +SET optimizer_switch= @save_optimizer_switch; + +CREATE TABLE t_uuid_aggregate AS + SELECT grp, test_plugin_first(value) AS value + FROM t_uuid GROUP BY grp; +SHOW CREATE TABLE t_uuid_aggregate; +SELECT * FROM t_uuid_aggregate ORDER BY grp; + +CREATE TABLE t_uuid_result (value UUID); +INSERT INTO t_uuid_result + SELECT test_plugin_first(value) FROM t_uuid WHERE grp = 1; +SELECT value, value = (SELECT value FROM t_uuid WHERE id = 2) +FROM t_uuid_result; + +CREATE TABLE t_blob (value TEXT); +INSERT INTO t_blob VALUES ('a'), ('a'), ('b'), (NULL); +SELECT test_plugin_count(DISTINCT value) FROM t_blob; +SELECT test_plugin_first(DISTINCT value) IS NOT NULL FROM t_blob; + +INSTALL SONAME 'type_test'; +PREPARE type_stmt FROM + 'SELECT test_plugin_first(CAST(1 AS test_int8))'; +UNINSTALL PLUGIN test_double; +UNINSTALL PLUGIN test_int8; +EXECUTE type_stmt; +DEALLOCATE PREPARE type_stmt; +INSTALL SONAME 'type_test'; +UNINSTALL SONAME 'type_test'; + +DROP TABLE t_blob, t_uuid_result, t_uuid_aggregate, t_uuid, t_window; +SELECT GET_LOCK('function_plugin_lock', 10); +--connect (con1,localhost,root,,) +--connection con1 +--send SELECT test_plugin_count(IF(GET_LOCK('function_plugin_lock', 10), a, a)) FROM t1 +--connection default +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE STATE = 'User lock' AND INFO LIKE 'SELECT test_plugin_count%'; +--source include/wait_condition.inc +UNINSTALL SONAME 'func_test'; +SELECT RELEASE_LOCK('function_plugin_lock'); +--connection con1 +--reap +--disconnect con1 +--connection default +INSTALL SONAME 'func_test'; + +PREPARE function_stmt FROM + 'SELECT test_plugin_count(a) FROM t1'; +UNINSTALL SONAME 'func_test'; +EXECUTE function_stmt; +DEALLOCATE PREPARE function_stmt; +INSTALL SONAME 'func_test'; +DROP TABLE t1; +--disable_query_log +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +DELETE FROM mysql.plugin WHERE dl='func_test.so'; +--enable_query_log diff --git a/plugin/func_test/mysql-test/func_test/function_plugin_extra.result b/plugin/func_test/mysql-test/func_test/function_plugin_extra.result new file mode 100644 index 0000000000000..52b10e0972fa2 --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin_extra.result @@ -0,0 +1,282 @@ +SELECT test_plugin_count(); +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_count' +SELECT test_plugin_count(1, 2); +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_count' +SELECT test_plugin_first(); +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_first' +CREATE TABLE t_group (g INT, v INT); +INSERT INTO t_group VALUES +(1, NULL), (1, NULL), +(2, 10), (2, NULL), +(3, 20), (3, 30); +SELECT * FROM t_group WHERE test_plugin_count(v) > 0; +ERROR HY000: Invalid use of group function +SELECT test_plugin_count(test_plugin_count(v)) FROM t_group; +ERROR HY000: Invalid use of group function +SELECT test_plugin_count(v) FROM t_group; +test_plugin_count(v) +3 +SELECT test_plugin_count(v) FROM t_group +HAVING test_plugin_count(v) > 0; +test_plugin_count(v) +3 +SELECT g, test_plugin_count(v) AS c +FROM t_group GROUP BY g HAVING c > 0 ORDER BY c, g; +g c +2 1 +3 2 +SELECT test_plugin_count(v), test_plugin_count(v), +test_plugin_count(v + 1), test_plugin_first(v) +FROM t_group; +test_plugin_count(v) test_plugin_count(v) test_plugin_count(v + 1) test_plugin_first(v) +3 3 3 10 +SELECT g, test_plugin_count(v), test_plugin_first(v) +FROM t_group GROUP BY g ORDER BY g; +g test_plugin_count(v) test_plugin_first(v) +1 0 NULL +2 1 10 +3 2 20 +SELECT g, test_plugin_count(v) +FROM t_group GROUP BY g WITH ROLLUP; +g test_plugin_count(v) +1 0 +2 1 +3 2 +NULL 3 +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT * FROM +(SELECT g, test_plugin_count(v) AS c +FROM t_group GROUP BY g) AS dt +ORDER BY g; +g c +1 0 +2 1 +3 2 +WITH cte AS +(SELECT g, test_plugin_count(v) AS c +FROM t_group GROUP BY g) +SELECT * FROM cte ORDER BY g; +g c +1 0 +2 1 +3 2 +SET optimizer_switch= @save_optimizer_switch; +SELECT g, test_plugin_count(DISTINCT v) +FROM t_group GROUP BY g ORDER BY g; +g test_plugin_count(DISTINCT v) +1 0 +2 1 +3 2 +SELECT test_plugin_count(DISTINCT v) FROM t_group WHERE v IS NULL; +test_plugin_count(DISTINCT v) +0 +SELECT test_plugin_first(DISTINCT v) IS NULL FROM t_group WHERE v IS NULL; +test_plugin_first(DISTINCT v) IS NULL +1 +SELECT test_plugin_count(DISTINCT v) FROM t_group WHERE FALSE; +test_plugin_count(DISTINCT v) +0 +SELECT test_plugin_first(DISTINCT v) IS NULL FROM t_group WHERE FALSE; +test_plugin_first(DISTINCT v) IS NULL +1 +CREATE TABLE t_blob (value LONGTEXT); +INSERT INTO t_blob VALUES +(REPEAT('a', 10000)), (REPEAT('a', 10000)), +(REPEAT('b', 10000)), (NULL); +SELECT test_plugin_count(DISTINCT value) FROM t_blob; +test_plugin_count(DISTINCT value) +2 +SELECT LENGTH(test_plugin_first(DISTINCT value)) FROM t_blob; +LENGTH(test_plugin_first(DISTINCT value)) +10000 +SET @save_tmp_table_size= @@tmp_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +SET tmp_table_size= 16384; +SET max_heap_table_size= 16384; +CREATE TABLE t_spill AS +WITH RECURSIVE seq AS +(SELECT 1 AS value UNION ALL +SELECT value + 1 FROM seq WHERE value < 1000) +SELECT value FROM seq; +INSERT INTO t_spill SELECT value FROM t_spill; +SELECT test_plugin_count(DISTINCT value) FROM t_spill; +test_plugin_count(DISTINCT value) +1000 +DROP TABLE t_spill; +SET tmp_table_size= @save_tmp_table_size; +SET max_heap_table_size= @save_max_heap_table_size; +INSTALL SONAME 'type_test'; +CREATE TABLE t_type (g INT, i test_int8, d test_double); +INSERT INTO t_type VALUES +(1, 10, 1.5), (1, 20, 2.5), (2, NULL, NULL); +SELECT g, test_plugin_first(i), test_plugin_first(d) +FROM t_type GROUP BY g ORDER BY g; +g test_plugin_first(i) test_plugin_first(d) +1 10 1.5 +2 NULL NULL +CREATE TABLE type_int_result AS +SELECT test_plugin_first(i) AS value FROM t_type; +SHOW CREATE TABLE type_int_result; +Table Create Table +type_int_result CREATE TABLE `type_int_result` ( + `value` test_int8(20) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE VIEW type_view AS +SELECT test_plugin_first(i) AS value FROM t_type; +SHOW CREATE VIEW type_view; +View Create View character_set_client collation_connection +type_view CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `type_view` AS select test_plugin_first(`t_type`.`i`) AS `value` from `t_type` latin1 latin1_swedish_ci +SELECT * FROM type_view; +value +10 +CREATE TEMPORARY TABLE type_tmp AS +SELECT g, test_plugin_first(i) AS value +FROM t_type GROUP BY g; +SHOW CREATE TABLE type_tmp; +Table Create Table +type_tmp CREATE TEMPORARY TABLE `type_tmp` ( + `g` int(11) DEFAULT NULL, + `value` test_int8(20) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT * FROM type_tmp ORDER BY g; +g value +1 10 +2 NULL +CREATE TABLE type_dst (value test_int8); +INSERT INTO type_dst SELECT test_plugin_first(i) FROM t_type; +SELECT * FROM type_dst; +value +10 +DROP VIEW type_view; +DROP TEMPORARY TABLE type_tmp; +DROP TABLE type_dst, type_int_result, t_type; +UNINSTALL SONAME 'type_test'; +CREATE TABLE t_uuid (id INT, g INT, value UUID); +INSERT INTO t_uuid VALUES +(1, 1, '00000000-0000-0000-0000-000000000001'), +(2, 1, '00000000-0000-0000-0000-000000000001'), +(3, 1, '00000000-0000-0000-0000-000000000002'), +(4, 2, NULL), +(5, 2, '00000000-0000-0000-0000-000000000003'); +SELECT test_plugin_count(DISTINCT value) FROM t_uuid; +test_plugin_count(DISTINCT value) +3 +SELECT test_plugin_first(value) = +(SELECT value FROM t_uuid WHERE id = 1) +FROM t_uuid WHERE g = 1; +test_plugin_first(value) = +(SELECT value FROM t_uuid WHERE id = 1) +1 +CREATE TABLE uuid_union AS +SELECT test_plugin_first(value) AS value FROM t_uuid +UNION ALL +SELECT value FROM t_uuid WHERE id = 1; +SHOW CREATE TABLE uuid_union; +Table Create Table +uuid_union CREATE TABLE `uuid_union` ( + `value` uuid DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT CASE WHEN 1 THEN test_plugin_first(value) +ELSE CAST(NULL AS UUID) END AS value +FROM t_uuid; +value +00000000-0000-0000-0000-000000000001 +CREATE TABLE t_window (id INT, g INT, v INT, u UUID); +INSERT INTO t_window VALUES +(1, 1, 10, '00000000-0000-0000-0000-000000000001'), +(2, 1, NULL, NULL), +(3, 1, 30, '00000000-0000-0000-0000-000000000003'), +(4, 1, 40, '00000000-0000-0000-0000-000000000004'), +(5, 2, NULL, NULL), +(6, 2, 60, '00000000-0000-0000-0000-000000000006'); +SELECT id, +test_plugin_count(v) OVER ( +PARTITION BY g ORDER BY id +ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS preceding_count, +test_plugin_count(v) OVER ( +PARTITION BY g ORDER BY id +ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS following_count, +test_plugin_count(v) OVER ( +PARTITION BY g ORDER BY id +ROWS BETWEEN 2 FOLLOWING AND 3 FOLLOWING) AS empty_count +FROM t_window ORDER BY id; +id preceding_count following_count empty_count +1 1 2 2 +2 1 2 1 +3 2 2 0 +4 2 1 0 +5 0 1 0 +6 1 1 0 +SELECT id, +test_plugin_count(v) OVER w AS c1, +test_plugin_count(v) OVER w AS c2, +test_plugin_first(u) OVER w AS first_uuid +FROM t_window +WINDOW w AS ( +PARTITION BY g ORDER BY id +ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) +ORDER BY id; +id c1 c2 first_uuid +1 1 1 00000000-0000-0000-0000-000000000001 +2 2 2 00000000-0000-0000-0000-000000000001 +3 2 2 00000000-0000-0000-0000-000000000003 +4 2 2 00000000-0000-0000-0000-000000000003 +5 1 1 00000000-0000-0000-0000-000000000006 +6 1 1 00000000-0000-0000-0000-000000000006 +CREATE TABLE window_uuid_result AS +SELECT test_plugin_first(u) OVER ( +PARTITION BY g ORDER BY id +ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS value +FROM t_window; +SHOW CREATE TABLE window_uuid_result; +Table Create Table +window_uuid_result CREATE TABLE `window_uuid_result` ( + `value` uuid DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT test_plugin_first(DISTINCT u) OVER () FROM t_window; +ERROR 42000: This version of MariaDB doesn't yet support 'plugin aggregate with DISTINCT as window function' +PREPARE reprepare_stmt FROM +'SELECT test_plugin_count(v) FROM t_group'; +EXECUTE reprepare_stmt; +test_plugin_count(v) +3 +ALTER TABLE t_group ADD COLUMN extra INT; +EXECUTE reprepare_stmt; +test_plugin_count(v) +3 +DEALLOCATE PREPARE reprepare_stmt; +PREPARE bad_stmt FROM 'SELECT test_plugin_count()'; +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_count' +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +PREPARE s1 FROM 'SELECT test_plugin_count(v) FROM t_group'; +PREPARE s2 FROM 'SELECT test_plugin_first(v) FROM t_group'; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +Warning 1620 Plugin is busy and will be uninstalled on shutdown +EXECUTE s1; +test_plugin_count(v) +3 +EXECUTE s2; +test_plugin_first(v) +10 +DEALLOCATE PREPARE s1; +EXECUTE s2; +test_plugin_first(v) +10 +DEALLOCATE PREPARE s2; +INSTALL SONAME 'func_test'; +connect con1,localhost,root,,; +connection con1; +PREPARE close_stmt FROM +'SELECT test_plugin_count(v) FROM t_group'; +connection default; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +disconnect con1; +connection default; +INSTALL SONAME 'func_test'; +DROP TABLE window_uuid_result, t_window, uuid_union, t_uuid, t_blob, t_group; diff --git a/plugin/func_test/mysql-test/func_test/function_plugin_extra.test b/plugin/func_test/mysql-test/func_test/function_plugin_extra.test new file mode 100644 index 0000000000000..2550db1e6aa8b --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin_extra.test @@ -0,0 +1,204 @@ +--source include/have_udf.inc + +--disable_query_log +if (`SELECT COUNT(*) FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME='test_plugin_count'`) +{ + UNINSTALL SONAME 'func_test'; +} +INSTALL SONAME 'func_test'; +--enable_query_log + +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT test_plugin_count(); +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT test_plugin_count(1, 2); +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT test_plugin_first(); + +CREATE TABLE t_group (g INT, v INT); +INSERT INTO t_group VALUES + (1, NULL), (1, NULL), + (2, 10), (2, NULL), + (3, 20), (3, 30); + +--error ER_INVALID_GROUP_FUNC_USE +SELECT * FROM t_group WHERE test_plugin_count(v) > 0; +--error ER_INVALID_GROUP_FUNC_USE +SELECT test_plugin_count(test_plugin_count(v)) FROM t_group; + +SELECT test_plugin_count(v) FROM t_group; +SELECT test_plugin_count(v) FROM t_group +HAVING test_plugin_count(v) > 0; +SELECT g, test_plugin_count(v) AS c +FROM t_group GROUP BY g HAVING c > 0 ORDER BY c, g; +SELECT test_plugin_count(v), test_plugin_count(v), + test_plugin_count(v + 1), test_plugin_first(v) +FROM t_group; +SELECT g, test_plugin_count(v), test_plugin_first(v) +FROM t_group GROUP BY g ORDER BY g; +SELECT g, test_plugin_count(v) +FROM t_group GROUP BY g WITH ROLLUP; + +SET @save_optimizer_switch= @@optimizer_switch; +SET optimizer_switch= 'derived_merge=off'; +SELECT * FROM + (SELECT g, test_plugin_count(v) AS c + FROM t_group GROUP BY g) AS dt +ORDER BY g; +WITH cte AS + (SELECT g, test_plugin_count(v) AS c + FROM t_group GROUP BY g) +SELECT * FROM cte ORDER BY g; +SET optimizer_switch= @save_optimizer_switch; + +SELECT g, test_plugin_count(DISTINCT v) +FROM t_group GROUP BY g ORDER BY g; +SELECT test_plugin_count(DISTINCT v) FROM t_group WHERE v IS NULL; +SELECT test_plugin_first(DISTINCT v) IS NULL FROM t_group WHERE v IS NULL; +SELECT test_plugin_count(DISTINCT v) FROM t_group WHERE FALSE; +SELECT test_plugin_first(DISTINCT v) IS NULL FROM t_group WHERE FALSE; + +CREATE TABLE t_blob (value LONGTEXT); +INSERT INTO t_blob VALUES + (REPEAT('a', 10000)), (REPEAT('a', 10000)), + (REPEAT('b', 10000)), (NULL); +SELECT test_plugin_count(DISTINCT value) FROM t_blob; +SELECT LENGTH(test_plugin_first(DISTINCT value)) FROM t_blob; + +SET @save_tmp_table_size= @@tmp_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +SET tmp_table_size= 16384; +SET max_heap_table_size= 16384; +CREATE TABLE t_spill AS + WITH RECURSIVE seq AS + (SELECT 1 AS value UNION ALL + SELECT value + 1 FROM seq WHERE value < 1000) + SELECT value FROM seq; +INSERT INTO t_spill SELECT value FROM t_spill; +SELECT test_plugin_count(DISTINCT value) FROM t_spill; +DROP TABLE t_spill; +SET tmp_table_size= @save_tmp_table_size; +SET max_heap_table_size= @save_max_heap_table_size; + +INSTALL SONAME 'type_test'; +CREATE TABLE t_type (g INT, i test_int8, d test_double); +INSERT INTO t_type VALUES + (1, 10, 1.5), (1, 20, 2.5), (2, NULL, NULL); +SELECT g, test_plugin_first(i), test_plugin_first(d) +FROM t_type GROUP BY g ORDER BY g; +CREATE TABLE type_int_result AS + SELECT test_plugin_first(i) AS value FROM t_type; +SHOW CREATE TABLE type_int_result; +CREATE VIEW type_view AS + SELECT test_plugin_first(i) AS value FROM t_type; +SHOW CREATE VIEW type_view; +SELECT * FROM type_view; +CREATE TEMPORARY TABLE type_tmp AS + SELECT g, test_plugin_first(i) AS value + FROM t_type GROUP BY g; +SHOW CREATE TABLE type_tmp; +SELECT * FROM type_tmp ORDER BY g; +CREATE TABLE type_dst (value test_int8); +INSERT INTO type_dst SELECT test_plugin_first(i) FROM t_type; +SELECT * FROM type_dst; +DROP VIEW type_view; +DROP TEMPORARY TABLE type_tmp; +DROP TABLE type_dst, type_int_result, t_type; +UNINSTALL SONAME 'type_test'; + +CREATE TABLE t_uuid (id INT, g INT, value UUID); +INSERT INTO t_uuid VALUES + (1, 1, '00000000-0000-0000-0000-000000000001'), + (2, 1, '00000000-0000-0000-0000-000000000001'), + (3, 1, '00000000-0000-0000-0000-000000000002'), + (4, 2, NULL), + (5, 2, '00000000-0000-0000-0000-000000000003'); +SELECT test_plugin_count(DISTINCT value) FROM t_uuid; +SELECT test_plugin_first(value) = + (SELECT value FROM t_uuid WHERE id = 1) +FROM t_uuid WHERE g = 1; +CREATE TABLE uuid_union AS + SELECT test_plugin_first(value) AS value FROM t_uuid + UNION ALL + SELECT value FROM t_uuid WHERE id = 1; +SHOW CREATE TABLE uuid_union; +SELECT CASE WHEN 1 THEN test_plugin_first(value) + ELSE CAST(NULL AS UUID) END AS value +FROM t_uuid; + +CREATE TABLE t_window (id INT, g INT, v INT, u UUID); +INSERT INTO t_window VALUES + (1, 1, 10, '00000000-0000-0000-0000-000000000001'), + (2, 1, NULL, NULL), + (3, 1, 30, '00000000-0000-0000-0000-000000000003'), + (4, 1, 40, '00000000-0000-0000-0000-000000000004'), + (5, 2, NULL, NULL), + (6, 2, 60, '00000000-0000-0000-0000-000000000006'); +SELECT id, + test_plugin_count(v) OVER ( + PARTITION BY g ORDER BY id + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS preceding_count, + test_plugin_count(v) OVER ( + PARTITION BY g ORDER BY id + ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS following_count, + test_plugin_count(v) OVER ( + PARTITION BY g ORDER BY id + ROWS BETWEEN 2 FOLLOWING AND 3 FOLLOWING) AS empty_count +FROM t_window ORDER BY id; +SELECT id, + test_plugin_count(v) OVER w AS c1, + test_plugin_count(v) OVER w AS c2, + test_plugin_first(u) OVER w AS first_uuid +FROM t_window +WINDOW w AS ( + PARTITION BY g ORDER BY id + ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) +ORDER BY id; +CREATE TABLE window_uuid_result AS +SELECT test_plugin_first(u) OVER ( + PARTITION BY g ORDER BY id + ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS value +FROM t_window; +SHOW CREATE TABLE window_uuid_result; +--error ER_NOT_SUPPORTED_YET +SELECT test_plugin_first(DISTINCT u) OVER () FROM t_window; + +PREPARE reprepare_stmt FROM + 'SELECT test_plugin_count(v) FROM t_group'; +EXECUTE reprepare_stmt; +ALTER TABLE t_group ADD COLUMN extra INT; +EXECUTE reprepare_stmt; +DEALLOCATE PREPARE reprepare_stmt; + +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +PREPARE bad_stmt FROM 'SELECT test_plugin_count()'; +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; + +PREPARE s1 FROM 'SELECT test_plugin_count(v) FROM t_group'; +PREPARE s2 FROM 'SELECT test_plugin_first(v) FROM t_group'; +UNINSTALL SONAME 'func_test'; +EXECUTE s1; +EXECUTE s2; +DEALLOCATE PREPARE s1; +EXECUTE s2; +DEALLOCATE PREPARE s2; +INSTALL SONAME 'func_test'; + +--connect (con1,localhost,root,,) +--connection con1 +PREPARE close_stmt FROM + 'SELECT test_plugin_count(v) FROM t_group'; +--connection default +UNINSTALL SONAME 'func_test'; +--disconnect con1 +--connection default +INSTALL SONAME 'func_test'; + +DROP TABLE window_uuid_result, t_window, uuid_union, t_uuid, t_blob, t_group; +--disable_query_log +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +DELETE FROM mysql.plugin WHERE dl='func_test.so'; +--enable_query_log diff --git a/plugin/func_test/mysql-test/func_test/function_plugin_negative.result b/plugin/func_test/mysql-test/func_test/function_plugin_negative.result new file mode 100644 index 0000000000000..ab79f071285d9 --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin_negative.result @@ -0,0 +1,221 @@ +# +# Setup +# +CREATE TABLE t1 (id INT PRIMARY KEY, g INT, v INT, u UUID); +INSERT INTO t1 VALUES +(1, 1, 10, '00000000-0000-0000-0000-000000000001'), +(2, 1, NULL, NULL), +(3, 2, 30, '00000000-0000-0000-0000-000000000003'); +# +# Aggregate functions in forbidden SQL contexts +# +CREATE TABLE bad_check (v INT, CHECK (test_plugin_count(v) > 0)); +ERROR HY000: Function or expression 'test_plugin_count()' cannot be used in the CHECK clause of `CONSTRAINT_1` +CREATE TABLE bad_default (v INT DEFAULT (test_plugin_count(1))); +ERROR HY000: Function or expression 'test_plugin_count()' cannot be used in the DEFAULT clause of `v` +CREATE TABLE bad_generated +(v INT, c INT AS (test_plugin_count(v))); +ERROR HY000: Function or expression 'test_plugin_count()' cannot be used in the GENERATED ALWAYS AS clause of `c` +SELECT test_plugin_count(SUM(v)) FROM t1; +ERROR HY000: Invalid use of group function +SELECT SUM(test_plugin_count(v)) FROM t1; +ERROR HY000: Invalid use of group function +SELECT test_plugin_first(test_plugin_count(v)) FROM t1; +ERROR HY000: Invalid use of group function +SELECT test_plugin_count(v) FROM t1; +test_plugin_count(v) +2 +# +# Invalid window usage +# +SELECT * FROM t1 WHERE test_plugin_count(v) OVER () > 0; +ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause +SELECT test_plugin_count(test_plugin_count(v) OVER ()) FROM t1; +ERROR HY000: Window functions can not be used as arguments to group functions. +SELECT test_plugin_count(DISTINCT v) OVER ( +ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +FROM t1; +ERROR 42000: This version of MariaDB doesn't yet support 'plugin aggregate with DISTINCT as window function' +SELECT test_plugin_first(DISTINCT u) OVER ( +PARTITION BY g ORDER BY id) +FROM t1; +ERROR 42000: This version of MariaDB doesn't yet support 'plugin aggregate with DISTINCT as window function' +SELECT test_plugin_count(v) OVER (ORDER BY id) FROM t1; +test_plugin_count(v) OVER (ORDER BY id) +1 +1 +2 +# +# Invalid argument lists +# +SELECT test_plugin_count(DISTINCT) FROM t1; +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_count' +SELECT test_plugin_count(DISTINCT v, g) FROM t1; +ERROR 42000: Incorrect parameter count in the call to native function 'test_plugin_count' +SELECT sysconst_test(DISTINCT 1); +ERROR 42000: Incorrect parameter count in the call to native function 'sysconst_test' +SELECT test_plugin_count(DISTINCT v) FROM t1; +test_plugin_count(DISTINCT v) +2 +# +# Invalid pluggable data type values and missing type plugin +# +SET @save_sql_mode= @@sql_mode; +SET sql_mode= 'STRICT_ALL_TABLES'; +INSERT INTO t1 VALUES (4, 2, 40, 'not-a-uuid'); +ERROR 22007: Incorrect uuid value: 'not-a-uuid' for column `test`.`t1`.`u` at row 1 +SELECT test_plugin_first(CAST('not-a-uuid' AS UUID)) IS NULL; +test_plugin_first(CAST('not-a-uuid' AS UUID)) IS NULL +1 +Warnings: +Warning 1292 Incorrect uuid value: 'not-a-uuid' +SHOW WARNINGS; +Level Code Message +Warning 1292 Incorrect uuid value: 'not-a-uuid' +SET sql_mode= @save_sql_mode; +INSTALL SONAME 'type_test'; +UNINSTALL SONAME 'type_test'; +PREPARE missing_type_stmt FROM +'SELECT test_plugin_first(CAST(1 AS test_int8))'; +ERROR HY000: Unknown data type: 'test_int8' +SELECT test_plugin_count(v) FROM t1; +test_plugin_count(v) +2 +# +# Function plugin unavailable and marked for deferred unload +# +UNINSTALL SONAME 'func_test'; +SELECT test_plugin_count(v) FROM t1; +ERROR 42000: FUNCTION test.test_plugin_count does not exist +INSTALL SONAME 'func_test'; +PREPARE deleted_stmt FROM +'SELECT test_plugin_count(v) FROM t1'; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +PREPARE deleted_stmt2 FROM +'SELECT test_plugin_count(v) FROM t1'; +EXECUTE deleted_stmt; +test_plugin_count(v) +2 +EXECUTE deleted_stmt2; +test_plugin_count(v) +2 +DEALLOCATE PREPARE deleted_stmt; +DEALLOCATE PREPARE deleted_stmt2; +INSTALL SONAME 'func_test'; +# +# Errors while storing aggregate results +# +CREATE TABLE uuid_dst (value UUID PRIMARY KEY); +INSERT INTO uuid_dst +SELECT test_plugin_first(u) FROM t1 WHERE g = 1; +INSERT INTO uuid_dst +SELECT test_plugin_first(u) FROM t1 WHERE g = 1; +ERROR 23000: Duplicate entry '00000000-0000-0000-0000-000000000001' for key 'PRIMARY' +CREATE TABLE not_null_dst (value INT NOT NULL); +INSERT INTO not_null_dst +SELECT test_plugin_first(v) FROM t1 WHERE FALSE; +ERROR 23000: Column 'value' cannot be null +SELECT test_plugin_count(v) FROM t1; +test_plugin_count(v) +2 +# +# Statement timeout releases execution references +# +SET STATEMENT max_statement_time=0.01 FOR +SELECT test_plugin_count(IF(SLEEP(0.1), v, v)) FROM t1; +ERROR 70100: Query was interrupted: execution time limit 0.01 sec exceeded +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +# +# KILL QUERY during aggregate execution and deferred unload +# +SELECT GET_LOCK('function_plugin_negative_lock', 10); +GET_LOCK('function_plugin_negative_lock', 10) +1 +connect kill_con,localhost,root,,; +connection kill_con; +SELECT test_plugin_count(IF(GET_LOCK('function_plugin_negative_lock', 10), v, v)) FROM t1; +connection default; +UNINSTALL SONAME 'func_test'; +Warnings: +Warning 1620 Plugin is busy and will be uninstalled on shutdown +SELECT RELEASE_LOCK('function_plugin_negative_lock'); +RELEASE_LOCK('function_plugin_negative_lock') +1 +connection kill_con; +ERROR 70100: Query execution was interrupted +disconnect kill_con; +connection default; +INSTALL SONAME 'func_test'; +# +# Failed automatic reprepare +# +PREPARE reprepare_drop FROM +'SELECT test_plugin_count(v) FROM t1'; +ALTER TABLE t1 DROP COLUMN v; +EXECUTE reprepare_drop; +ERROR 42S22: Unknown column 'v' in 'SELECT' +DEALLOCATE PREPARE reprepare_drop; +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +ALTER TABLE t1 ADD COLUMN v INT; +# +# Stored objects after plugin unload +# +CREATE VIEW aggregate_view AS +SELECT test_plugin_count(v) AS c FROM t1; +UNINSTALL SONAME 'func_test'; +SELECT * FROM aggregate_view; +ERROR HY000: View 'test.aggregate_view' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them +INSTALL SONAME 'func_test'; +DROP VIEW aggregate_view; +CREATE PROCEDURE aggregate_proc() +BEGIN +SELECT test_plugin_count(v) FROM t1; +END| +UNINSTALL SONAME 'func_test'; +CALL aggregate_proc(); +ERROR 42000: FUNCTION test.test_plugin_count does not exist +INSTALL SONAME 'func_test'; +DROP PROCEDURE aggregate_proc; +CREATE TRIGGER aggregate_trigger BEFORE INSERT ON t1 +FOR EACH ROW SET NEW.v= test_plugin_count(NEW.v); +UNINSTALL SONAME 'func_test'; +INSERT INTO t1 (id, g, v, u) VALUES (4, 2, 40, NULL); +ERROR 42000: FUNCTION test.test_plugin_count does not exist +INSTALL SONAME 'func_test'; +DROP TRIGGER aggregate_trigger; +# +# Privilege errors do not change plugin state +# +CREATE USER plugin_user@localhost; +connect priv_con,localhost,plugin_user,,; +connection priv_con; +INSTALL SONAME 'func_test'; +ERROR 42000: INSERT command denied to user 'plugin_user'@'localhost' for table `mysql`.`plugin` +UNINSTALL SONAME 'func_test'; +ERROR 42000: DELETE command denied to user 'plugin_user'@'localhost' for table `mysql`.`plugin` +disconnect priv_con; +connection default; +DROP USER plugin_user@localhost; +SELECT test_plugin_count(v) FROM t1; +test_plugin_count(v) +0 +# +# Repeated result access and cleanup +# +SELECT test_plugin_first(u), +test_plugin_first(u) IS NULL, +CAST(test_plugin_first(u) AS CHAR) +FROM t1; +test_plugin_first(u) test_plugin_first(u) IS NULL CAST(test_plugin_first(u) AS CHAR) +00000000-0000-0000-0000-000000000001 0 00000000-0000-0000-0000-000000000001 +SELECT test_plugin_count(v) AS c +FROM t1 HAVING c > 0 ORDER BY c; +c +# +# Cleanup +# +DROP TABLE not_null_dst, uuid_dst, t1; diff --git a/plugin/func_test/mysql-test/func_test/function_plugin_negative.test b/plugin/func_test/mysql-test/func_test/function_plugin_negative.test new file mode 100644 index 0000000000000..9219652edd3ac --- /dev/null +++ b/plugin/func_test/mysql-test/func_test/function_plugin_negative.test @@ -0,0 +1,228 @@ +--source include/have_udf.inc + +--disable_query_log +if (`SELECT COUNT(*) FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME='test_plugin_count'`) +{ + UNINSTALL SONAME 'func_test'; +} +INSTALL SONAME 'func_test'; +--enable_query_log + +--echo # +--echo # Setup +--echo # +CREATE TABLE t1 (id INT PRIMARY KEY, g INT, v INT, u UUID); +INSERT INTO t1 VALUES + (1, 1, 10, '00000000-0000-0000-0000-000000000001'), + (2, 1, NULL, NULL), + (3, 2, 30, '00000000-0000-0000-0000-000000000003'); + +--echo # +--echo # Aggregate functions in forbidden SQL contexts +--echo # +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +CREATE TABLE bad_check (v INT, CHECK (test_plugin_count(v) > 0)); +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +CREATE TABLE bad_default (v INT DEFAULT (test_plugin_count(1))); +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +CREATE TABLE bad_generated + (v INT, c INT AS (test_plugin_count(v))); +--error ER_INVALID_GROUP_FUNC_USE +SELECT test_plugin_count(SUM(v)) FROM t1; +--error ER_INVALID_GROUP_FUNC_USE +SELECT SUM(test_plugin_count(v)) FROM t1; +--error ER_INVALID_GROUP_FUNC_USE +SELECT test_plugin_first(test_plugin_count(v)) FROM t1; +SELECT test_plugin_count(v) FROM t1; + +--echo # +--echo # Invalid window usage +--echo # +--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION +SELECT * FROM t1 WHERE test_plugin_count(v) OVER () > 0; +--error ER_SUM_FUNC_WITH_WINDOW_FUNC_AS_ARG +SELECT test_plugin_count(test_plugin_count(v) OVER ()) FROM t1; +--error ER_NOT_SUPPORTED_YET +SELECT test_plugin_count(DISTINCT v) OVER ( + ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +FROM t1; +--error ER_NOT_SUPPORTED_YET +SELECT test_plugin_first(DISTINCT u) OVER ( + PARTITION BY g ORDER BY id) +FROM t1; +SELECT test_plugin_count(v) OVER (ORDER BY id) FROM t1; + +--echo # +--echo # Invalid argument lists +--echo # +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT test_plugin_count(DISTINCT) FROM t1; +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT test_plugin_count(DISTINCT v, g) FROM t1; +--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT +SELECT sysconst_test(DISTINCT 1); +SELECT test_plugin_count(DISTINCT v) FROM t1; + +--echo # +--echo # Invalid pluggable data type values and missing type plugin +--echo # +SET @save_sql_mode= @@sql_mode; +SET sql_mode= 'STRICT_ALL_TABLES'; +--error ER_TRUNCATED_WRONG_VALUE +INSERT INTO t1 VALUES (4, 2, 40, 'not-a-uuid'); +SELECT test_plugin_first(CAST('not-a-uuid' AS UUID)) IS NULL; +SHOW WARNINGS; +SET sql_mode= @save_sql_mode; +INSTALL SONAME 'type_test'; +UNINSTALL SONAME 'type_test'; +--error ER_UNKNOWN_DATA_TYPE +PREPARE missing_type_stmt FROM + 'SELECT test_plugin_first(CAST(1 AS test_int8))'; +SELECT test_plugin_count(v) FROM t1; + +--echo # +--echo # Function plugin unavailable and marked for deferred unload +--echo # +UNINSTALL SONAME 'func_test'; +--error ER_SP_DOES_NOT_EXIST +SELECT test_plugin_count(v) FROM t1; +INSTALL SONAME 'func_test'; +PREPARE deleted_stmt FROM + 'SELECT test_plugin_count(v) FROM t1'; +UNINSTALL SONAME 'func_test'; +PREPARE deleted_stmt2 FROM + 'SELECT test_plugin_count(v) FROM t1'; +EXECUTE deleted_stmt; +EXECUTE deleted_stmt2; +DEALLOCATE PREPARE deleted_stmt; +DEALLOCATE PREPARE deleted_stmt2; +INSTALL SONAME 'func_test'; + +--echo # +--echo # Errors while storing aggregate results +--echo # +CREATE TABLE uuid_dst (value UUID PRIMARY KEY); +INSERT INTO uuid_dst + SELECT test_plugin_first(u) FROM t1 WHERE g = 1; +--error ER_DUP_ENTRY +INSERT INTO uuid_dst + SELECT test_plugin_first(u) FROM t1 WHERE g = 1; +CREATE TABLE not_null_dst (value INT NOT NULL); +--error ER_BAD_NULL_ERROR +INSERT INTO not_null_dst + SELECT test_plugin_first(v) FROM t1 WHERE FALSE; +SELECT test_plugin_count(v) FROM t1; + +--echo # +--echo # Statement timeout releases execution references +--echo # +--error ER_STATEMENT_TIMEOUT +SET STATEMENT max_statement_time=0.01 FOR + SELECT test_plugin_count(IF(SLEEP(0.1), v, v)) FROM t1; +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; + +--echo # +--echo # KILL QUERY during aggregate execution and deferred unload +--echo # +SELECT GET_LOCK('function_plugin_negative_lock', 10); +--connect (kill_con,localhost,root,,) +--connection kill_con +--send SELECT test_plugin_count(IF(GET_LOCK('function_plugin_negative_lock', 10), v, v)) FROM t1 +--connection default +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE STATE = 'User lock' AND INFO LIKE 'SELECT test_plugin_count%'; +--source include/wait_condition.inc +UNINSTALL SONAME 'func_test'; +let $kill_id= query_get_value( + SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE STATE = 'User lock' AND INFO LIKE 'SELECT test_plugin_count%', ID, 1); +--disable_query_log +eval KILL QUERY $kill_id; +--enable_query_log +SELECT RELEASE_LOCK('function_plugin_negative_lock'); +--connection kill_con +--error ER_QUERY_INTERRUPTED +--reap +--disconnect kill_con +--connection default +INSTALL SONAME 'func_test'; + +--echo # +--echo # Failed automatic reprepare +--echo # +PREPARE reprepare_drop FROM + 'SELECT test_plugin_count(v) FROM t1'; +ALTER TABLE t1 DROP COLUMN v; +--error ER_BAD_FIELD_ERROR +EXECUTE reprepare_drop; +DEALLOCATE PREPARE reprepare_drop; +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +ALTER TABLE t1 ADD COLUMN v INT; + +--echo # +--echo # Stored objects after plugin unload +--echo # +CREATE VIEW aggregate_view AS + SELECT test_plugin_count(v) AS c FROM t1; +UNINSTALL SONAME 'func_test'; +--error ER_VIEW_INVALID +SELECT * FROM aggregate_view; +INSTALL SONAME 'func_test'; +DROP VIEW aggregate_view; +DELIMITER |; +CREATE PROCEDURE aggregate_proc() +BEGIN + SELECT test_plugin_count(v) FROM t1; +END| +DELIMITER ;| +UNINSTALL SONAME 'func_test'; +--error ER_SP_DOES_NOT_EXIST +CALL aggregate_proc(); +INSTALL SONAME 'func_test'; +DROP PROCEDURE aggregate_proc; +CREATE TRIGGER aggregate_trigger BEFORE INSERT ON t1 +FOR EACH ROW SET NEW.v= test_plugin_count(NEW.v); +UNINSTALL SONAME 'func_test'; +--error ER_SP_DOES_NOT_EXIST +INSERT INTO t1 (id, g, v, u) VALUES (4, 2, 40, NULL); +INSTALL SONAME 'func_test'; +DROP TRIGGER aggregate_trigger; + +--echo # +--echo # Privilege errors do not change plugin state +--echo # +CREATE USER plugin_user@localhost; +--connect (priv_con,localhost,plugin_user,,) +--connection priv_con +--error ER_TABLEACCESS_DENIED_ERROR +INSTALL SONAME 'func_test'; +--error ER_TABLEACCESS_DENIED_ERROR +UNINSTALL SONAME 'func_test'; +--disconnect priv_con +--connection default +DROP USER plugin_user@localhost; +SELECT test_plugin_count(v) FROM t1; + +--echo # +--echo # Repeated result access and cleanup +--echo # +SELECT test_plugin_first(u), + test_plugin_first(u) IS NULL, + CAST(test_plugin_first(u) AS CHAR) +FROM t1; +SELECT test_plugin_count(v) AS c +FROM t1 HAVING c > 0 ORDER BY c; + +--echo # +--echo # Cleanup +--echo # +DROP TABLE not_null_dst, uuid_dst, t1; +--disable_query_log +UNINSTALL SONAME 'func_test'; +INSTALL SONAME 'func_test'; +DELETE FROM mysql.plugin WHERE dl='func_test.so'; +--enable_query_log diff --git a/plugin/func_test/plugin.cc b/plugin/func_test/plugin.cc index 7c93b47e8e59a..6d716612e90e4 100644 --- a/plugin/func_test/plugin.cc +++ b/plugin/func_test/plugin.cc @@ -18,6 +18,7 @@ #include #include +#include #include class Item_func_sysconst_test :public Item_func_sysconst @@ -275,6 +276,295 @@ class Item_func_strnxfrm: public Item_str_func } }; + +class Item_sum_test_plugin_first: public Item_sum_plugin, + public Type_handler_hybrid_field_type +{ + using Self= Item_sum_test_plugin_first; + Item_cache *value; + + void endup() + { + if (aggr) + aggr->endup(); + } + + bool setup_cache(THD *thd, Item *source) + { + if (!(value= args[0]->get_cache(thd)) || value->setup(thd, args[0])) + return true; + if (source) + { + value->store(source); + value->cache_value(); + null_value= value->null_value; + } + return false; + } + +public: + Item_sum_test_plugin_first(THD *thd, Item *item): + Item_sum_plugin(thd, item), + Type_handler_hybrid_field_type(&type_handler_string), value(nullptr) + {} + Item_sum_test_plugin_first(THD *thd, Self *item): + Item_sum_plugin(thd, item), Type_handler_hybrid_field_type(item), + value(nullptr) + {} + Item_sum_test_plugin_first(const Self &item): + Item_sum_plugin(item), Type_handler_hybrid_field_type(item), value(nullptr) + {} + void clear() override + { + value->clear(); + value->set_null(); + null_value= true; + } + bool add() override + { + if (!null_value) + return false; + value->store(aggregation_arg(0)); + value->cache_value(); + null_value= value->null_value; + return false; + } + double val_real() override + { + endup(); + if (null_value) + return 0.0; + double result= value->val_real(); + null_value= value->null_value; + return result; + } + longlong val_int() override + { + endup(); + if (null_value) + return 0; + longlong result= value->val_int(); + null_value= value->null_value; + return result; + } + String *val_str(String *str) override + { + endup(); + if (null_value) + return nullptr; + String *result= value->val_str(str); + null_value= value->null_value; + return result; + } + my_decimal *val_decimal(my_decimal *to) override + { + endup(); + if (null_value) + return nullptr; + my_decimal *result= value->val_decimal(to); + null_value= value->null_value; + return result; + } + bool get_date(THD *thd, MYSQL_TIME *ltime, + date_mode_t fuzzydate) override + { + endup(); + if (null_value) + return true; + bool result= value->get_date(thd, ltime, fuzzydate); + null_value= value->null_value; + return result; + } + bool val_native(THD *thd, Native *to) override + { + endup(); + if (null_value) + return true; + return val_native_from_item(thd, value, to); + } + bool is_null() override + { + endup(); + return null_value; + } + const Type_handler *type_handler() const override + { + return Type_handler_hybrid_field_type::type_handler(); + } + const Type_handler *real_type_handler() const override + { + return args[0]->real_type_handler(); + } + const Type_extra_attributes type_extra_attributes() const override + { + return args[0]->type_extra_attributes(); + } + bool fix_length_and_dec(THD *thd) override + { + Type_std_attributes::set(args[0]); + set_handler(args[0]->type_handler()); + set_maybe_null(); + null_value= true; + return setup_cache(thd, nullptr); + } + void cleanup() override + { + value= nullptr; + null_value= true; + Item_sum_plugin::cleanup(); + } + LEX_CSTRING func_name_cstring() const override + { + return "test_plugin_first("_LEX_CSTRING; + } + Item *copy_or_same(THD *thd) override + { + Self *item= new (thd->mem_root) Self(thd, this); + if (!item || item->setup_cache(thd, value)) + return nullptr; + return item; + } + + class Create_func: public Create_aggregate_func + { + public: + Item *create_native(THD *thd, const LEX_CSTRING *name, + List *item_list) override + { + uint arg_count= item_list ? item_list->elements : 0; + if (arg_count != 1) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name->str); + return nullptr; + } + return new (thd->mem_root) Self(thd, item_list->head()); + } + }; + + static Plugin_function *plugin_descriptor() + { + static Create_func creator; + static Plugin_function descriptor(&creator); + return &descriptor; + } +protected: + Item *shallow_copy(THD *thd) const override + { + Self *item= new (thd->mem_root) Self(thd, const_cast(this)); + if (!item || item->setup_cache(thd, value)) + return nullptr; + return item; + } +}; + + +class Item_sum_test_plugin_count: public Item_sum_plugin +{ + using Self= Item_sum_test_plugin_count; + longlong count; +public: + Item_sum_test_plugin_count(THD *thd, Item *item): + Item_sum_plugin(thd, item), count(0) + {} + Item_sum_test_plugin_count(THD *thd, Self *item): + Item_sum_plugin(thd, item), count(0) + {} + Item_sum_test_plugin_count(const Self &item): + Item_sum_plugin(item), count(0) + {} + void clear() override + { + count= 0; + null_value= false; + } + bool add() override + { + if (!aggr->arg_is_null(false)) + count++; + return false; + } + bool supports_removal() const override { return true; } + void remove() override + { + if (aggr->arg_is_null(false)) + return; + if (count > 0) + count--; + } + longlong val_int() override + { + DBUG_ASSERT(fixed()); + if (aggr) + aggr->endup(); + null_value= false; + return count; + } + double val_real() override { return static_cast(val_int()); } + String *val_str(String *str) override { return val_string_from_int(str); } + my_decimal *val_decimal(my_decimal *to) override + { + return val_decimal_from_int(to); + } + bool get_date(THD *thd, MYSQL_TIME *ltime, + date_mode_t fuzzydate) override + { + return get_date_from_int(thd, ltime, fuzzydate); + } + const Type_handler *type_handler() const override + { + return &type_handler_slonglong; + } + bool fix_length_and_dec(THD *thd) override + { + decimals= 0; + max_length= 21; + base_flags&= ~item_base_t::MAYBE_NULL; + null_value= false; + return false; + } + void cleanup() override + { + count= 0; + Item_sum_plugin::cleanup(); + } + LEX_CSTRING func_name_cstring() const override + { + return "test_plugin_count("_LEX_CSTRING; + } + Item *copy_or_same(THD *thd) override + { + return new (thd->mem_root) Self(thd, this); + } + + class Create_func: public Create_aggregate_func + { + public: + Item *create_native(THD *thd, const LEX_CSTRING *name, + List *item_list) override + { + uint arg_count= item_list ? item_list->elements : 0; + if (arg_count != 1) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name->str); + return nullptr; + } + return new (thd->mem_root) Self(thd, item_list->head()); + } + }; + + static Plugin_function *plugin_descriptor() + { + static Create_func creator; + static Plugin_function descriptor(&creator); + return &descriptor; + } +protected: + Item *shallow_copy(THD *thd) const override + { + return get_item_copy(thd, this); + } +}; + /*************************************************************************/ maria_declare_plugin(type_test) @@ -337,5 +627,35 @@ maria_declare_plugin(type_test) NULL, // System variables "1.0", // String version representation MariaDB_PLUGIN_MATURITY_EXPERIMENTAL // Maturity +}, +{ + MariaDB_FUNCTION_PLUGIN, // the plugin type + Item_sum_test_plugin_first::plugin_descriptor(), + "test_plugin_first", // plugin name + "MariaDB Corporation", // plugin author + "Function TEST_PLUGIN_FIRST()", // the plugin description + PLUGIN_LICENSE_GPL, // the plugin license + 0, // Pointer to plugin initialization function + 0, // Pointer to plugin deinitialization function + 0x0100, // Numeric version 0xAABB means AA.BB version + NULL, // Status variables + NULL, // System variables + "1.0", // String version representation + MariaDB_PLUGIN_MATURITY_EXPERIMENTAL // Maturity +}, +{ + MariaDB_FUNCTION_PLUGIN, // the plugin type + Item_sum_test_plugin_count::plugin_descriptor(), + "test_plugin_count", // plugin name + "MariaDB Corporation", // plugin author + "Function TEST_PLUGIN_COUNT()", // the plugin description + PLUGIN_LICENSE_GPL, // the plugin license + 0, // Pointer to plugin initialization function + 0, // Pointer to plugin deinitialization function + 0x0100, // Numeric version 0xAABB means AA.BB version + NULL, // Status variables + NULL, // System variables + "1.0", // String version representation + MariaDB_PLUGIN_MATURITY_EXPERIMENTAL // Maturity } maria_declare_plugin_end; diff --git a/sql/item_create.cc b/sql/item_create.cc index f2716e643668a..80175a87550f2 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -6651,15 +6651,21 @@ void Native_functions_hash::cleanup() static Create_func * -function_plugin_find_native_function_builder(THD *thd, const LEX_CSTRING &name) +function_plugin_find_native_function_builder( + THD *thd, const LEX_CSTRING &name, plugin_ref *plugin_out) { plugin_ref plugin; if ((plugin= my_plugin_lock_by_name(thd, &name, MariaDB_FUNCTION_PLUGIN))) { - Create_func *builder= - reinterpret_cast(plugin_decl(plugin)->info)-> - create_func(); - // TODO: MDEV-20846 Add proper unlocking for MariaDB_FUNCTION_PLUGIN + Plugin_function *descriptor= + reinterpret_cast(plugin_decl(plugin)->info); + Create_func *builder= descriptor->create_func(); + if (plugin_out && !(*plugin_out= plugin_lock(NULL, plugin))) + { + plugin_unlock(thd, plugin); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } plugin_unlock(thd, plugin); return builder; } @@ -6668,11 +6674,15 @@ function_plugin_find_native_function_builder(THD *thd, const LEX_CSTRING &name) Create_func * -Native_functions_hash::find(THD *thd, const LEX_CSTRING &name) const +Native_functions_hash::find(THD *thd, const LEX_CSTRING &name, + plugin_ref *plugin) const { Native_func_registry *func; Create_func *builder= NULL; + if (plugin) + *plugin= NULL; + /* Thread safe */ func= (Native_func_registry*) my_hash_search(this, (uchar*) name.str, @@ -6681,7 +6691,8 @@ Native_functions_hash::find(THD *thd, const LEX_CSTRING &name) const if (func && (builder= func->builder)) return builder; - if ((builder= function_plugin_find_native_function_builder(thd, name))) + if ((builder= function_plugin_find_native_function_builder(thd, name, + plugin))) return builder; return NULL; diff --git a/sql/item_create.h b/sql/item_create.h index 019acde5b41ef..e10b01a431ad3 100644 --- a/sql/item_create.h +++ b/sql/item_create.h @@ -20,6 +20,7 @@ #define ITEM_CREATE_H #include "item_func.h" // Cast_target +#include "sql_plugin.h" typedef struct st_udf_func udf_func; @@ -209,6 +210,14 @@ class Create_native_func : public Create_func }; +class Create_aggregate_func : public Create_native_func +{ +protected: + Create_aggregate_func() = default; + virtual ~Create_aggregate_func() = default; +}; + + /** Function builder for qualified functions. This builder is used with functions call using a qualified function name @@ -337,7 +346,8 @@ class Native_functions_hash: public HASH @param name The native function name @return The native function builder associated with the name, or NULL */ - Create_func *find(THD *thd, const LEX_CSTRING &name) const; + Create_func *find(THD *thd, const LEX_CSTRING &name, + plugin_ref *plugin= NULL) const; }; extern MYSQL_PLUGIN_IMPORT Native_functions_hash native_functions_hash; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index cc07a6de608eb..d39ab9702ca32 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -31,6 +31,7 @@ #include "sql_parse.h" #include "sp_head.h" #include "item_sum.h" +#include "sql_plugin.h" #include "sql_type_geom.h" /** @@ -513,6 +514,135 @@ Item_sum::Item_sum(THD *thd, Item_sum *item): } +struct Item_sum_plugin_lifetime +{ + uint ref_count; + plugin_ref function_plugin; + plugin_ref *type_plugins; + uint type_plugin_count; + + Item_sum_plugin_lifetime(plugin_ref plugin) + :ref_count(1), function_plugin(plugin), type_plugins(NULL), + type_plugin_count(0) + { } + + ~Item_sum_plugin_lifetime() + { + plugin_unlock_list(NULL, type_plugins, type_plugin_count); + my_free(type_plugins); + plugin_unlock(NULL, function_plugin); + } +}; + + +Item_sum_plugin::Item_sum_plugin(THD *thd, Item *item) + :Item_sum(thd, item), m_plugin_lifetime(NULL) +{ + quick_group= false; +} + + +Item_sum_plugin::Item_sum_plugin(THD *thd, Item_sum_plugin *item) + :Item_sum(thd, item), m_plugin_lifetime(NULL) +{ + quick_group= false; + retain_plugin_lifetime(item); +} + + +Item_sum_plugin::Item_sum_plugin(const Item_sum_plugin &item) + :Item_sum(item), m_plugin_lifetime(NULL) +{ + retain_plugin_lifetime(&item); +} + + +Item_sum_plugin::~Item_sum_plugin() +{ + Item_sum_plugin_lifetime *lifetime= + static_cast(m_plugin_lifetime); + if (lifetime && !--lifetime->ref_count) + delete lifetime; +} + + +void Item_sum_plugin::retain_plugin_lifetime(const Item_sum_plugin *item) +{ + m_plugin_lifetime= item->m_plugin_lifetime; + if (m_plugin_lifetime) + static_cast(m_plugin_lifetime)->ref_count++; +} + + +bool Item_sum_plugin::set_function_plugin(void *plugin) +{ + DBUG_ASSERT(!m_plugin_lifetime); + m_plugin_lifetime= + new Item_sum_plugin_lifetime(static_cast(plugin)); + return !m_plugin_lifetime; +} + + +bool Item_sum_plugin::lock_type_plugins(THD *thd) +{ + Item_sum_plugin_lifetime *lifetime= + static_cast(m_plugin_lifetime); + if (!lifetime || lifetime->type_plugins) + return false; + + uint count= arg_count + 1; + plugin_ref *plugins= static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(plugin_ref) * count, MYF(MY_WME))); + if (!plugins) + return true; + + count= 0; + for (uint i= 0; i <= arg_count; i++) + { + const Type_handler *handler= i < arg_count ? args[i]->type_handler() : + type_handler(); + const LEX_CSTRING name= handler->name().lex_cstring(); + plugin_ref plugin= plugin_lock_by_name(NULL, &name, + MariaDB_DATA_TYPE_PLUGIN); + if (plugin) + plugins[count++]= plugin; + } + if (!count) + { + my_free(plugins); + return false; + } + lifetime->type_plugins= plugins; + lifetime->type_plugin_count= count; + return false; +} + + +bool Item_sum_plugin::fix_fields(THD *thd, Item **ref) +{ + DBUG_ASSERT(fixed() == 0); + + if (init_sum_func_check(thd)) + return true; + + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->fix_fields_if_needed_for_scalar(thd, &args[i])) + return true; + with_flags|= args[i]->with_flags & ~item_with_t::FIELD; + } + result_field= NULL; + if (fix_length_and_dec(thd) || lock_type_plugins(thd) || + check_sum_func(thd, ref)) + return true; + + if (arg_count) + memcpy(orig_args, args, sizeof(Item *) * arg_count); + base_flags|= item_base_t::FIXED; + return false; +} + + void Item_sum::mark_as_sum_func() { SELECT_LEX *cur_select= current_thd->lex->current_select; @@ -770,8 +900,9 @@ bool Aggregator_distinct::setup(THD *thd) if (item_sum->setup(thd)) return TRUE; - if (item_sum->sum_func() == Item_sum::COUNT_FUNC || - item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC) + if (item_sum->sum_func() == Item_sum::COUNT_FUNC || + item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC || + item_sum->sum_func() == Item_sum::PLUGIN_SUM_FUNC) { List list; SELECT_LEX *select_lex= thd->lex->current_select; @@ -807,6 +938,17 @@ bool Aggregator_distinct::setup(THD *thd) return TRUE; table->file->extra(HA_EXTRA_NO_ROWS); // Don't update rows table->no_rows=1; + if (item_sum->sum_func() == Item_sum::PLUGIN_SUM_FUNC) + { + if (!(distinct_args= thd->alloc(item_sum->get_arg_count()))) + return TRUE; + for (uint i= 0; i < item_sum->get_arg_count(); i++) + { + if (!(distinct_args[i]= new (thd->mem_root) + Item_field(thd, table->field[i]))) + return TRUE; + } + } if (table->s->db_type() == heap_hton) { @@ -949,8 +1091,9 @@ void Aggregator_distinct::clear() if (tree) tree->reset(); /* tree and table can be both null only if always_null */ - if (item_sum->sum_func() == Item_sum::COUNT_FUNC || - item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC) + if (item_sum->sum_func() == Item_sum::COUNT_FUNC || + item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC || + item_sum->sum_func() == Item_sum::PLUGIN_SUM_FUNC) { if (!tree && table) { @@ -987,8 +1130,9 @@ bool Aggregator_distinct::add() if (always_null) return 0; - if (item_sum->sum_func() == Item_sum::COUNT_FUNC || - item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC) + if (item_sum->sum_func() == Item_sum::COUNT_FUNC || + item_sum->sum_func() == Item_sum::COUNT_DISTINCT_FUNC || + item_sum->sum_func() == Item_sum::PLUGIN_SUM_FUNC) { int error; copy_fields(tmp_table_param); @@ -1077,6 +1221,27 @@ void Aggregator_distinct::endup() } } + if (!tree && table && + item_sum->sum_func() == Item_sum::PLUGIN_SUM_FUNC && !endup_done) + { + int error; + bool add_error= false; + use_distinct_values= true; + if (!(error= table->file->ha_rnd_init_with_error(true))) + { + while (!(error= table->file->ha_rnd_next(table->record[0]))) + { + if ((add_error= item_sum->add())) + break; + } + if (!add_error && error != HA_ERR_END_OF_FILE) + table->file->print_error(error, MYF(0)); + table->file->ha_rnd_end(); + } + use_distinct_values= false; + endup_done= true; + } + /* We don't have a tree only if 'setup()' hasn't been called; this is the case of sql_executor.cc:return_zero_rows. @@ -1903,6 +2068,13 @@ bool Aggregator_distinct::arg_is_null(bool use_null_value) } +Item *Aggregator_distinct::arg_item(uint i) +{ + DBUG_ASSERT(i < item_sum->get_arg_count()); + return use_distinct_values ? distinct_args[i] : item_sum->get_arg(i); +} + + Item *Item_sum_count::copy_or_same(THD* thd) { DBUG_ENTER("Item_sum_count::copy_or_same"); diff --git a/sql/item_sum.h b/sql/item_sum.h index 39ed79e7c0203..50f2a240e775c 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -103,6 +103,7 @@ class Aggregator : public Sql_alloc (updated by arg_val*()). */ virtual bool arg_is_null(bool use_null_value) = 0; + virtual Item *arg_item(uint i) = 0; }; @@ -349,7 +350,8 @@ class Item_sum :public Item_func_or_sum enum Sumfunctype { COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC, AVG_DISTINCT_FUNC, MIN_FUNC, MAX_FUNC, STD_FUNC, - VARIANCE_FUNC, SUM_BIT_FUNC, UDF_SUM_FUNC, GROUP_CONCAT_FUNC, + VARIANCE_FUNC, SUM_BIT_FUNC, UDF_SUM_FUNC, PLUGIN_SUM_FUNC, + GROUP_CONCAT_FUNC, ROW_NUMBER_FUNC, RANK_FUNC, DENSE_RANK_FUNC, PERCENT_RANK_FUNC, CUME_DIST_FUNC, NTILE_FUNC, FIRST_VALUE_FUNC, LAST_VALUE_FUNC, NTH_VALUE_FUNC, LEAD_FUNC, LAG_FUNC, PERCENTILE_CONT_FUNC, @@ -433,6 +435,7 @@ class Item_sum :public Item_func_or_sum case VARIANCE_FUNC: case SUM_BIT_FUNC: case UDF_SUM_FUNC: + case PLUGIN_SUM_FUNC: case GROUP_CONCAT_FUNC: case JSON_ARRAYAGG_FUNC: case GEOMETRY_COLLECT_FUNC: @@ -611,6 +614,36 @@ class Item_sum :public Item_func_or_sum }; +/** + Base class for native aggregate function plugins. + + Derived classes implement clear(), add(), result accessors and copy methods. + aggregation_arg() must be used instead of args[] while consuming rows, so + DISTINCT replay can substitute values from its internal temporary storage. + Implement supports_removal() and remove() when the state is invertible. + Plugin references are retained until the original Item and all copies are + destroyed; cleanup() does not release them because prepared Items are reused. +*/ +class Item_sum_plugin : public Item_sum +{ + void *m_plugin_lifetime; + + bool lock_type_plugins(THD *thd); + void retain_plugin_lifetime(const Item_sum_plugin *item); +public: + Item_sum_plugin(THD *thd, Item *item); + Item_sum_plugin(THD *thd, Item_sum_plugin *item); + Item_sum_plugin(const Item_sum_plugin &item); + ~Item_sum_plugin() override; + enum Sumfunctype sum_func() const override { return PLUGIN_SUM_FUNC; } + bool fix_fields(THD *thd, Item **ref) override; + bool set_function_plugin(void *plugin); + Item *aggregation_arg(uint i) { return aggr->arg_item(i); } + void reset_field() override { DBUG_ASSERT(0); } + void update_field() override { DBUG_ASSERT(0); } +}; + + class Unique; @@ -696,11 +729,12 @@ class Aggregator_distinct : public Aggregator instead of calling the relevant val_..() method. */ bool use_distinct_values; + Item **distinct_args; public: Aggregator_distinct (Item_sum *sum) : Aggregator(sum), table(NULL), tmp_table_param(NULL), tree(NULL), - always_null(false), use_distinct_values(false) {} + always_null(false), use_distinct_values(false), distinct_args(NULL) {} virtual ~Aggregator_distinct (); Aggregator_type Aggrtype() override { return DISTINCT_AGGREGATOR; } @@ -711,6 +745,7 @@ class Aggregator_distinct : public Aggregator my_decimal *arg_val_decimal(my_decimal * value) override; double arg_val_real() override; bool arg_is_null(bool use_null_value) override; + Item *arg_item(uint i) override; bool unique_walk_function(void *element); bool unique_walk_function_for_count(void *element); @@ -738,6 +773,7 @@ class Aggregator_simple : public Aggregator my_decimal *arg_val_decimal(my_decimal * value) override; double arg_val_real() override; bool arg_is_null(bool use_null_value) override; + Item *arg_item(uint i) override { return item_sum->get_arg(i); } }; diff --git a/sql/sql_schema.cc b/sql/sql_schema.cc index 776f7bcc4d75f..593c50b26df3e 100644 --- a/sql/sql_schema.cc +++ b/sql/sql_schema.cc @@ -33,10 +33,11 @@ class Schema_oracle: public Schema return src; } - Create_func *find_native_function_builder(THD *thd, const LEX_CSTRING &name) - const override + Create_func *find_native_function_builder( + THD *thd, const LEX_CSTRING &name, + plugin_ref *plugin= NULL) const override { - return native_functions_hash_oracle.find(thd, name); + return native_functions_hash_oracle.find(thd, name, plugin); } Item *make_item_func_replace(THD *thd, @@ -96,9 +97,10 @@ Schema *Schema::find_implied(THD *thd) Create_func * -Schema::find_native_function_builder(THD *thd, const LEX_CSTRING &name) const +Schema::find_native_function_builder( + THD *thd, const LEX_CSTRING &name, plugin_ref *plugin) const { - return native_functions_hash.find(thd, name); + return native_functions_hash.find(thd, name, plugin); } diff --git a/sql/sql_schema.h b/sql/sql_schema.h index 2406dd5ff08ec..a1cbbaca6c84d 100644 --- a/sql/sql_schema.h +++ b/sql/sql_schema.h @@ -18,6 +18,7 @@ #include "mysqld.h" #include "lex_string.h" +#include "sql_plugin.h" class Lex_ident_sys; class Create_func; @@ -51,9 +52,8 @@ class Schema @param name The native function name @return The native function builder associated with the name, or NULL */ - virtual Create_func *find_native_function_builder(THD *thd, - const LEX_CSTRING &name) - const; + virtual Create_func *find_native_function_builder( + THD *thd, const LEX_CSTRING &name, plugin_ref *plugin= NULL) const; // Builders for native SQL function with a special syntax in sql_yacc.yy virtual Item *make_item_func_replace(THD *thd, diff --git a/sql/sql_window.cc b/sql/sql_window.cc index e5b4b4de7d644..77d7ce3aa943b 100644 --- a/sql/sql_window.cc +++ b/sql/sql_window.cc @@ -3020,6 +3020,12 @@ bool Window_func_runner::add_function_to_run(Item_window_func *win_func) sum_func->setup_window_func(current_thd, win_func->window_spec); Item_sum::Sumfunctype type= win_func->window_func()->sum_func(); + if (type == Item_sum::PLUGIN_SUM_FUNC && sum_func->has_with_distinct()) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), + "plugin aggregate with DISTINCT as window function"); + return true; + } switch (type) { diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index b2071b1c50b7f..31eb4e08bcf70 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -327,6 +327,11 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() class Window_frame *window_frame; class Window_frame_bound *window_frame_bound; udf_func *udf; + struct + { + udf_func *udf; + bool aggregate; + } generic_function; st_trg_execution_order trg_execution_order; /* enums */ @@ -11248,31 +11253,35 @@ function_call_conflict: function_call_generic: ident_cli_func '(' { + const Lex_ident_sys sysname(thd, &$1); + udf_func *udf= NULL; + bool aggregate= false; + if (sysname.is_null()) + MYSQL_YYABORT; // EOM + Create_func *native_builder= Schema::find_implied(thd)-> + find_native_function_builder(thd, sysname); + aggregate= dynamic_cast(native_builder) != NULL; #ifdef HAVE_DLOPEN - udf_func *udf= 0; - LEX *lex= Lex; - if (using_udf_functions) + if (!native_builder && using_udf_functions) { // find_udf expectes a 0-terminated string - const Lex_ident_sys sysname(thd, &$1); - if (sysname.is_null()) - MYSQL_YYABORT; // EOM - if ((udf= find_udf(sysname.str, sysname.length)) && - udf->type == UDFTYPE_AGGREGATE) - { - if (unlikely(lex->current_select->inc_in_sum_expr())) - { - thd->parse_error(); - MYSQL_YYABORT; - } - } + udf= find_udf(sysname.str, sysname.length); + if (udf && udf->type == UDFTYPE_AGGREGATE) + aggregate= true; } - /* Temporary placing the result of find_udf in $3 */ - $$= udf; #endif + if (aggregate && + unlikely(Lex->current_select->inc_in_sum_expr())) + { + thd->parse_error(); + MYSQL_YYABORT; + } + $$.udf= udf; + $$.aggregate= aggregate; } - opt_udf_expr_list_or_join_operator ')' opt_object_member_access + opt_distinct opt_udf_expr_list_or_join_operator ')' + opt_object_member_access { const Type_handler *h; Create_func *builder; @@ -11282,6 +11291,11 @@ function_call_generic: const Sp_rcontext_handler *rh; sp_variable *spv= NULL; bool allow_field_accessor= false; + bool native_item= false; + plugin_ref function_plugin= NULL; + + if ($3.aggregate) + Select->in_sum_expr--; if (unlikely(ident.is_null() || Lex_ident_routine::check_name_with_error(ident))) @@ -11297,10 +11311,10 @@ function_call_generic: This will be revised with WL#2128 (SQL PATH) */ - if ($4 && $4->elements == 1 && - dynamic_cast($4->head())) + if ($5 && $5->elements == 1 && + dynamic_cast($5->head())) { - if ($6.str) + if ($7.str) { thd->parse_error(); MYSQL_YYABORT; @@ -11309,28 +11323,30 @@ function_call_generic: if (!item || Lex->mark_item_ident_for_ora_join(thd, item)) MYSQL_YYABORT; } else if ((builder= Schema::find_implied(thd)-> - find_native_function_builder(thd, ident))) + find_native_function_builder(thd, ident, + &function_plugin))) { - item= builder->create_func(thd, &ident, $4); + item= builder->create_func(thd, &ident, $5); + native_item= true; } else if ((h= Type_handler::handler_by_name(thd, ident)) && - (item= h->make_constructor_item(thd, $4))) + (item= h->make_constructor_item(thd, $5))) { // Found a constructor with a proper argument count } else if (Lex->spcont && (tdef= Lex->find_type_def(ident))) { - item= tdef->make_constructor_item(thd, $4); + item= tdef->make_constructor_item(thd, $5); } else if (Lex->spcont && (spv= Lex->find_variable(&ident, &rh)) && spv->type_handler()->has_functors()) { - const char *end= $6.str ? $6.end() : $5.end(); + const char *end= $7.str ? $7.end() : $6.end(); const Lex_ident_cli name_cli($1.pos(), end - $1.pos()); - auto ident2= $6.str ? Lex_ident_sys(thd, &$6) : Lex_ident_sys(); - if (($6.str && ident2.is_null()) || - !(item= Lex->create_item_functor(thd, ident, $4, + auto ident2= $7.str ? Lex_ident_sys(thd, &$7) : Lex_ident_sys(); + if (($7.str && ident2.is_null()) || + !(item= Lex->create_item_functor(thd, ident, $5, ident2, name_cli))) MYSQL_YYABORT; item->set_name(thd, $1.pos(), end - $1.pos(), thd->charset()); @@ -11342,29 +11358,54 @@ function_call_generic: { #ifdef HAVE_DLOPEN /* Retrieving the result of find_udf */ - udf_func *udf= $3; + udf_func *udf= $3.udf; if (udf) - { - if (udf->type == UDFTYPE_AGGREGATE) - { - Select->in_sum_expr--; - } - - item= Create_udf_func::s_singleton.create(thd, udf, $4); - } + item= Create_udf_func::s_singleton.create(thd, udf, $5); else #endif { builder= find_qualified_function_builder(thd); DBUG_ASSERT(builder); - item= builder->create_func(thd, &ident, $4); + item= builder->create_func(thd, &ident, $5); + } + } + + Item_sum *sum_item= item ? dynamic_cast(item) : NULL; + if (function_plugin && sum_item) + { + Item_sum_plugin *plugin_item= + dynamic_cast(sum_item); + if (!plugin_item) + { + plugin_unlock(NULL, function_plugin); + my_error(ER_INTERNAL_ERROR, MYF(0), + "Aggregate function plugin did not return Item_sum_plugin"); + MYSQL_YYABORT; + } + if (plugin_item->set_function_plugin(function_plugin)) + { + plugin_unlock(NULL, function_plugin); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + MYSQL_YYABORT; + } + function_plugin= NULL; + } + if (function_plugin) + plugin_unlock(NULL, function_plugin); + if ($4) + { + if (!native_item || !sum_item) + { + thd->parse_error(); + MYSQL_YYABORT; } + sum_item->set_distinct(true); } - if ($6.str && !allow_field_accessor) + if ($7.str && !allow_field_accessor) { - Lex_ident_sys field_sys(thd, &$6); + Lex_ident_sys field_sys(thd, &$7); my_error(ER_BAD_FIELD_ERROR, MYF(0), field_sys.str, ident.str); MYSQL_YYABORT; } @@ -11765,11 +11806,10 @@ window_func: | function_call_generic { - Item* item = (Item*)$1; - /* Only UDF aggregate here possible */ - if ((item == NULL) || - (item->type() != Item::SUM_FUNC_ITEM) - || (((Item_sum *)item)->sum_func() != Item_sum::UDF_SUM_FUNC)) + Item *item= (Item *) $1; + if (!item || item->type() != Item::SUM_FUNC_ITEM || + (((Item_sum *) item)->sum_func() != Item_sum::UDF_SUM_FUNC && + ((Item_sum *) item)->sum_func() != Item_sum::PLUGIN_SUM_FUNC)) { thd->parse_error(); MYSQL_YYABORT;