From 5930c48c415ee0cab102d85a75f6bc0e98928a4a Mon Sep 17 00:00:00 2001 From: Iqbal Hassan Date: Fri, 11 Oct 2024 13:06:20 +0800 Subject: [PATCH] MDEV-34319: DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines This patch adds support for associative arrays in stored procedures for sql_mode=ORACLE. The syntax follows Oracle's PL/SQL syntax for associative arrays - TYPE assoc_array_t IS TABLE OF VARCHAR2(100) INDEX BY INTEGER; or TYPE assoc_array_t IS TABLE OF record_t INDEX BY VARCHAR2(100); where record_t is a record type. The following functions were added for associative arrays: - COUNT - Retrieve the number of elements within the arra - EXISTS - Check whether given key exists in the array - FIRST - Retrieve the first key in the array - LAST - Retrieve the last key in the array - PRIOR - Retrieve the key before the given key - NEXT - Retrieve the key after the given key - DELETE - Remove the element with the given key or remove all elements if no key is given The arrays/elements can be initialized with the following methods: - Constructor i.e. array:= assoc_array_t('key1'=>1, 'key2'=>2, 'key3'=>3) - Assignment i.e. array(key):= record_t(1, 2) - SELECT INTO i.e. SELECT x INTO array(key) TODOs: - Nested tables are not supported yet. i.e. TYPE assoc_array_t IS TABLE OF other_assoc_array_t INDEX BY INTEGER; - Associative arrays comparisons are not supported yet. --- libmysqld/CMakeLists.txt | 1 + plugin/type_assoc_array/CMakeLists.txt | 18 + .../binlog_sp-assoc-array.result | 89 + .../binlog_sp-assoc-array.test | 67 + .../rpl_sp-assoc-array.result | 75 + .../type_assoc_array/rpl_sp-assoc-array.test | 87 + .../sp-assoc-array-code.result | 107 + .../type_assoc_array/sp-assoc-array-code.test | 89 + .../type_assoc_array/sp-assoc-array.result | 2593 +++++++++++++++ .../type_assoc_array/sp-assoc-array.test | 2948 +++++++++++++++++ .../mysql-test/type_assoc_array/suite.pm | 7 + .../type_assoc_array/sql_type_assoc_array.cc | 2573 ++++++++++++++ .../type_assoc_array/sql_type_assoc_array.h | 462 +++ .../type_uuid/type_uuid_sp_assoc_array.result | 35 + .../type_uuid/type_uuid_sp_assoc_array.test | 38 + sql/CMakeLists.txt | 2 + sql/field.h | 18 +- sql/field_composite.h | 73 + sql/item_composite.cc | 31 + sql/item_composite.h | 103 + sql/item_func.cc | 2 +- sql/item_func.h | 2 + sql/item_row.cc | 9 - sql/item_row.h | 40 +- sql/lex.h | 1 + sql/share/errmsg-utf8.txt | 6 + sql/sp_head.cc | 49 +- sql/sp_head.h | 5 + sql/sp_instr.cc | 115 +- sql/sp_instr.h | 99 +- sql/sp_pcontext.cc | 15 + sql/sp_pcontext.h | 47 +- sql/sp_rcontext.cc | 89 +- sql/sp_rcontext.h | 13 +- sql/sp_rcontext_handler.h | 16 + sql/sp_type_def.h | 24 + sql/sql_class.cc | 45 + sql/sql_class.h | 54 + sql/sql_lex.cc | 292 +- sql/sql_lex.h | 45 +- sql/sql_string.cc | 9 + sql/sql_string.h | 8 + sql/sql_type.cc | 369 +-- sql/sql_type.h | 147 +- sql/sql_type_composite.cc | 211 ++ sql/sql_type_composite.h | 425 +++ sql/sql_type_row.cc | 251 +- sql/sql_type_row.h | 370 +-- sql/sql_yacc.yy | 444 ++- 49 files changed, 11651 insertions(+), 967 deletions(-) create mode 100644 plugin/type_assoc_array/CMakeLists.txt create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.result create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.test create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.result create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.test create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-code.result create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-code.test create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array.result create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array.test create mode 100644 plugin/type_assoc_array/mysql-test/type_assoc_array/suite.pm create mode 100644 plugin/type_assoc_array/sql_type_assoc_array.cc create mode 100644 plugin/type_assoc_array/sql_type_assoc_array.h create mode 100644 plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.result create mode 100644 plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.test create mode 100644 sql/field_composite.h create mode 100644 sql/item_composite.cc create mode 100644 sql/item_composite.h create mode 100644 sql/sql_type_composite.cc create mode 100644 sql/sql_type_composite.h diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 14b06dd5559..b79f4b939f1 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -136,6 +136,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_schema.cc ../sql/lex_charset.cc ../sql/charset_collations.cc ../sql/sql_type.cc ../sql/sql_type.h + ../sql/sql_type_composite.cc ../sql/item_composite.cc ../sql/sql_type_row.cc ../sql/sql_mode.cc ../sql/sql_type_string.cc diff --git a/plugin/type_assoc_array/CMakeLists.txt b/plugin/type_assoc_array/CMakeLists.txt new file mode 100644 index 00000000000..e24ef50d696 --- /dev/null +++ b/plugin/type_assoc_array/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2023-2025, MariaDB corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +MYSQL_ADD_PLUGIN(type_assoc_array sql_type_assoc_array.cc + RECOMPILE_FOR_EMBEDDED + MANDATORY COMPONENT Assoc_array) diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.result b/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.result new file mode 100644 index 00000000000..99d66e1b38e --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.result @@ -0,0 +1,89 @@ +SET sql_mode=oracle; +CREATE TABLE t1 (a VARCHAR(64)); +# +# MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +INSERT INTO t1 VALUES (person_by_nickname.FIRST); +INSERT INTO t1 VALUES (person_by_nickname('Monty').first_name); +DELETE FROM t1 WHERE person_by_nickname.EXISTS('Monty'); +INSERT INTO t1 VALUES ('1'); +DELETE FROM t1 WHERE person_by_nickname IS NOT NULL; +person_by_nickname:= NULL; +INSERT INTO t1 VALUES ('1'); +DELETE FROM t1 WHERE person_by_nickname IS NULL; +END; +$$ +DECLARE +TYPE first_names_t IS TABLE OF VARCHAR2(64) INDEX BY VARCHAR2(20); +first_names first_names_t; +nick VARCHAR(64):= 'Monty'; +BEGIN +first_names('Monty') := 'Michael'; +INSERT INTO t1 VALUES (first_names('Monty')); +INSERT INTO t1 VALUES (first_names(nick)); +INSERT INTO t1 VALUES (first_names(TRIM(nick || ' '))); +INSERT INTO t1 VALUES (first_names(TRIM(first_names.LAST))); +SELECT * FROM t1; +CREATE TABLE t2 AS SELECT first_names('Monty'); +END; +$$ +a +Michael +Michael +Michael +Michael +DROP TABLE t1; +DROP TABLE t2; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a VARCHAR(64)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('person_by_nickname.FIRST',_utf8mb4'Monty' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('person_by_nickname(\'Monty\').first_name',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; DELETE FROM t1 WHERE NAME_CONST('person_by_nickname.EXISTS(\'Monty\')',1) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES ('1') +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; DELETE FROM t1 WHERE NAME_CONST('person_by_nickname',1) IS NOT NULL +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES ('1') +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; DELETE FROM t1 WHERE NAME_CONST('person_by_nickname',NULL) IS NULL +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('first_names(\'Monty\')',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('first_names(nick)',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('first_names(TRIM(nick || \' \'))',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (NAME_CONST('first_names(TRIM(first_names.LAST))',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci')) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t2 AS SELECT NAME_CONST('first_names(\'Monty\')',_utf8mb4'Michael' COLLATE 'utf8mb4_uca1400_ai_ci') +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE "t1" /* generated by server */ +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE "t2" /* generated by server */ diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.test b/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.test new file mode 100644 index 00000000000..cd27da8edb8 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/binlog_sp-assoc-array.test @@ -0,0 +1,67 @@ +--source include/not_embedded.inc +--source include/have_binlog_format_statement.inc + +SET sql_mode=oracle; + +--disable_query_log +reset master; # get rid of previous tests binlog +--enable_query_log + +CREATE TABLE t1 (a VARCHAR(64)); + +--echo # +--echo # MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + + INSERT INTO t1 VALUES (person_by_nickname.FIRST); + INSERT INTO t1 VALUES (person_by_nickname('Monty').first_name); + + DELETE FROM t1 WHERE person_by_nickname.EXISTS('Monty'); + + INSERT INTO t1 VALUES ('1'); + DELETE FROM t1 WHERE person_by_nickname IS NOT NULL; + + person_by_nickname:= NULL; + INSERT INTO t1 VALUES ('1'); + DELETE FROM t1 WHERE person_by_nickname IS NULL; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +DECLARE + TYPE first_names_t IS TABLE OF VARCHAR2(64) INDEX BY VARCHAR2(20); + first_names first_names_t; + nick VARCHAR(64):= 'Monty'; +BEGIN + first_names('Monty') := 'Michael'; + + INSERT INTO t1 VALUES (first_names('Monty')); + INSERT INTO t1 VALUES (first_names(nick)); + + INSERT INTO t1 VALUES (first_names(TRIM(nick || ' '))); + INSERT INTO t1 VALUES (first_names(TRIM(first_names.LAST))); + + SELECT * FROM t1; + + CREATE TABLE t2 AS SELECT first_names('Monty'); +END; +$$ +DELIMITER ;$$ + +DROP TABLE t1; +DROP TABLE t2; + +--let $binlog_file = LAST +source include/show_binlog_events.inc; diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.result b/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.result new file mode 100644 index 00000000000..1315a642560 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.result @@ -0,0 +1,75 @@ +include/master-slave.inc +[connection master] +# +# MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +# +SET sql_mode=oracle; +SET NAMES utf8; +connection master; +CREATE TABLE t1 (a VARCHAR(64)); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +INSERT INTO t1 VALUES (person_by_nickname.FIRST); +INSERT INTO t1 VALUES (person_by_nickname('Monty').first_name); +END; +$$ +connection slave; +SELECT a FROM t1; +a +Monty +Michael +connection master; +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +DELETE FROM t1 WHERE person_by_nickname IS NOT NULL; +END; +$$ +connection slave; +SELECT a FROM t1; +a +connection master; +DECLARE +TYPE first_names_t IS TABLE OF VARCHAR2(64) INDEX BY VARCHAR2(20); +first_names first_names_t; +`first names` first_names_t; +nick VARCHAR(64):= 'Monty'; +BEGIN +first_names('Monty') := 'Michael'; +`first names`('Alex') := 'Alexander'; +INSERT INTO t1 VALUES (first_names('Monty')); +INSERT INTO t1 VALUES (first_names(nick)); +INSERT INTO t1 VALUES (first_names(TRIM(nick || ' '))); +INSERT INTO t1 VALUES (first_names(TRIM(first_names.LAST))); +INSERT INTO t1 VALUES (`first names`(`first names`.LAST)); +CREATE TABLE t2 AS SELECT first_names('Monty'); +END; +$$ +connection slave; +SELECT a FROM t1; +a +Michael +Michael +Michael +Michael +Alexander +connection master; +DROP TABLE t1; +DROP TABLE t2; +connection slave; +include/rpl_end.inc diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.test b/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.test new file mode 100644 index 00000000000..637dc3ec791 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/rpl_sp-assoc-array.test @@ -0,0 +1,87 @@ +--source include/master-slave.inc + +--echo # +--echo # MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +--echo # + +SET sql_mode=oracle; +SET NAMES utf8; + +connection master; +CREATE TABLE t1 (a VARCHAR(64)); + +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + + INSERT INTO t1 VALUES (person_by_nickname.FIRST); + INSERT INTO t1 VALUES (person_by_nickname('Monty').first_name); +END; +$$ +DELIMITER ;$$ + +sync_slave_with_master; +SELECT a FROM t1; +connection master; + +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + + DELETE FROM t1 WHERE person_by_nickname IS NOT NULL; +END; +$$ +DELIMITER ;$$ + +sync_slave_with_master; +SELECT a FROM t1; + +connection master; +DELIMITER $$; +DECLARE + TYPE first_names_t IS TABLE OF VARCHAR2(64) INDEX BY VARCHAR2(20); + first_names first_names_t; + `first names` first_names_t; + nick VARCHAR(64):= 'Monty'; +BEGIN + first_names('Monty') := 'Michael'; + `first names`('Alex') := 'Alexander'; + + INSERT INTO t1 VALUES (first_names('Monty')); + INSERT INTO t1 VALUES (first_names(nick)); + + INSERT INTO t1 VALUES (first_names(TRIM(nick || ' '))); + INSERT INTO t1 VALUES (first_names(TRIM(first_names.LAST))); + + INSERT INTO t1 VALUES (`first names`(`first names`.LAST)); + + CREATE TABLE t2 AS SELECT first_names('Monty'); +END; +$$ +DELIMITER ;$$ + +sync_slave_with_master; +SELECT a FROM t1; + +connection master; +DROP TABLE t1; +DROP TABLE t2; +sync_slave_with_master; + +--source include/rpl_end.inc diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-code.result b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-code.result new file mode 100644 index 00000000000..21163ca38eb --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-code.result @@ -0,0 +1,107 @@ +SET sql_mode=ORACLE; +# +# MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +# +CREATE PROCEDURE p1() AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +marks marks_t:= marks_t('1' => 43, '2' => 99); +BEGIN +marks(1) := 62; +SELECT marks(1); +END; +$$ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set marks@0 marks_t('1'=>43,'2'=>99) +1 set marks@0[1] 62 +2 stmt 0 "SELECT marks(1)" +3 destruct associative_array marks@0 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() AS +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t:= +table_of_peson_t( +'Monty' => person_t('Michael', 'Widenius'), +'Serg' => person_t('Sergei ', 'Golubchik')) ; +nick VARCHAR(20); +BEGIN +nick:= person_by_nickname.FIRST; +person_by_nickname(nick).first_name:= 'Michael'; +person_by_nickname(nick):= person_t('Michael', 'Widenius'); +END; +$$ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set person_by_nickname@0 table_of_peson_t('Monty'=>('Michael','Widenius'),'Serg'=>('Sergei ','Golubchik')) +1 set nick@1 NULL +2 set nick@1 person_by_nickname@0.first() +3 set person_by_nickname@0[nick@1].first_name 'Michael' +4 set person_by_nickname@0[nick@1] ('Michael','Widenius') +5 destruct associative_array person_by_nickname@0 +DROP PROCEDURE p1; +# +# Make sure assoc array variables generate sp_instr_destruct_variable +# +CREATE PROCEDURE p1 AS +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +TYPE table_of_int_t IS TABLE OF INT INDEX BY INT; +BEGIN +SELECT '>block#0' AS comment; +DECLARE +assoc_of_record_0 table_of_peson_t; +assoc_of_scalar_0 table_of_int_t; +BEGIN +SELECT '>block#1' AS comment; +DECLARE +assoc_of_record_1 table_of_peson_t; +assoc_of_scalar_1 table_of_int_t; +BEGIN +SELECT '>block#2' AS comment; +DECLARE +assoc_of_record_2 table_of_peson_t; +assoc_of_scalar_2 table_of_int_t; +BEGIN +SELECT '>block#3' AS comment; +NULL; +SELECT 'block#0' AS comment" +1 set assoc_of_record_0@0 NULL +2 set assoc_of_scalar_0@1 NULL +3 stmt 0 "SELECT '>block#1' AS comment" +4 set assoc_of_record_1@2 NULL +5 set assoc_of_scalar_1@3 NULL +6 stmt 0 "SELECT '>block#2' AS comment" +7 set assoc_of_record_2@4 NULL +8 set assoc_of_scalar_2@5 NULL +9 stmt 0 "SELECT '>block#3' AS comment" +10 stmt 0 "SELECT ' 43, '2' => 99); +BEGIN + marks(1) := 62; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ +SHOW PROCEDURE CODE p1; +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1() AS + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t:= + table_of_peson_t( + 'Monty' => person_t('Michael', 'Widenius'), + 'Serg' => person_t('Sergei ', 'Golubchik')) ; + nick VARCHAR(20); +BEGIN + nick:= person_by_nickname.FIRST; + person_by_nickname(nick).first_name:= 'Michael'; + + person_by_nickname(nick):= person_t('Michael', 'Widenius'); +END; +$$ +DELIMITER ;$$ +SHOW PROCEDURE CODE p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Make sure assoc array variables generate sp_instr_destruct_variable +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + TYPE table_of_int_t IS TABLE OF INT INDEX BY INT; +BEGIN + SELECT '>block#0' AS comment; + DECLARE + assoc_of_record_0 table_of_peson_t; + assoc_of_scalar_0 table_of_int_t; + BEGIN + SELECT '>block#1' AS comment; + DECLARE + assoc_of_record_1 table_of_peson_t; + assoc_of_scalar_1 table_of_int_t; + BEGIN + SELECT '>block#2' AS comment; + DECLARE + assoc_of_record_2 table_of_peson_t; + assoc_of_scalar_2 table_of_int_t; + BEGIN + SELECT '>block#3' AS comment; + NULL; + SELECT ' person_t('Michael', 'Widenius'), +'Serg' => person_t('Sergei ', 'Golubchik')) ; +nick VARCHAR(20); +BEGIN +nick:= person_by_nickname.FIRST; +WHILE nick IS NOT NULL +LOOP +SELECT +nick, person_by_nickname(nick).first_name, +person_by_nickname(nick).last_name +FROM DUAL; +nick:= person_by_nickname.NEXT(nick); +END LOOP; +END; +$$ +nick person_by_nickname(nick).first_name person_by_nickname(nick).last_name +Monty Michael Widenius +nick person_by_nickname(nick).first_name person_by_nickname(nick).last_name +Serg Sergei Golubchik +# +# SCALAR element type +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +id INTEGER; +BEGIN +marks(0) := 62; +marks(1) := 78; +id:= marks.FIRST; +WHILE id IS NOT NULL +LOOP +SELECT marks(id), id; +id:= marks.NEXT(id); +END LOOP; +SELECT marks(0) + marks(1) as total, POW(marks(0), 2), POW(marks(1), 2); +marks(1) := NULL; +SELECT marks(1); +END; +$$ +marks(id) id +62 0 +marks(id) id +78 1 +total POW(marks(0), 2) POW(marks(1), 2) +140 3844 6084 +marks(1) +NULL +# +# SCALAR element type with initialization +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); +id INTEGER; +BEGIN +id:= marks.FIRST; +WHILE id IS NOT NULL +LOOP +SELECT marks(id), id; +id:= marks.NEXT(id); +END LOOP; +END; +$$ +marks(id) id +62 1 +marks(id) id +78 2 +marks(id) id +99 3 +# +# SCALAR element type with initialization (2) +# NUMBER element type initialized with string +# On Oracle error PLS-00306 will be raised +# In this implementation we just convert +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); +id INTEGER; +BEGIN +id:= marks.FIRST; +WHILE id IS NOT NULL +LOOP +SELECT marks(id), id; +id:= marks.NEXT(id); +END LOOP; +END; +$$ +marks(id) id +62 1 +marks(id) id +78 2 +marks(id) id +99 3 +# +# Initialization without named association +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(62, 78, 99); +id INTEGER; +BEGIN +NULL; +END; +$$ +ERROR HY000: Initializing marks_t requires named association +# +# Initialization without named association (2) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 62, 2 => 78, 99); +id INTEGER; +BEGIN +NULL; +END; +$$ +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 '); +id INTEGER; +BEGIN +NULL; +END' at line 3 +# +# Wrong use: assoc_array_var.x(1) +# +CREATE PROCEDURE p1 AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks.x(1):= 10; +SELECT marks(1); +END; +$$ +ERROR HY000: Illegal parameter data type associative_array for operation '`marks`.`x`(..)' +# +# Wrong use: assoc_array_var.x.y(1) +# +CREATE PROCEDURE p1 AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks.x.y(1):= 10; +SELECT marks(1); +END; +$$ +ERROR HY000: Illegal parameter data type associative_array for operation '`marks`.`x`.`y`(..)' +# +# Wrong use: assoc_array_var.x.y.z(1) +# +CREATE PROCEDURE p1 AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks.x.y.z(1):= 10; +SELECT marks(1); +END; +$$ +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 'marks.x.y.z(1):= 10; +SELECT marks(1); +END' at line 5 +# +# Initialization with empty elements +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(); +id INTEGER; +BEGIN +SELECT marks.first; +END; +$$ +marks.first +NULL +# +# Initialization with duplicate key +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 1, 2 => 2, 1 => 3); +id INTEGER; +BEGIN +SELECT marks.first; +END; +$$ +ERROR 23000: Duplicate entry for key '1' +# +# RECORD array assignment +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t:= +table_of_peson_t( +'Monty' => person_t('Michael', 'Widenius'), +'Serg' => person_t('Sergei ', 'Golubchik')) ; +person_by_nickname_copy table_of_peson_t; +nick VARCHAR(20); +BEGIN +person_by_nickname_copy:= person_by_nickname; +nick:= person_by_nickname_copy.FIRST; +WHILE nick IS NOT NULL +LOOP +SELECT +nick, person_by_nickname_copy(nick).first_name, +person_by_nickname_copy(nick).last_name +FROM DUAL; +nick:= person_by_nickname_copy.NEXT(nick); +END LOOP; +person_by_nickname_copy('Alex'):= NULL; +SELECT person_by_nickname_copy('Alex').first_name; +person_by_nickname_copy('Alex'):= person_by_nickname('Monty'); +SELECT person_by_nickname_copy('Alex').first_name; +END; +$$ +nick person_by_nickname_copy(nick).first_name person_by_nickname_copy(nick).last_name +Monty Michael Widenius +nick person_by_nickname_copy(nick).first_name person_by_nickname_copy(nick).last_name +Serg Sergei Golubchik +person_by_nickname_copy('Alex').first_name +NULL +person_by_nickname_copy('Alex').first_name +Michael +# +# SCALAR array assignment +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); +marks2 marks_t; +id INTEGER; +BEGIN +marks2:= marks; +id:= marks2.FIRST; +WHILE id IS NOT NULL +LOOP +SELECT marks2(id), id; +id:= marks2.NEXT(id); +END LOOP; +END; +$$ +marks2(id) id +62 1 +marks2(id) id +78 2 +marks2(id) id +99 3 +# +# SCALAR array assignment with differing element types +# Oracle do not allow this (PLS-00382: expression is of wrong type) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +TYPE names_t IS TABLE OF VARCHAR(64) INDEX BY VARCHAR2(20); +marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); +names2 names_t:= names_t('1' => 'Klaus', '2' => 'Lee', '3' => 'Arun'); +id INTEGER; +BEGIN +marks:= names2; +id:= marks.FIRST; +WHILE id IS NOT NULL +LOOP +id:= marks.NEXT(id); +END LOOP; +END; +$$ +Warnings: +Warning 1366 Incorrect double value: 'Klaus' for column ``.``.`` at row 0 +Warning 1366 Incorrect double value: 'Lee' for column ``.``.`` at row 0 +Warning 1366 Incorrect double value: 'Arun' for column ``.``.`` at row 0 +# +# Anchored ROWTYPE for array element +# +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30) +); +DECLARE +TYPE depts_t IS TABLE OF t1%ROWTYPE INDEX BY INTEGER; +depts depts_t; +t INTEGER; +BEGIN +depts(666666) := ROW(1, 'HR'); +depts(777777) := ROW(2, 'Accounting'); +t:= depts.FIRST; +WHILE t IS NOT NULL +LOOP +SELECT depts(t).dept_name, t; +t:= depts.NEXT(t); +END LOOP; +END; +$$ +depts(t).dept_name t +HR 666666 +depts(t).dept_name t +Accounting 777777 +DROP TABLE t1; +# +# Anchored column TYPE for array element +# +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30) +); +DECLARE +TYPE depts_t IS TABLE OF t1.dept_name%TYPE INDEX BY INTEGER; +depts depts_t; +t INTEGER; +BEGIN +depts(666666) := 'HR'; +depts(777777) := 'Accounting'; +depts(-1) := 'Engineering'; +t:= depts.FIRST; +WHILE t IS NOT NULL +LOOP +SELECT depts(t), t; +t:= depts.NEXT(t); +END LOOP; +END; +$$ +depts(t) t +Engineering -1 +depts(t) t +HR 666666 +depts(t) t +Accounting 777777 +DROP TABLE t1; +# +# Retrieve keys from uninitialized array +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +nick VARCHAR2(20); +BEGIN +nick:= person_by_nickname.FIRST; +SELECT nick FROM DUAL; +nick:= person_by_nickname.LAST; +SELECT nick FROM DUAL; +nick:= person_by_nickname.NEXT(nick); +SELECT nick FROM DUAL; +END; +$$ +nick +NULL +nick +NULL +nick +NULL +# +# Retrieve keys from initialized array +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +nick VARCHAR2(20); +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +person_by_nickname('Serg') := person_t('Sergei ', 'Golubchik'); +person_by_nickname('Zam') := person_t('Kitamura ', 'Motoyasu'); +nick:= person_by_nickname.LAST; +SELECT nick FROM DUAL; +nick:= person_by_nickname.FIRST; +SELECT nick FROM DUAL; +nick:= person_by_nickname.NEXT(nick); +SELECT nick FROM DUAL; +END; +$$ +nick +Zam +nick +Monty +nick +Serg +# +# NEXT +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.NEXT(marks.FIRST), marks.NEXT(marks.LAST), marks.NEXT(NULL), +marks.next(marks.FIRST); +END; +$$ +marks.NEXT(marks.FIRST) marks.NEXT(marks.LAST) marks.NEXT(NULL) marks.next(marks.FIRST) +1 NULL NULL 1 +# +# NEXT, argument count error 1 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.NEXT(); +END; +$$ +ERROR 42000: Incorrect number of arguments for NEXT ; expected 1, got 0 +# +# NEXT, argument count error 2 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.NEXT(0, 1); +END; +$$ +ERROR 42000: Incorrect number of arguments for NEXT ; expected 1, got 2 +# +# NEXT, NULL array +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT marks.NEXT(0); +END; +$$ +marks.NEXT(0) +NULL +# +# NEXT on scalar +# +DECLARE +marks INTEGER; +BEGIN +SELECT marks.NEXT(1); +END; +$$ +ERROR 42000: FUNCTION marks.NEXT does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual +# +# PRIOR +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.PRIOR(marks.LAST), marks.PRIOR(marks.FIRST), marks.PRIOR(NULL), +marks.prior(marks.LAST); +END; +$$ +marks.PRIOR(marks.LAST) marks.PRIOR(marks.FIRST) marks.PRIOR(NULL) marks.prior(marks.LAST) +0 NULL NULL 0 +# +# PRIOR, argument count error 1 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.PRIOR(); +END; +$$ +ERROR 42000: Incorrect number of arguments for PRIOR ; expected 1, got 0 +# +# PRIOR, argument count error 2 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.PRIOR(0, 1); +END; +$$ +ERROR 42000: Incorrect number of arguments for PRIOR ; expected 1, got 2 +# +# PRIOR, NULL array +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT marks.PRIOR(0); +END; +$$ +marks.PRIOR(0) +NULL +# +# PRIOR on scalar +# +DECLARE +marks INTEGER; +BEGIN +SELECT marks.PRIOR(1); +END; +$$ +ERROR 42000: FUNCTION marks.PRIOR does not exist +# +# EXISTS +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +SELECT marks.EXISTS(1), marks.EXISTS(4), +marks.exists(1); +END; +$$ +marks.EXISTS(1) marks.EXISTS(4) marks.exists(1) +1 0 1 +# +# EXISTS, argument count error +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +SELECT marks.EXISTS(); +END; +$$ +ERROR 42000: Incorrect number of arguments for EXISTS ; expected 1, got 0 +# +# EXISTS, NULL key +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +SELECT marks.EXISTS(NULL); +END; +$$ +marks.EXISTS(NULL) +0 +# +# EXISTS on scalar +# +DECLARE +marks INTEGER; +BEGIN +SELECT marks.EXISTS(1); +END; +$$ +ERROR 42000: FUNCTION marks.EXISTS does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual +# +# EXISTS, NULL array +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT marks.EXISTS(4); +END; +$$ +marks.EXISTS(4) +0 +# +# DELETE +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +marks(2) := 99; +SELECT marks.DELETE(1); +SELECT marks.EXISTS(0), marks.EXISTS(1), marks.EXISTS(2); +SELECT marks.DELETE; +SELECT marks.EXISTS(0), marks.EXISTS(1), marks.EXISTS(2); +END; +$$ +marks.DELETE(1) +0 +marks.EXISTS(0) marks.EXISTS(1) marks.EXISTS(2) +1 0 1 +marks.DELETE +0 +marks.EXISTS(0) marks.EXISTS(1) marks.EXISTS(2) +0 0 0 +# +# DELETE, argument count error +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +marks(2) := 99; +SELECT marks.DELETE(1, 2); +END; +$$ +ERROR 42000: Incorrect number of arguments for DELETE ; expected 1, got 2 +# +# DELETE, NULL key +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +SELECT marks.DELETE(NULL); +END; +$$ +marks.DELETE(NULL) +0 +# +# DELETE on scalar +# +DECLARE +marks INTEGER; +BEGIN +SELECT marks.DELETE(1); +END; +$$ +ERROR 42000: FUNCTION marks.DELETE does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual +# +# COUNT +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(0) := 62; +marks(1) := 78; +marks(2) := 99; +SELECT marks.COUNT; +marks(3) := 44; +SELECT marks.COUNT; +SELECT marks.DELETE(1); +SELECT marks.COUNT; +END; +$$ +marks.COUNT +3 +marks.COUNT +4 +marks.DELETE(1) +0 +marks.COUNT +3 +# +# COUNT on scalar +# +DECLARE +marks INTEGER; +BEGIN +SELECT marks.COUNT; +END; +$$ +ERROR 42S02: Unknown table 'marks' in SELECT +# +# VARCHAR2 key length error +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(10); +marks marks_t; +BEGIN +marks('Clementine Montgomery') := 99; +END; +$$ +ERROR 42000: Specified key was too long; max key length is 10 bytes +# +# VARCHAR2 key fix +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(10); +marks marks_t; +BEGIN +marks('Cle' || 'mentine') := 99; +SELECT marks('Cleme' || 'ntine'); +END; +$$ +marks('Cleme' || 'ntine') +99 +# +# Key collation, case insensitive, accent insensitive +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_ci; +marks marks_t; +id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN +marks(id) := 62; +id:= 'steve'; +SELECT marks(id); +END; +$$ +marks(id) +62 +# +# Key collation, case sensitive, accent insensitive (1) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_cs; +marks marks_t; +id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN +marks(id) := 62; +SELECT marks('Steve'); +END; +$$ +marks('Steve') +62 +# +# Key collation, case sensitive, accent insensitive (2) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_cs; +marks marks_t; +id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN +marks(id) := 62; +SELECT marks('steve'); +END; +$$ +ERROR HY000: Element not found with key 'steve' +# +# Key ascii charset +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4; +marks marks_t; +id VARCHAR2(20) CHARACTER SET ascii:= 'Stéve'; +BEGIN +marks('Stéve') := 62; +SELECT marks(id); +END; +$$ +ERROR HY000: Element not found with key 'St?ve' +# +# VARCHAR2 key with numeric key retrival +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +marks marks_t; +BEGIN +marks('1') := 62; +SELECT marks(1); +END; +$$ +marks(1) +62 +# +# INTEGER key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(-2147483649) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-2147483649' +# +# INTEGER key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(2147483648) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '2147483648' +# +# INTEGER UNSIGNED key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; +marks marks_t; +BEGIN +marks(-1) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-1' +# +# INTEGER UNSIGNED key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; +marks marks_t; +BEGIN +marks(4294967296) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '4294967296' +# +# INTEGER UNSIGNED key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; +marks marks_t; +BEGIN +marks(4294967295) := 62; +SELECT marks(4294967295); +END; +$$ +marks(4294967295) +62 +# +# TINYINT key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; +marks marks_t; +BEGIN +marks(-129) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-129' +# +# TINYINT key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; +marks marks_t; +BEGIN +marks(128) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '128' +# +# TINYINT key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; +marks marks_t; +BEGIN +marks(0) := 62; +END; +$$ +# +# TINYINT UNSIGNED key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; +marks marks_t; +BEGIN +marks(-1) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-1' +# +# TINYINT UNSIGNED key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; +marks marks_t; +BEGIN +marks(256) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '256' +# +# TINYINT UNSIGNED key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; +marks marks_t; +BEGIN +marks(255) := 62; +SELECT marks(255); +END; +$$ +marks(255) +62 +# +# SMALLINT key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; +marks marks_t; +BEGIN +marks(-32769) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-32769' +# +# SMALLINT key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; +marks marks_t; +BEGIN +marks(32768) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '32768' +# +# SMALLINT key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; +marks marks_t; +BEGIN +marks(0) := 62; +END; +$$ +# +# SMALLINT UNSIGNED key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; +marks marks_t; +BEGIN +marks(-1) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-1' +# +# SMALLINT UNSIGNED key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; +marks marks_t; +BEGIN +marks(65536) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '65536' +# +# SMALLINT UNSIGNED key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; +marks marks_t; +BEGIN +marks(65535) := 62; +SELECT marks(65535); +END; +$$ +marks(65535) +62 +# +# MEDIUMINT key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; +marks marks_t; +BEGIN +marks(-8388609) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-8388609' +# +# MEDIUMINT key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; +marks marks_t; +BEGIN +marks(8388608) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '8388608' +# +# MEDIUMINT key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; +marks marks_t; +BEGIN +marks(0) := 62; +END; +$$ +# +# MEDIUMINT UNSIGNED key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; +marks marks_t; +BEGIN +marks(-1) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-1' +# +# MEDIUMINT UNSIGNED key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; +marks marks_t; +BEGIN +marks(16777216) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '16777216' +# +# MEDIUMINT UNSIGNED key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; +marks marks_t; +BEGIN +marks(16777215) := 62; +SELECT marks(16777215); +END; +$$ +marks(16777215) +62 +# +# BIGINT key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; +marks marks_t; +BEGIN +marks(-9223372036854775809) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-9223372036854775809' +# +# BIGINT key, range (max) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; +marks marks_t; +BEGIN +marks(9223372036854775808) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '9223372036854775808' +# +# BIGINT key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; +marks marks_t; +BEGIN +marks(0) := 62; +END; +$$ +# +# BIGINT UNSIGNED key, range (min) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT UNSIGNED; +marks marks_t; +BEGIN +marks(-1) := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: '-1' +# +# BIGINT UNSIGNED key, range (valid) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT UNSIGNED; +marks marks_t; +BEGIN +marks(18446744073709551615) := 62; +SELECT marks(18446744073709551615); +END; +$$ +marks(18446744073709551615) +62 +# +# INTEGER key, sort order +# +DECLARE +TYPE depts_t IS TABLE OF VARCHAR2(30) INDEX BY INTEGER; +depts depts_t; +t INTEGER; +BEGIN +depts(999) := 'Security'; +depts(99) := 'HR'; +depts(9) := 'Accounting'; +depts(-9) := 'Engineering'; +depts(-99) := 'Marketing'; +depts(-999) := 'Sales'; +t:= depts.FIRST; +WHILE t IS NOT NULL +LOOP +SELECT depts(t), t; +t:= depts.NEXT(t); +END LOOP; +END; +$$ +depts(t) t +Sales -999 +depts(t) t +Marketing -99 +depts(t) t +Engineering -9 +depts(t) t +Accounting 9 +depts(t) t +HR 99 +depts(t) t +Security 999 +# +# Key numeric, character access (convertable to integer) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(1) := 62; +SELECT marks('01'); +END; +$$ +marks('01') +62 +# +# Key numeric, character access +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks('Test') := 62; +END; +$$ +ERROR HY000: Incorrect ASSOCIATIVE ARRAY KEY value: 'Test' +# +# NULL key when assigning variable +# On Oracle ORA-06502 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(NULL):= 62; +END; +$$ +ERROR HY000: NULL key used for associative array 'marks' +# +# NULL key when accessing elements +# On Oracle ORA-06502 +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(1):= 62; +SELECT marks(NULL); +END; +$$ +ERROR HY000: NULL key used for associative array 'marks' +# +# NULL key when assigning variable +# On Oracle ORA-06502 +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +p table_of_person_t; +BEGIN +p(NULL).first_name:= 'aa'; +END; +$$ +ERROR HY000: NULL key used for associative array 'p' +# +# NULL key when accessing elements +# On Oracle ORA-06502 +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +p table_of_person_t; +BEGIN +p('cc'):= person_t('aa', 'bb'); +SELECT p(NULL).first_name; +END; +$$ +ERROR HY000: NULL key used for associative array 'p' +# +# Nested tables (INDEX BY is not specified) +# This is not supported yet +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER; +marks marks_t:= marks_t(62, 78, 99); +id INTEGER; +BEGIN +marks:= names2; +id:= marks.FIRST; +WHILE id IS NOT NULL +LOOP +-- SELECT marks(id), id; +id:= marks.NEXT(id); +END LOOP; +END; +$$ +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 '; +marks marks_t:= marks_t(62, 78, 99); +id INTEGER; +BEGIN +marks:= names2; +id:=...' at line 2 +# +# Element access for scalar variable +# +DECLARE +marks INTEGER; +BEGIN +marks(1):= 62; +END; +$$ +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 ':= 62; +END' at line 4 +# +# Element assignment with wrong argument count +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(1, 2):= 62; +END; +$$ +ERROR 42000: Incorrect number of arguments for ASSOC_ARRAY KEY .; expected 1, got 2 +# +# Element assignment with wrong argument count (2) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks():= 62; +END; +$$ +ERROR 42000: Incorrect number of arguments for ASSOC_ARRAY KEY .; expected 1, got 0 +# +# Element access with wrong argument count +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(1):= 62; +SELECT marks(1, 2); +END; +$$ +ERROR 42000: Incorrect number of arguments for ASSOC_ARRAY_ELEMENT .; expected 1, got 2 +# +# Element access with wrong argument count (2) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT marks(); +END; +$$ +ERROR 42000: Incorrect number of arguments for ASSOC_ARRAY_ELEMENT .; expected 1, got 0 +# +# Non-existant field access +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +SELECT person_by_nickname('Monty').email; +END; +$$ +ERROR HY000: Row variable 'person_by_nickname' does not have a field 'email' +# +# Field access on array with scalar element +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(1) := 1; +SELECT marks(1).name; +END; +$$ +ERROR 42S22: Unknown column '1' in 'SELECT' +# +# Field assignment +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +BEGIN +person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); +person_by_nickname('Serg') := person_t('Sergei ', 'Golubchik'); +person_by_nickname('Monty').first_name:= 'Mike'; +SELECT person_by_nickname('Monty').first_name, +person_by_nickname('Monty').last_name; +SELECT person_by_nickname('Serg').first_name, +person_by_nickname('Serg').last_name; +person_by_nickname('Serg').first_name:= NULL; +SELECT person_by_nickname('Serg').first_name, person_by_nickname('Serg').first_name IS NULL; +END; +$$ +person_by_nickname('Monty').first_name person_by_nickname('Monty').last_name +Mike Widenius +person_by_nickname('Serg').first_name person_by_nickname('Serg').last_name +Sergei Golubchik +person_by_nickname('Serg').first_name person_by_nickname('Serg').first_name IS NULL +NULL 1 +# +# Field access - non-existant key +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +BEGIN +SELECT person_by_nickname('Monty').first_name; +END; +$$ +ERROR HY000: Element not found with key 'Monty' +# +# Field assignment - non-existant key +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +BEGIN +person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); +person_by_nickname('Jeff').nick_name:= 'Mike'; +SELECT person_by_nickname('Monty').first_name, +person_by_nickname('Monty').last_name; +END; +$$ +ERROR HY000: Element not found with key 'Jeff' +# +# Field assignment - non existant field +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_person_t; +BEGIN +person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); +person_by_nickname('Monty').nick_name:= 'Mike'; +SELECT person_by_nickname('Monty').first_name, +person_by_nickname('Monty').last_name; +END; +$$ +ERROR HY000: Row variable 'person_by_nickname' does not have a field 'nick_name' +# +# Key access using another array's element +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +marks marks_t; +fname VARCHAR2(20); +TYPE hist_t IS TABLE OF NUMBER INDEX BY INTEGER; +hist hist_t; +hist_bin INTEGER; +BEGIN +marks('Steve') := 62; +marks('Mark') := 77; +marks('Lee') := 79; +marks('John') := 31; +fname:= marks.FIRST; +WHILE fname IS NOT NULL +LOOP +IF hist.EXISTS(FLOOR(marks(fname) / 10)) THEN +hist(FLOOR(marks(fname) / 10)):= hist(FLOOR(marks(fname) / 10)) + 1; +ELSE +hist(FLOOR(marks(fname) / 10)):= 1; +END IF; +fname:= marks.NEXT(fname); +END LOOP; +hist_bin:= hist.FIRST; +WHILE hist_bin IS NOT NULL +LOOP +SELECT hist_bin, hist(hist_bin); +hist_bin:= hist.NEXT(hist_bin); +END LOOP; +END; +$$ +hist_bin hist(hist_bin) +3 1 +hist_bin hist(hist_bin) +6 1 +hist_bin hist(hist_bin) +7 2 +# +# ASSOC ARRAY type used in a stored PROCEDURE +# +CREATE PROCEDURE p1(v NUMBER) AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +marks marks_t; +BEGIN +marks(1) := 62; +SELECT marks(1); +END; +$$ +CALL p1(4); +marks(1) +62 +DROP PROCEDURE p1; +# +# ASSOC ARRAY type used in a stored FUNCTION +# +CREATE FUNCTION f1(v NUMBER) +RETURN NUMBER IS +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +marks marks_t; +BEGIN +marks(1) := 62; +RETURN marks(1); +END; +$$ +SELECT f1(4); +f1(4) +62 +DROP FUNCTION f1; +# +# Accessing member fields from constructor should fail +# for RECORD type +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +first_name VARCHAR(64):= person_t('First', 'Last').first_name; +BEGIN +SELECT first_name; +END; +$$ +ERROR 42S22: Unknown column 'first_name' in 'person_t' +# +# Accessing member fields from constructor should fail +# for ASSOC ARRAY type +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +first_name VARCHAR(64):= table_of_person_t('1' => person_t('First', 'Last')).first_name; +BEGIN +SELECT first_name; +END; +$$ +ERROR 42S22: Unknown column 'first_name' in 'table_of_person_t' +# +# Selecting assoc array +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 AS '1'); +BEGIN +SELECT marks; +END; +$$ +ERROR 21000: Operand should contain 1 column(s) +# +# Assoc array compare - scalar element +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 => 1); +prev_marks marks_t:=marks_t(1 => 1); +BEGIN +IF marks = prev_marks THEN +SELECT 'Equal'; +END IF; +END; +$$ +ERROR 21000: Operand should contain 1 column(s) +# +# Assoc array compare - record element +# +DECLARE +TYPE subjects_t IS RECORD +( +english INTEGER, +math INTEGER +); +TYPE marks_t IS TABLE OF subjects_t INDEX BY VARCHAR2(64); +marks marks_t:=marks_t('Nur' => subjects_t(80, 90)); +prev_marks marks_t:=marks_t('Nur' => subjects_t(75, 69)); +BEGIN +IF marks = prev_marks THEN +SELECT 'Equal'; +END IF; +END; +$$ +ERROR 21000: Operand should contain 1 column(s) +# +# Assoc array SUM +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN +SELECT SUM(marks); +END; +$$ +ERROR 21000: Operand should contain 1 column(s) +# +# Assoc array IN +# +CREATE TABLE t1 (a INT); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN +SELECT marks(2) IN (2, 3); +END; +$$ +marks(2) IN (2, 3) +1 +DROP TABLE t1; +# +# Assoc array IN (2) +# +CREATE TABLE t1 (a INT); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +persons('Serg') := person_t('Sergei ', 'Golubchik'); +SELECT persons('Monty') IN (person_t('Michael', 'Widenius')); +END; +$$ +persons('Monty') IN (person_t('Michael', 'Widenius')) +1 +DROP TABLE t1; +# +# Assoc array IN (2) +# +CREATE TABLE t1 (a INT); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +persons('Serg') := person_t('Sergei ', 'Golubchik'); +SELECT persons('Monty').first_name IN ('Michael', 'Sergei '); +SELECT persons('Monty').last_name IN ('Michael', 'Sergei '); +END; +$$ +persons('Monty').first_name IN ('Michael', 'Sergei ') +1 +persons('Monty').last_name IN ('Michael', 'Sergei ') +0 +DROP TABLE t1; +# +# IN Assoc array element +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN +SELECT * FROM t1 WHERE a IN (marks(2), marks(3)); +END; +$$ +a +2 +3 +DROP TABLE t1; +# +# IN Assoc array, not going to support this, test just +# to ensure that we don't crash the server +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN +SELECT 2 IN (marks); +END; +$$ +ERROR HY000: Illegal parameter data types int and associative_array for operation '=' +# +# SELECT .. INTO spvar_assoc_array +# +CREATE PROCEDURE p1 AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT 1 INTO marks; +END; +$$ +CALL p1; +ERROR 21000: The used SELECT statements have a different number of columns +DROP PROCEDURE p1; +CREATE PROCEDURE p1 AS +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT 1,2 INTO marks; +END; +$$ +CALL p1; +ERROR 21000: The used SELECT statements have a different number of columns +DROP PROCEDURE p1; +# +# SELECT INTO ASSOC ARRAY scalar element +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT * FROM t1 WHERE a = 2 INTO marks(200); +SELECT marks(200); +END; +$$ +marks(200) +2 +DROP TABLE t1; +# +# SELECT INTO ASSOC ARRAY non-scalar element +# +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +SELECT * FROM t1 WHERE first_name = 'Anwar' INTO persons('Nuau'); +SELECT persons('Nuau').last_name; +END; +$$ +persons('Nuau').last_name +Ibrahim +DROP TABLE t1; +# +# SELECT INTO ASSOC ARRAY for a field of a non-scalar element +# +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +SELECT * FROM t1 WHERE first_name = 'Anwar' INTO persons('Nuau'); +SELECT * FROM t1 WHERE first_name= 'Muhyiddin' INTO persons('Muhyiddin'); +SELECT 'ibn Arabi' INTO persons('Muhyiddin').last_name; +SELECT persons('Muhyiddin').first_name, persons('Muhyiddin').last_name; +END; +$$ +persons('Muhyiddin').first_name persons('Muhyiddin').last_name +Muhyiddin ibn Arabi +DROP TABLE t1; +# +# SELECT INTO ASSOC ARRAY for a field of a non-scalar element +# for a non-existing key +# +CREATE PROCEDURE p1 IS +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +SELECT 'ibn Arabi' INTO persons('Muhyiddin').last_name; +SELECT persons('Muhyiddin').first_name, persons('Muhyiddin').last_name; +END; +$$ +CALL p1; +ERROR HY000: Element not found with key 'Muhyiddin' +DROP PROCEDURE p1; +# +# SELECT INTO assoc_array_of_scalars('key').field +# Fails during the CREATE time. +# +CREATE PROCEDURE p1 IS +TYPE assoc_t IS TABLE OF VARCHAR2(20) INDEX BY VARCHAR2(20); +assoc assoc_t; +BEGIN +SELECT 'ibn Arabi' INTO assoc('Muhyiddin').last_name; +END; +$$ +ERROR HY000: Illegal parameter data type varchar for operation '`assoc`(..).`last_name`' +# +# SELECT scalar INTO ASSOC ARRAY non-scalar element +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +SELECT * FROM t1 WHERE a = 3 INTO persons('Nuau'); +SELECT persons('Nuau').last_name; +END; +$$ +ERROR 21000: The used SELECT statements have a different number of columns +DROP TABLE t1; +# +# SELECT non-scalar INTO ASSOC ARRAY scalar element +# +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +SELECT * FROM t1 WHERE first_name = 'Najib' INTO marks(200); +SELECT marks(200); +END; +$$ +ERROR 21000: The used SELECT statements have a different number of columns +DROP TABLE t1; +# +# SELECT scalar INTO scalar variable with non-existant key +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +id INTEGER; +BEGIN +SELECT * FROM t1 WHERE a = 3 INTO id('Nuau'); +END; +$$ +ERROR HY000: Illegal parameter data type int for operation '`id`(..)' +DECLARE +id INTEGER; +BEGIN +EXPLAIN SELECT * FROM t1 WHERE a = 3 INTO id('Nuau'); +END; +$$ +ERROR HY000: Illegal parameter data type int for operation '`id`(..)' +DROP TABLE t1; +# +# SELECT scalar INTO non-existant variable with key +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +id INTEGER; +BEGIN +SELECT * FROM t1 WHERE a = 3 INTO missing_var('Nuau'); +END; +$$ +ERROR 42000: Undeclared variable: missing_var +DROP TABLE t1; +# +# SELECT scalar INTO ASSOC_ARRAY's element field +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +SELECT 'Mike' INTO persons('Monty').first_name; +SELECT persons('Monty').first_name; +END; +$$ +persons('Monty').first_name +Mike +# +# SELECT non-scalar INTO ASSOC ARRAY scalar element's field +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +SELECT 'Mike','Widenius' INTO persons('Monty').first_name; +SELECT persons('Monty').first_name; +END; +$$ +ERROR 21000: The used SELECT statements have a different number of columns +# +# SELECT scalar INTO scalar element's field with non-existant key +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +SELECT 'Mike' INTO persons('Serg').first_name; +END; +$$ +ERROR HY000: Element not found with key 'Serg' +# +# SELECT scalar INTO non-existant variable with key +# +DECLARE +BEGIN +SELECT 'Mike' INTO missing_var('Serg').first_name; +END; +$$ +ERROR 42000: Undeclared variable: missing_var +# +# Use field on scalar element with SELECT .. INTO +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(200):= 88; +SELECT 1 INTO marks(200).non_exist; +END; +$$ +ERROR HY000: Illegal parameter data type double for operation '`marks`(..).`non_exist`' +# +# Use field on scalar variable with SELECT .. INTO +# +DECLARE +marks INTEGER; +BEGIN +SELECT 1 INTO marks(200).non_exist; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation '`marks`(..)' +# +# SELECT scalar INTO scalar variable with non-existant field +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +persons table_of_person_t; +BEGIN +persons('Monty') := person_t('Michael', 'Widenius'); +SELECT 'Mike' INTO persons('Monty').first_namex; +END; +$$ +ERROR 42S22: Unknown column 'first_namex' in 'persons' +SET sql_mode=default; +# +# Basic ASSOC ARRAY, anonymous block sql_mode=default; +# +BEGIN NOT ATOMIC +DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +DECLARE marks marks_t; +END; +$$ +ERROR HY000: Unknown data type: 'marks_t' +# +# Basic ASSOC ARRAY, stored procedure sql_mode=default; +# +CREATE OR REPLACE PROCEDURE p1() +BEGIN +DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +DECLARE marks marks_t; +END; +$$ +ERROR HY000: Unknown data type: 'marks_t' +CALL p1(); +ERROR 42000: PROCEDURE test.p1 does not exist +DROP PROCEDURE p1; +ERROR 42000: PROCEDURE test.p1 does not exist +# +# Basic ASSOC ARRAY, stored function sql_mode=default; +# +CREATE OR REPLACE FUNCTION f1() RETURNS INT +BEGIN +DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +DECLARE marks marks_t; +RETURN marks(1); +END; +$$ +ERROR HY000: Unknown data type: 'marks_t' +SELECT f1(); +ERROR 42000: FUNCTION test.f1 does not exist +DROP FUNCTION f1; +ERROR 42000: FUNCTION test.f1 does not exist +SET sql_mode=ORACLE; +# +# Ensure that nested assoc array types are properly parsed (without crash, etc) +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +TYPE classes_t IS TABLE OF marks_t INDEX BY INTEGER; +BEGIN +NULL; +END; +$$ +ERROR 42000: This version of MariaDB doesn't yet support 'nested associative arrays' +# +# Ensure that nested assoc array types cannot be used for record field +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +TYPE rec_t IS RECORD (a INT, b marks_t); +BEGIN +NULL; +END; +$$ +ERROR HY000: Unknown data type: 'marks_t' +# +# Ensure that DATE element maps correctly to MariaDB +# DATETIME +# +DECLARE +TYPE dates_t IS TABLE OF DATE INDEX BY INTEGER; +dates dates_t:= dates_t(1 => '2021-01-01 10:20:30'); +BEGIN +SELECT dates(1); +END; +$$ +dates(1) +2021-01-01 10:20:30 +# +# Multiple variable declaration +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +m1,m2,m3 marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); +id INTEGER; +BEGIN +id:= m1.FIRST; +WHILE id IS NOT NULL +LOOP +SELECT m1(id) || m2(id) || m3(id) AS m; +id:= m1.NEXT(id); +END LOOP; +END; +$$ +m +626262 +m +787878 +m +999999 +# +# EXPLAIN SELECT for element of array +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +SELECT person_t('Michael', 'Widenius') IN (person_by_nickname('Monty')); +EXPLAIN EXTENDED SELECT person_t('Michael', 'Widenius') IN (person_by_nickname('Monty')); +END; +$$ +person_t('Michael', 'Widenius') IN (person_by_nickname('Monty')) +1 +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select ('Michael','Widenius') = person_by_nickname['Monty']@0['Monty'] AS "person_t('Michael', 'Widenius') IN (person_by_nickname('Monty'))" +# +# EXPLAIN SELECT for field of element of array +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +person_by_nickname('Monty') := person_t('Michael', 'Widenius'); +EXPLAIN EXTENDED SELECT person_by_nickname('Monty').first_name; +END; +$$ +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select person_by_nickname['Monty'].first_name@0['Monty'].0 AS "person_by_nickname('Monty').first_name" +# +# EXPLAIN SELECT for IS NULL with array argument +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +pbn table_of_peson_t; +BEGIN +pbn('Monty') := person_t('Michael', 'Widenius'); +SELECT pbn IS NULL; +EXPLAIN EXTENDED SELECT pbn IS NULL; +END; +$$ +pbn IS NULL +0 +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select pbn@0 is null AS "pbn IS NULL" +# +# EXPLAIN SELECT for IS NOT NULL with array argument +# +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +pbn table_of_peson_t; +BEGIN +pbn('Monty') := person_t('Michael', 'Widenius'); +SELECT pbn IS NOT NULL; +EXPLAIN EXTENDED SELECT pbn IS NOT NULL; +END; +$$ +pbn IS NOT NULL +1 +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select pbn@0 is not null AS "pbn IS NOT NULL" +# +# IS NULL, IS NOT NULL +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +m1 marks_t:= marks_t(1 => 62); +m2 marks_t; +m3 marks_t:= marks_t(1 => 62); +id INTEGER; +BEGIN +SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; +m1:= m2; +SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; +m2:= marks_t(1 => 62); +SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; +m1:= m3; +SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; +END; +$$ +m1 IS NULL m1 IS NOT NULL m2 is NULL m2 IS NOT NULL +0 1 1 0 +m1 IS NULL m1 IS NOT NULL m2 is NULL m2 IS NOT NULL +1 0 1 0 +m1 IS NULL m1 IS NOT NULL m2 is NULL m2 IS NOT NULL +1 0 0 1 +m1 IS NULL m1 IS NOT NULL m2 is NULL m2 IS NOT NULL +0 1 0 1 +# +# Call associative array method directly +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +m1 marks_t:= marks_t(1 => 62); +BEGIN +m1.delete; +END; +$$ +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 'm1.delete; +END' at line 5 +# +# Create an associative array type sharing the same name as a built-in function +# TODO: This should be allowed +# +DECLARE +TYPE trim IS TABLE OF NUMBER INDEX BY INTEGER; +m1 trim:= trim(1=>1); +BEGIN +SELECT m1(1); +END; +$$ +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 '=>1); +BEGIN +SELECT m1(1); +END' at line 3 +# +# Create an associative array type sharing the same name as a built-in function (2) +# TODO: This should be allowed +# +DECLARE +TYPE acos IS TABLE OF NUMBER INDEX BY INTEGER; +m1 acos:= acos(1=>1); +BEGIN +SELECT m1(1); +END; +$$ +ERROR 42000: Incorrect parameters in the call to native function 'acos' +# +# Create an associative array type sharing the same name as a stored procedure +# +CREATE PROCEDURE p1() AS +BEGIN +SELECT 'p1()'; +END; +$$ +DECLARE +TYPE p1 IS TABLE OF NUMBER INDEX BY INTEGER; +m1 p1:= p1(1=>1); +BEGIN +SELECT m1(1); +END; +$$ +m1(1) +1 +DROP PROCEDURE p1; +# +# Create an associative array type sharing the same name as a package +# +CREATE PACKAGE pkg1 AS +PROCEDURE p1(); +END; +$$ +CREATE PACKAGE BODY pkg1 AS +PROCEDURE p1() AS +BEGIN +SELECT 'p1()'; +END; +END; +$$ +DECLARE +TYPE pkg1 IS TABLE OF NUMBER INDEX BY INTEGER; +m1 pkg1:= pkg1(1=>1); +BEGIN +SELECT m1(1); +END; +$$ +m1(1) +1 +DROP PACKAGE pkg1; +# +# Create an associative array sharing the same name as a package +# The expected behaviour is that the associative array local variable +# will hide the package. On the other hand prepared statements +# will not see the local variable. +# +CREATE PACKAGE pkg1 AS +PROCEDURE prior(a VARCHAR); +PROCEDURE prior1(a VARCHAR); +FUNCTION prior(a VARCHAR) RETURN VARCHAR; +END; +$$ +CREATE PACKAGE BODY pkg1 AS +PROCEDURE prior(a VARCHAR) AS +BEGIN +SELECT 'pkg1.prior()'; +END; +PROCEDURE prior1(a VARCHAR) AS +BEGIN +SELECT 'pkg1.prior1()'; +END; +FUNCTION prior(a VARCHAR) RETURN VARCHAR AS +BEGIN +RETURN 'pkg1.prior()'; +END; +END; +$$ +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); +pkg1 marks_t:= marks_t('1'=>1,'2'=>2); +BEGIN +SELECT pkg1.prior('2'); +CALL pkg1.prior('2'); +EXECUTE IMMEDIATE 'CALL pkg1.prior(2)'; +EXECUTE IMMEDIATE 'CALL pkg1.prior1(2)'; +END; +$$ +pkg1.prior('2') +1 +pkg1.prior() +pkg1.prior() +pkg1.prior() +pkg1.prior() +pkg1.prior1() +pkg1.prior1() +DROP PACKAGE pkg1; +# +# A scalar variable does not shadow a package +# +CREATE PACKAGE pkg AS +PROCEDURE p1; +FUNCTION f1 RETURN TEXT; +END; +$$ +CREATE PACKAGE BODY pkg AS +p1 INT; +PROCEDURE p1 AS +BEGIN +SELECT 'pkg.p1'; +END; +FUNCTION f1 RETURN TEXT AS +BEGIN +RETURN 'pkg.f1'; +END; +END; +$$ +CREATE PROCEDURE p1 AS +pkg INT; -- This scalar variable does not shadow the package 'pkg' +BEGIN +pkg.p1(); -- This is resolved to the package procedure call +SELECT pkg.f1(); -- This is resolved to the package function call +END; +$$ +CALL p1; +pkg.p1 +pkg.p1 +pkg.f1() +pkg.f1 +DROP PROCEDURE p1; +DROP PACKAGE pkg; +# +# A ROW variable does not shadow a package +# +CREATE PACKAGE pkg AS +PROCEDURE p1; +FUNCTION f1 RETURN TEXT; +END; +$$ +CREATE PACKAGE BODY pkg AS +p1 INT; +PROCEDURE p1 AS +BEGIN +SELECT 'pkg.p1'; +END; +FUNCTION f1 RETURN TEXT AS +BEGIN +RETURN 'pkg.f1'; +END; +END; +$$ +CREATE PROCEDURE p1 AS +pkg ROW(p1 INT,p2 INT); -- This ROW variable does not shadow the package 'pkg' +BEGIN +pkg.p1(); -- This is resolved to the package procedure call +SELECT pkg.f1(); -- This is resolved to the package function call +END; +$$ +CALL p1; +pkg.p1 +pkg.p1 +pkg.f1() +pkg.f1 +DROP PROCEDURE p1; +DROP PACKAGE pkg; +# +# Checking that when the routine execution leaves +# a DECLARE..BEGIN..END block with an assoc array declared, +# the memory used by the assoc array is freed. +# +CREATE FUNCTION memory_used() RETURN BIGINT AS +BEGIN +RETURN (SELECT variable_value FROM INFORMATION_SCHEMA.SESSION_STATUS +WHERE variable_name='memory_used'); +END; +/ +CREATE PROCEDURE p1 AS +memory_used0 BIGINT:= memory_used(); +BEGIN +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); +person_by_nickname table_of_peson_t; +BEGIN +FOR i IN 1..3 +LOOP +person_by_nickname(i):= person_t(CONCAT('first_name',i), +CONCAT('last_name',i)); +SELECT i, memory_used()-memory_used0 >0 AS diff; +END LOOP; +END; +SELECT memory_used()-memory_used0 >0 AS diff1; +END; +/ +CALL p1; +i diff +1 1 +i diff +2 1 +i diff +3 1 +diff1 +0 +DROP PROCEDURE p1; +DROP FUNCTION memory_used; +# +# Ensure that subqueries are disallowed for keys +# +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES (1); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 78); +BEGIN +SELECT marks((SELECT * FROM t1)); +END; +$$ +ERROR HY000: '(select "*" from "test"."t1")' is not allowed in this context +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 78); +BEGIN +SELECT marks((SELECT 1)); +END; +$$ +ERROR HY000: '(select 1)' is not allowed in this context +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; +p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN +SELECT p((SELECT * FROM t1)).first_name; +END; +$$ +ERROR HY000: '(select "*" from "test"."t1")' is not allowed in this context +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; +p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN +SELECT p((SELECT 1)).first_name; +END; +$$ +ERROR HY000: '(select 1)' is not allowed in this context +# +# Ensure that fields are disallowed for keys +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 78); +BEGIN +SELECT marks(t1.a) FROM t1; +END; +$$ +ERROR HY000: '"t1"."a"' is not allowed in this context +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; +p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN +SELECT p(t1.a).first_name FROM t1; +END; +$$ +ERROR HY000: '"t1"."a"' is not allowed in this context +# +# Ensure that ROWNUM is disallowed for keys +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 78); +BEGIN +SELECT marks(ROWNUM) FROM t1; +END; +$$ +ERROR HY000: 'rownum()' is not allowed in this context +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; +p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN +SELECT p(ROWNUM).first_name FROM t1; +END; +$$ +ERROR HY000: 'rownum()' is not allowed in this context +# +# Ensure that WINDOW functions are disallowed for keys +# +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t:= marks_t(1 => 78); +BEGIN +SELECT marks(COUNT(t1.a)) FROM t1; +END; +$$ +ERROR HY000: 'count("t1"."a")' is not allowed in this context +DECLARE +TYPE person_t IS RECORD +( +first_name VARCHAR(64), +last_name VARCHAR(64) +); +TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; +p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN +SELECT p(COUNT(t1.a)).first_name FROM t1; +END; +$$ +ERROR HY000: 'count("t1"."a")' is not allowed in this context +DROP TABLE t1; diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array.test b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array.test new file mode 100644 index 00000000000..84223579120 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array.test @@ -0,0 +1,2948 @@ +--echo # +--echo # MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +--echo # + +set sql_mode=oracle; +SET NAMES utf8mb4; + +--echo # +--echo # Wrong of ASSOCIATIVE_ARRAY +--echo # + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE TABLE t1 (a ASSOCIATIVE_ARRAY); + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE FUNCTION f1() RETURN ASSOCIATIVE_ARRAY RETURN 0; + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE FUNCTION f1(a ASSOCIATIVE_ARRAY) RETURN INT RETURN 0; + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE FUNCTION f1(a ROW(a ASSOCIATIVE_ARRAY, b INT)) RETURN INT RETURN 0; + + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + a ASSOCIATIVE_ARRAY; +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + r ROW(a ASSOCIATIVE_ARRAY, b INT); +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE table_of_aa_t IS TABLE OF ASSOCIATIVE_ARRAY INDEX BY VARCHAR2(20); +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE rec_t IS RECORD (a ASSOCIATIVE_ARRAY, b INT); +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # RECORD element type +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; + nick VARCHAR(20); + temp person_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + person_by_nickname('Serg') := person_t('Sergei ', 'Golubchik'); + nick:= person_by_nickname.FIRST; + WHILE nick IS NOT NULL + LOOP + temp:= person_by_nickname(nick); + SELECT nick, temp.first_name, temp.last_name FROM DUAL; + nick:= person_by_nickname.NEXT(nick); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD element type with initialization +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t:= + table_of_peson_t( + 'Monty' => person_t('Michael', 'Widenius'), + 'Serg' => person_t('Sergei ', 'Golubchik')) ; + nick VARCHAR(20); +BEGIN + nick:= person_by_nickname.FIRST; + WHILE nick IS NOT NULL + LOOP + SELECT + nick, person_by_nickname(nick).first_name, + person_by_nickname(nick).last_name + FROM DUAL; + + nick:= person_by_nickname.NEXT(nick); + END LOOP; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # SCALAR element type +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; + id INTEGER; +BEGIN + marks(0) := 62; + marks(1) := 78; + id:= marks.FIRST; + WHILE id IS NOT NULL + LOOP + SELECT marks(id), id; + id:= marks.NEXT(id); + END LOOP; + + SELECT marks(0) + marks(1) as total, POW(marks(0), 2), POW(marks(1), 2); + + marks(1) := NULL; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SCALAR element type with initialization +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); + id INTEGER; +BEGIN + id:= marks.FIRST; + WHILE id IS NOT NULL + LOOP + SELECT marks(id), id; + id:= marks.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SCALAR element type with initialization (2) +--echo # NUMBER element type initialized with string +--echo # On Oracle error PLS-00306 will be raised +--echo # In this implementation we just convert +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); + id INTEGER; +BEGIN + id:= marks.FIRST; + WHILE id IS NOT NULL + LOOP + SELECT marks(id), id; + id:= marks.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Initialization without named association +--echo # +DELIMITER $$; +--error ER_NEED_NAMED_ASSOCIATION +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(62, 78, 99); + id INTEGER; +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Initialization without named association (2) +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 62, 2 => 78, 99); + id INTEGER; +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Wrong use: assoc_array_var.x(1) +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE PROCEDURE p1 AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks.x(1):= 10; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Wrong use: assoc_array_var.x.y(1) +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE PROCEDURE p1 AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks.x.y(1):= 10; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Wrong use: assoc_array_var.x.y.z(1) +--echo # + +DELIMITER $$; +--error ER_PARSE_ERROR +CREATE PROCEDURE p1 AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks.x.y.z(1):= 10; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Initialization with empty elements +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(); + id INTEGER; +BEGIN + SELECT marks.first; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Initialization with duplicate key +--echo # +DELIMITER $$; +--error ER_DUP_UNKNOWN_IN_INDEX +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 1, 2 => 2, 1 => 3); + id INTEGER; +BEGIN + SELECT marks.first; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD array assignment +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t:= + table_of_peson_t( + 'Monty' => person_t('Michael', 'Widenius'), + 'Serg' => person_t('Sergei ', 'Golubchik')) ; + person_by_nickname_copy table_of_peson_t; + nick VARCHAR(20); +BEGIN + person_by_nickname_copy:= person_by_nickname; + nick:= person_by_nickname_copy.FIRST; + WHILE nick IS NOT NULL + LOOP + SELECT + nick, person_by_nickname_copy(nick).first_name, + person_by_nickname_copy(nick).last_name + FROM DUAL; + + nick:= person_by_nickname_copy.NEXT(nick); + END LOOP; + + person_by_nickname_copy('Alex'):= NULL; + SELECT person_by_nickname_copy('Alex').first_name; + person_by_nickname_copy('Alex'):= person_by_nickname('Monty'); + SELECT person_by_nickname_copy('Alex').first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SCALAR array assignment +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); + marks2 marks_t; + id INTEGER; +BEGIN + marks2:= marks; + id:= marks2.FIRST; + WHILE id IS NOT NULL + LOOP + SELECT marks2(id), id; + id:= marks2.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SCALAR array assignment with differing element types +--echo # Oracle do not allow this (PLS-00382: expression is of wrong type) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + TYPE names_t IS TABLE OF VARCHAR(64) INDEX BY VARCHAR2(20); + marks marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); + names2 names_t:= names_t('1' => 'Klaus', '2' => 'Lee', '3' => 'Arun'); + + id INTEGER; +BEGIN + marks:= names2; + id:= marks.FIRST; + WHILE id IS NOT NULL + LOOP + id:= marks.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Anchored ROWTYPE for array element +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30) +); +DELIMITER $$; +DECLARE + TYPE depts_t IS TABLE OF t1%ROWTYPE INDEX BY INTEGER; + depts depts_t; + t INTEGER; +BEGIN + depts(666666) := ROW(1, 'HR'); + depts(777777) := ROW(2, 'Accounting'); + t:= depts.FIRST; + WHILE t IS NOT NULL + LOOP + SELECT depts(t).dept_name, t; + t:= depts.NEXT(t); + END LOOP; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # Anchored column TYPE for array element +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30) +); +DELIMITER $$; +DECLARE + TYPE depts_t IS TABLE OF t1.dept_name%TYPE INDEX BY INTEGER; + depts depts_t; + t INTEGER; +BEGIN + depts(666666) := 'HR'; + depts(777777) := 'Accounting'; + depts(-1) := 'Engineering'; + t:= depts.FIRST; + WHILE t IS NOT NULL + LOOP + SELECT depts(t), t; + t:= depts.NEXT(t); + END LOOP; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # Retrieve keys from uninitialized array +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; + nick VARCHAR2(20); +BEGIN + nick:= person_by_nickname.FIRST; + SELECT nick FROM DUAL; + + nick:= person_by_nickname.LAST; + SELECT nick FROM DUAL; + + nick:= person_by_nickname.NEXT(nick); + SELECT nick FROM DUAL; + + +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Retrieve keys from initialized array +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; + nick VARCHAR2(20); +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + person_by_nickname('Serg') := person_t('Sergei ', 'Golubchik'); + person_by_nickname('Zam') := person_t('Kitamura ', 'Motoyasu'); + + nick:= person_by_nickname.LAST; + SELECT nick FROM DUAL; + + nick:= person_by_nickname.FIRST; + SELECT nick FROM DUAL; + + nick:= person_by_nickname.NEXT(nick); + SELECT nick FROM DUAL; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # NEXT +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.NEXT(marks.FIRST), marks.NEXT(marks.LAST), marks.NEXT(NULL), + marks.next(marks.FIRST); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # NEXT, argument count error 1 +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.NEXT(); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # NEXT, argument count error 2 +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.NEXT(0, 1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # NEXT, NULL array +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT marks.NEXT(0); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # NEXT on scalar +--echo # +DELIMITER $$; +--error ER_FUNC_INEXISTENT_NAME_COLLISION +DECLARE + marks INTEGER; +BEGIN + SELECT marks.NEXT(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # PRIOR +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.PRIOR(marks.LAST), marks.PRIOR(marks.FIRST), marks.PRIOR(NULL), + marks.prior(marks.LAST); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # PRIOR, argument count error 1 +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.PRIOR(); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # PRIOR, argument count error 2 +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.PRIOR(0, 1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # PRIOR, NULL array +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT marks.PRIOR(0); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # PRIOR on scalar +--echo # +DELIMITER $$; +--error ER_SP_DOES_NOT_EXIST +DECLARE + marks INTEGER; +BEGIN + SELECT marks.PRIOR(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXISTS +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + SELECT marks.EXISTS(1), marks.EXISTS(4), + marks.exists(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXISTS, argument count error +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + SELECT marks.EXISTS(); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXISTS, NULL key +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + SELECT marks.EXISTS(NULL); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXISTS on scalar +--echo # +DELIMITER $$; +--error ER_FUNC_INEXISTENT_NAME_COLLISION +DECLARE + marks INTEGER; +BEGIN + SELECT marks.EXISTS(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXISTS, NULL array +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT marks.EXISTS(4); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # DELETE +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + marks(2) := 99; + SELECT marks.DELETE(1); + SELECT marks.EXISTS(0), marks.EXISTS(1), marks.EXISTS(2); + + SELECT marks.DELETE; + SELECT marks.EXISTS(0), marks.EXISTS(1), marks.EXISTS(2); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # DELETE, argument count error +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + marks(2) := 99; + SELECT marks.DELETE(1, 2); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # DELETE, NULL key +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + SELECT marks.DELETE(NULL); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # DELETE on scalar +--echo # +DELIMITER $$; +--error ER_FUNC_INEXISTENT_NAME_COLLISION +DECLARE + marks INTEGER; +BEGIN + SELECT marks.DELETE(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # COUNT +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(0) := 62; + marks(1) := 78; + marks(2) := 99; + SELECT marks.COUNT; + + marks(3) := 44; + SELECT marks.COUNT; + + SELECT marks.DELETE(1); + SELECT marks.COUNT; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # COUNT on scalar +--echo # +DELIMITER $$; +--error ER_UNKNOWN_TABLE +DECLARE + marks INTEGER; +BEGIN + SELECT marks.COUNT; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # VARCHAR2 key length error +--echo # +DELIMITER $$; +--error ER_TOO_LONG_KEY +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(10); + marks marks_t; +BEGIN + marks('Clementine Montgomery') := 99; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # VARCHAR2 key fix +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(10); + marks marks_t; +BEGIN + marks('Cle' || 'mentine') := 99; + SELECT marks('Cleme' || 'ntine'); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key collation, case insensitive, accent insensitive +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_ci; + marks marks_t; + id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN + marks(id) := 62; + id:= 'steve'; + SELECT marks(id); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key collation, case sensitive, accent insensitive (1) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_cs; + marks marks_t; + id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN + marks(id) := 62; + SELECT marks('Steve'); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key collation, case sensitive, accent insensitive (2) +--echo # +DELIMITER $$; +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4 COLLATE uca1400_ai_cs; + marks marks_t; + id VARCHAR2(20) CHARACTER SET utf8mb4:= 'Stéve'; +BEGIN + marks(id) := 62; + SELECT marks('steve'); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key ascii charset +--echo # +DELIMITER $$; +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20) CHARACTER SET utf8mb4; + marks marks_t; + id VARCHAR2(20) CHARACTER SET ascii:= 'Stéve'; +BEGIN + marks('Stéve') := 62; + SELECT marks(id); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # VARCHAR2 key with numeric key retrival +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + marks marks_t; +BEGIN + marks('1') := 62; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # INTEGER key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(-2147483649) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # INTEGER key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(2147483648) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # INTEGER UNSIGNED key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; + marks marks_t; +BEGIN + marks(-1) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # INTEGER UNSIGNED key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; + marks marks_t; +BEGIN + marks(4294967296) := 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # INTEGER UNSIGNED key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER UNSIGNED; + marks marks_t; +BEGIN + marks(4294967295) := 62; + SELECT marks(4294967295); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # TINYINT key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; + marks marks_t; +BEGIN + marks(-129) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # TINYINT key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; + marks marks_t; +BEGIN + marks(128) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # TINYINT key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT; + marks marks_t; +BEGIN + marks(0) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # TINYINT UNSIGNED key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; + marks marks_t; +BEGIN + marks(-1) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # TINYINT UNSIGNED key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; + marks marks_t; +BEGIN + marks(256) := 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # TINYINT UNSIGNED key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY TINYINT UNSIGNED; + marks marks_t; +BEGIN + marks(255) := 62; + SELECT marks(255); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; + marks marks_t; +BEGIN + marks(-32769) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; + marks marks_t; +BEGIN + marks(32768) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT; + marks marks_t; +BEGIN + marks(0) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT UNSIGNED key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; + marks marks_t; +BEGIN + marks(-1) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT UNSIGNED key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; + marks marks_t; +BEGIN + marks(65536) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SMALLINT UNSIGNED key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY SMALLINT UNSIGNED; + marks marks_t; +BEGIN + marks(65535) := 62; + SELECT marks(65535); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; + marks marks_t; +BEGIN + marks(-8388609) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; + marks marks_t; +BEGIN + marks(8388608) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT; + marks marks_t; +BEGIN + marks(0) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT UNSIGNED key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; + marks marks_t; +BEGIN + marks(-1) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT UNSIGNED key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; + marks marks_t; +BEGIN + marks(16777216) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MEDIUMINT UNSIGNED key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY MEDIUMINT UNSIGNED; + marks marks_t; +BEGIN + marks(16777215) := 62; + SELECT marks(16777215); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # BIGINT key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; + marks marks_t; +BEGIN + marks(-9223372036854775809) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # BIGINT key, range (max) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; + marks marks_t; +BEGIN + marks(9223372036854775808) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # BIGINT key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT; + marks marks_t; +BEGIN + marks(0) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # BIGINT UNSIGNED key, range (min) +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT UNSIGNED; + marks marks_t; +BEGIN + marks(-1) := 62; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # BIGINT UNSIGNED key, range (valid) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY BIGINT UNSIGNED; + marks marks_t; +BEGIN + marks(18446744073709551615) := 62; + SELECT marks(18446744073709551615); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # INTEGER key, sort order +--echo # +DELIMITER $$; +DECLARE + TYPE depts_t IS TABLE OF VARCHAR2(30) INDEX BY INTEGER; + depts depts_t; + t INTEGER; +BEGIN + depts(999) := 'Security'; + depts(99) := 'HR'; + depts(9) := 'Accounting'; + depts(-9) := 'Engineering'; + depts(-99) := 'Marketing'; + depts(-999) := 'Sales'; + t:= depts.FIRST; + WHILE t IS NOT NULL + LOOP + SELECT depts(t), t; + t:= depts.NEXT(t); + END LOOP; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key numeric, character access (convertable to integer) +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(1) := 62; + SELECT marks('01'); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Key numeric, character access +--echo # +DELIMITER $$; +--error ER_WRONG_VALUE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks('Test') := 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # NULL key when assigning variable +--echo # On Oracle ORA-06502 +--echo # +DELIMITER $$; +--error ER_NULL_FOR_ASSOC_ARRAY_INDEX +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(NULL):= 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # NULL key when accessing elements +--echo # On Oracle ORA-06502 +--echo # +DELIMITER $$; +--error ER_NULL_FOR_ASSOC_ARRAY_INDEX +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(1):= 62; + SELECT marks(NULL); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # NULL key when assigning variable +--echo # On Oracle ORA-06502 +--echo # +DELIMITER $$; +--error ER_NULL_FOR_ASSOC_ARRAY_INDEX +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + p table_of_person_t; +BEGIN + p(NULL).first_name:= 'aa'; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # NULL key when accessing elements +--echo # On Oracle ORA-06502 +--echo # +DELIMITER $$; +--error ER_NULL_FOR_ASSOC_ARRAY_INDEX +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + p table_of_person_t; +BEGIN + p('cc'):= person_t('aa', 'bb'); + SELECT p(NULL).first_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Nested tables (INDEX BY is not specified) +--echo # This is not supported yet +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE marks_t IS TABLE OF NUMBER; + marks marks_t:= marks_t(62, 78, 99); + + id INTEGER; +BEGIN + marks:= names2; + id:= marks.FIRST; + WHILE id IS NOT NULL + LOOP + -- SELECT marks(id), id; + id:= marks.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Element access for scalar variable +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + marks INTEGER; +BEGIN + marks(1):= 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Element assignment with wrong argument count +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(1, 2):= 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Element assignment with wrong argument count (2) +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks():= 62; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Element access with wrong argument count +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(1):= 62; + SELECT marks(1, 2); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Element access with wrong argument count (2) +--echo # +DELIMITER $$; +--error ER_SP_WRONG_NO_OF_ARGS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT marks(); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Non-existant field access +--echo # +DELIMITER $$; +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + SELECT person_by_nickname('Monty').email; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Field access on array with scalar element +--echo # +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(1) := 1; + SELECT marks(1).name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Field assignment +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; +BEGIN + person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); + person_by_nickname('Serg') := person_t('Sergei ', 'Golubchik'); + person_by_nickname('Monty').first_name:= 'Mike'; + + SELECT person_by_nickname('Monty').first_name, + person_by_nickname('Monty').last_name; + SELECT person_by_nickname('Serg').first_name, + person_by_nickname('Serg').last_name; + + person_by_nickname('Serg').first_name:= NULL; + SELECT person_by_nickname('Serg').first_name, person_by_nickname('Serg').first_name IS NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Field access - non-existant key +--echo # +DELIMITER $$; +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; +BEGIN + SELECT person_by_nickname('Monty').first_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Field assignment - non-existant key +--echo # +DELIMITER $$; +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; +BEGIN + person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); + person_by_nickname('Jeff').nick_name:= 'Mike'; + + SELECT person_by_nickname('Monty').first_name, + person_by_nickname('Monty').last_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Field assignment - non existant field +--echo # +DELIMITER $$; +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_person_t; +BEGIN + person_by_nickname('Monty'):= person_t('Michael', 'Widenius'); + person_by_nickname('Monty').nick_name:= 'Mike'; + + SELECT person_by_nickname('Monty').first_name, + person_by_nickname('Monty').last_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Key access using another array's element +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + marks marks_t; + fname VARCHAR2(20); + + TYPE hist_t IS TABLE OF NUMBER INDEX BY INTEGER; + hist hist_t; + hist_bin INTEGER; +BEGIN + marks('Steve') := 62; + marks('Mark') := 77; + marks('Lee') := 79; + marks('John') := 31; + + fname:= marks.FIRST; + WHILE fname IS NOT NULL + LOOP + IF hist.EXISTS(FLOOR(marks(fname) / 10)) THEN + hist(FLOOR(marks(fname) / 10)):= hist(FLOOR(marks(fname) / 10)) + 1; + ELSE + hist(FLOOR(marks(fname) / 10)):= 1; + END IF; + + fname:= marks.NEXT(fname); + END LOOP; + + hist_bin:= hist.FIRST; + WHILE hist_bin IS NOT NULL + LOOP + SELECT hist_bin, hist(hist_bin); + hist_bin:= hist.NEXT(hist_bin); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # ASSOC ARRAY type used in a stored PROCEDURE +--echo # +DELIMITER $$; +CREATE PROCEDURE p1(v NUMBER) AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + marks marks_t; +BEGIN + marks(1) := 62; + SELECT marks(1); +END; +$$ +DELIMITER ;$$ +CALL p1(4); +DROP PROCEDURE p1; + +--echo # +--echo # ASSOC ARRAY type used in a stored FUNCTION +--echo # +DELIMITER $$; +CREATE FUNCTION f1(v NUMBER) +RETURN NUMBER IS + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + marks marks_t; +BEGIN + marks(1) := 62; + RETURN marks(1); +END; +$$ +DELIMITER ;$$ +SELECT f1(4); +DROP FUNCTION f1; + +--echo # +--echo # Accessing member fields from constructor should fail +--echo # for RECORD type +--echo # +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + + first_name VARCHAR(64):= person_t('First', 'Last').first_name; +BEGIN + SELECT first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Accessing member fields from constructor should fail +--echo # for ASSOC ARRAY type +--echo # +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + first_name VARCHAR(64):= table_of_person_t('1' => person_t('First', 'Last')).first_name; +BEGIN + SELECT first_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Selecting assoc array +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 AS '1'); +BEGIN + SELECT marks; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Assoc array compare - scalar element +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 => 1); + prev_marks marks_t:=marks_t(1 => 1); +BEGIN + IF marks = prev_marks THEN + SELECT 'Equal'; + END IF; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Assoc array compare - record element +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE subjects_t IS RECORD + ( + english INTEGER, + math INTEGER + ); + TYPE marks_t IS TABLE OF subjects_t INDEX BY VARCHAR2(64); + marks marks_t:=marks_t('Nur' => subjects_t(80, 90)); + prev_marks marks_t:=marks_t('Nur' => subjects_t(75, 69)); +BEGIN + IF marks = prev_marks THEN + SELECT 'Equal'; + END IF; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Assoc array SUM +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN + SELECT SUM(marks); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Assoc array IN +--echo # +CREATE TABLE t1 (a INT); +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN + SELECT marks(2) IN (2, 3); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # Assoc array IN (2) +--echo # +CREATE TABLE t1 (a INT); +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + persons('Serg') := person_t('Sergei ', 'Golubchik'); + SELECT persons('Monty') IN (person_t('Michael', 'Widenius')); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # Assoc array IN (2) +--echo # +CREATE TABLE t1 (a INT); +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + persons('Serg') := person_t('Sergei ', 'Golubchik'); + SELECT persons('Monty').first_name IN ('Michael', 'Sergei '); + SELECT persons('Monty').last_name IN ('Michael', 'Sergei '); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # IN Assoc array element +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN + SELECT * FROM t1 WHERE a IN (marks(2), marks(3)); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + + +--echo # +--echo # IN Assoc array, not going to support this, test just +--echo # to ensure that we don't crash the server +--echo # +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:=marks_t(1 => 1, 2 => 2, 3 => 3); +BEGIN + SELECT 2 IN (marks); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # SELECT .. INTO spvar_assoc_array +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT 1 INTO marks; +END; +$$ +DELIMITER ;$$ +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT 1,2 INTO marks; +END; +$$ +DELIMITER ;$$ +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # SELECT INTO ASSOC ARRAY scalar element +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT * FROM t1 WHERE a = 2 INTO marks(200); + SELECT marks(200); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT INTO ASSOC ARRAY non-scalar element +--echo # +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + SELECT * FROM t1 WHERE first_name = 'Anwar' INTO persons('Nuau'); + SELECT persons('Nuau').last_name; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT INTO ASSOC ARRAY for a field of a non-scalar element +--echo # +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + SELECT * FROM t1 WHERE first_name = 'Anwar' INTO persons('Nuau'); + SELECT * FROM t1 WHERE first_name= 'Muhyiddin' INTO persons('Muhyiddin'); + SELECT 'ibn Arabi' INTO persons('Muhyiddin').last_name; + SELECT persons('Muhyiddin').first_name, persons('Muhyiddin').last_name; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT INTO ASSOC ARRAY for a field of a non-scalar element +--echo # for a non-existing key +--echo # +DELIMITER $$; +CREATE PROCEDURE p1 IS + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + SELECT 'ibn Arabi' INTO persons('Muhyiddin').last_name; + SELECT persons('Muhyiddin').first_name, persons('Muhyiddin').last_name; +END; +$$ +DELIMITER ;$$ +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # SELECT INTO assoc_array_of_scalars('key').field +--echo # Fails during the CREATE time. +--echo # +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE PROCEDURE p1 IS + TYPE assoc_t IS TABLE OF VARCHAR2(20) INDEX BY VARCHAR2(20); + assoc assoc_t; +BEGIN + SELECT 'ibn Arabi' INTO assoc('Muhyiddin').last_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # SELECT scalar INTO ASSOC ARRAY non-scalar element +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + SELECT * FROM t1 WHERE a = 3 INTO persons('Nuau'); + SELECT persons('Nuau').last_name; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT non-scalar INTO ASSOC ARRAY scalar element +--echo # +CREATE TABLE t1 (first_name VARCHAR(64), last_name VARCHAR(64)); +INSERT INTO t1 VALUES ('Anwar', 'Ibrahim'); +INSERT INTO t1 VALUES ('Najib', 'Razak'); +INSERT INTO t1 VALUES ('Muhyiddin', 'Yassin'); +DELIMITER $$; +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + SELECT * FROM t1 WHERE first_name = 'Najib' INTO marks(200); + SELECT marks(200); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT scalar INTO scalar variable with non-existant key +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + id INTEGER; +BEGIN + SELECT * FROM t1 WHERE a = 3 INTO id('Nuau'); +END; +$$ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + id INTEGER; +BEGIN + EXPLAIN SELECT * FROM t1 WHERE a = 3 INTO id('Nuau'); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT scalar INTO non-existant variable with key +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +--error ER_SP_UNDECLARED_VAR +DECLARE + id INTEGER; +BEGIN + SELECT * FROM t1 WHERE a = 3 INTO missing_var('Nuau'); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT scalar INTO ASSOC_ARRAY's element field +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + SELECT 'Mike' INTO persons('Monty').first_name; + SELECT persons('Monty').first_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # SELECT non-scalar INTO ASSOC ARRAY scalar element's field +--echo # +DELIMITER $$; +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + SELECT 'Mike','Widenius' INTO persons('Monty').first_name; + SELECT persons('Monty').first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SELECT scalar INTO scalar element's field with non-existant key +--echo # +DELIMITER $$; +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + SELECT 'Mike' INTO persons('Serg').first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SELECT scalar INTO non-existant variable with key +--echo # +DELIMITER $$; +--error ER_SP_UNDECLARED_VAR +DECLARE +BEGIN + SELECT 'Mike' INTO missing_var('Serg').first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Use field on scalar element with SELECT .. INTO +--echo # +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(200):= 88; + SELECT 1 INTO marks(200).non_exist; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Use field on scalar variable with SELECT .. INTO +--echo # +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + marks INTEGER; +BEGIN + SELECT 1 INTO marks(200).non_exist; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # SELECT scalar INTO scalar variable with non-existant field +--echo # +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_person_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + persons table_of_person_t; +BEGIN + persons('Monty') := person_t('Michael', 'Widenius'); + SELECT 'Mike' INTO persons('Monty').first_namex; +END; +$$ +DELIMITER ;$$ + +SET sql_mode=default; + +--echo # +--echo # Basic ASSOC ARRAY, anonymous block sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +BEGIN NOT ATOMIC + DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + DECLARE marks marks_t; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Basic ASSOC ARRAY, stored procedure sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +CREATE OR REPLACE PROCEDURE p1() +BEGIN + DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + DECLARE marks marks_t; +END; +$$ +DELIMITER ;$$ +--error ER_SP_DOES_NOT_EXIST +CALL p1(); +--error ER_SP_DOES_NOT_EXIST +DROP PROCEDURE p1; + +--echo # +--echo # Basic ASSOC ARRAY, stored function sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +CREATE OR REPLACE FUNCTION f1() RETURNS INT +BEGIN + DECLARE TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + DECLARE marks marks_t; + RETURN marks(1); +END; +$$ +DELIMITER ;$$ +--error ER_SP_DOES_NOT_EXIST +SELECT f1(); +--error ER_SP_DOES_NOT_EXIST +DROP FUNCTION f1; + +SET sql_mode=ORACLE; + +--echo # +--echo # Ensure that nested assoc array types are properly parsed (without crash, etc) +--echo # +DELIMITER $$; +--error ER_NOT_SUPPORTED_YET +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + TYPE classes_t IS TABLE OF marks_t INDEX BY INTEGER; +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Ensure that nested assoc array types cannot be used for record field +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + TYPE rec_t IS RECORD (a INT, b marks_t); +BEGIN + NULL; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Ensure that DATE element maps correctly to MariaDB +--echo # DATETIME +--echo # +DELIMITER $$; +DECLARE + TYPE dates_t IS TABLE OF DATE INDEX BY INTEGER; + dates dates_t:= dates_t(1 => '2021-01-01 10:20:30'); +BEGIN + SELECT dates(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Multiple variable declaration +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + m1,m2,m3 marks_t:= marks_t(1 => 62, 2 => 78, 3 => 99); + id INTEGER; +BEGIN + id:= m1.FIRST; + WHILE id IS NOT NULL + LOOP + SELECT m1(id) || m2(id) || m3(id) AS m; + id:= m1.NEXT(id); + END LOOP; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXPLAIN SELECT for element of array +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + + SELECT person_t('Michael', 'Widenius') IN (person_by_nickname('Monty')); + EXPLAIN EXTENDED SELECT person_t('Michael', 'Widenius') IN (person_by_nickname('Monty')); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # EXPLAIN SELECT for field of element of array +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; +BEGIN + person_by_nickname('Monty') := person_t('Michael', 'Widenius'); + EXPLAIN EXTENDED SELECT person_by_nickname('Monty').first_name; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # EXPLAIN SELECT for IS NULL with array argument +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + pbn table_of_peson_t; +BEGIN + pbn('Monty') := person_t('Michael', 'Widenius'); + + SELECT pbn IS NULL; + EXPLAIN EXTENDED SELECT pbn IS NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # EXPLAIN SELECT for IS NOT NULL with array argument +--echo # +DELIMITER $$; +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + pbn table_of_peson_t; +BEGIN + pbn('Monty') := person_t('Michael', 'Widenius'); + + SELECT pbn IS NOT NULL; + EXPLAIN EXTENDED SELECT pbn IS NOT NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # IS NULL, IS NOT NULL +--echo # +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + m1 marks_t:= marks_t(1 => 62); + m2 marks_t; + m3 marks_t:= marks_t(1 => 62); + id INTEGER; +BEGIN + SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; + m1:= m2; + SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; + m2:= marks_t(1 => 62); + SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; + m1:= m3; + SELECT m1 IS NULL, m1 IS NOT NULL, m2 is NULL, m2 IS NOT NULL; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Call associative array method directly +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + m1 marks_t:= marks_t(1 => 62); +BEGIN + m1.delete; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Create an associative array type sharing the same name as a built-in function +--echo # TODO: This should be allowed +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE trim IS TABLE OF NUMBER INDEX BY INTEGER; + m1 trim:= trim(1=>1); +BEGIN + SELECT m1(1); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Create an associative array type sharing the same name as a built-in function (2) +--echo # TODO: This should be allowed +--echo # +DELIMITER $$; +--error ER_WRONG_PARAMETERS_TO_NATIVE_FCT +DECLARE + TYPE acos IS TABLE OF NUMBER INDEX BY INTEGER; + m1 acos:= acos(1=>1); +BEGIN + SELECT m1(1); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Create an associative array type sharing the same name as a stored procedure +--echo # +DELIMITER $$; +CREATE PROCEDURE p1() AS +BEGIN + SELECT 'p1()'; +END; +$$ + +DECLARE + TYPE p1 IS TABLE OF NUMBER INDEX BY INTEGER; + m1 p1:= p1(1=>1); +BEGIN + SELECT m1(1); +END; +$$ +DELIMITER ;$$ + +DROP PROCEDURE p1; + +--echo # +--echo # Create an associative array type sharing the same name as a package +--echo # +DELIMITER $$; +CREATE PACKAGE pkg1 AS + PROCEDURE p1(); +END; +$$ +CREATE PACKAGE BODY pkg1 AS + PROCEDURE p1() AS + BEGIN + SELECT 'p1()'; + END; +END; +$$ + +DECLARE + TYPE pkg1 IS TABLE OF NUMBER INDEX BY INTEGER; + m1 pkg1:= pkg1(1=>1); +BEGIN + SELECT m1(1); +END; +$$ +DELIMITER ;$$ + +DROP PACKAGE pkg1; + + +--echo # +--echo # Create an associative array sharing the same name as a package +--echo # The expected behaviour is that the associative array local variable +--echo # will hide the package. On the other hand prepared statements +--echo # will not see the local variable. +--echo # +DELIMITER $$; +CREATE PACKAGE pkg1 AS + PROCEDURE prior(a VARCHAR); + PROCEDURE prior1(a VARCHAR); + FUNCTION prior(a VARCHAR) RETURN VARCHAR; +END; +$$ +CREATE PACKAGE BODY pkg1 AS + PROCEDURE prior(a VARCHAR) AS + BEGIN + SELECT 'pkg1.prior()'; + END; + PROCEDURE prior1(a VARCHAR) AS + BEGIN + SELECT 'pkg1.prior1()'; + END; + FUNCTION prior(a VARCHAR) RETURN VARCHAR AS + BEGIN + RETURN 'pkg1.prior()'; + END; +END; +$$ + +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY VARCHAR2(20); + pkg1 marks_t:= marks_t('1'=>1,'2'=>2); +BEGIN + SELECT pkg1.prior('2'); + CALL pkg1.prior('2'); + EXECUTE IMMEDIATE 'CALL pkg1.prior(2)'; + EXECUTE IMMEDIATE 'CALL pkg1.prior1(2)'; +END; +$$ +DELIMITER ;$$ + +DROP PACKAGE pkg1; + + +--echo # +--echo # A scalar variable does not shadow a package +--echo # + +DELIMITER $$; +CREATE PACKAGE pkg AS + PROCEDURE p1; + FUNCTION f1 RETURN TEXT; +END; +$$ +CREATE PACKAGE BODY pkg AS + p1 INT; + PROCEDURE p1 AS + BEGIN + SELECT 'pkg.p1'; + END; + FUNCTION f1 RETURN TEXT AS + BEGIN + RETURN 'pkg.f1'; + END; +END; +$$ +CREATE PROCEDURE p1 AS + pkg INT; -- This scalar variable does not shadow the package 'pkg' +BEGIN + pkg.p1(); -- This is resolved to the package procedure call + SELECT pkg.f1(); -- This is resolved to the package function call +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP PACKAGE pkg; + + +--echo # +--echo # A ROW variable does not shadow a package +--echo # + +DELIMITER $$; +CREATE PACKAGE pkg AS + PROCEDURE p1; + FUNCTION f1 RETURN TEXT; +END; +$$ +CREATE PACKAGE BODY pkg AS + p1 INT; + PROCEDURE p1 AS + BEGIN + SELECT 'pkg.p1'; + END; + FUNCTION f1 RETURN TEXT AS + BEGIN + RETURN 'pkg.f1'; + END; +END; +$$ +CREATE PROCEDURE p1 AS + pkg ROW(p1 INT,p2 INT); -- This ROW variable does not shadow the package 'pkg' +BEGIN + pkg.p1(); -- This is resolved to the package procedure call + SELECT pkg.f1(); -- This is resolved to the package function call +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP PACKAGE pkg; + +--echo # +--echo # Checking that when the routine execution leaves +--echo # a DECLARE..BEGIN..END block with an assoc array declared, +--echo # the memory used by the assoc array is freed. +--echo # + +DELIMITER /; +CREATE FUNCTION memory_used() RETURN BIGINT AS +BEGIN + RETURN (SELECT variable_value FROM INFORMATION_SCHEMA.SESSION_STATUS + WHERE variable_name='memory_used'); +END; +/ +CREATE PROCEDURE p1 AS + memory_used0 BIGINT:= memory_used(); +BEGIN + DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY VARCHAR2(20); + person_by_nickname table_of_peson_t; + BEGIN + FOR i IN 1..3 + LOOP + person_by_nickname(i):= person_t(CONCAT('first_name',i), + CONCAT('last_name',i)); + SELECT i, memory_used()-memory_used0 >0 AS diff; + END LOOP; + END; + SELECT memory_used()-memory_used0 >0 AS diff1; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION memory_used; + + +--echo # +--echo # Ensure that subqueries are disallowed for keys +--echo # +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES (1); +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 78); +BEGIN + SELECT marks((SELECT * FROM t1)); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 78); +BEGIN + SELECT marks((SELECT 1)); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; + p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN + SELECT p((SELECT * FROM t1)).first_name; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; + p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN + SELECT p((SELECT 1)).first_name; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Ensure that fields are disallowed for keys +--echo # +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 78); +BEGIN + SELECT marks(t1.a) FROM t1; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; + p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN + SELECT p(t1.a).first_name FROM t1; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Ensure that ROWNUM is disallowed for keys +--echo # +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 78); +BEGIN + SELECT marks(ROWNUM) FROM t1; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; + p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN + SELECT p(ROWNUM).first_name FROM t1; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Ensure that WINDOW functions are disallowed for keys +--echo # +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t:= marks_t(1 => 78); +BEGIN + SELECT marks(COUNT(t1.a)) FROM t1; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DECLARE + TYPE person_t IS RECORD + ( + first_name VARCHAR(64), + last_name VARCHAR(64) + ); + TYPE table_of_peson_t IS TABLE OF person_t INDEX BY INTEGER; + p table_of_peson_t:= table_of_peson_t(1 => person_t('Michael', 'Widenius')); +BEGIN + SELECT p(COUNT(t1.a)).first_name FROM t1; +END; +$$ +DELIMITER ;$$ + +DROP TABLE t1; \ No newline at end of file diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/suite.pm b/plugin/type_assoc_array/mysql-test/type_assoc_array/suite.pm new file mode 100644 index 00000000000..9ea9f43f8c4 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/suite.pm @@ -0,0 +1,7 @@ +package My::Suite::Type_assoc_array; + +@ISA = qw(My::Suite); + +sub is_default { 1 } + +bless { }; diff --git a/plugin/type_assoc_array/sql_type_assoc_array.cc b/plugin/type_assoc_array/sql_type_assoc_array.cc new file mode 100644 index 00000000000..df600fea1ad --- /dev/null +++ b/plugin/type_assoc_array/sql_type_assoc_array.cc @@ -0,0 +1,2573 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#define MYSQL_SERVER +#include "my_global.h" +#include "mysql/plugin_data_type.h" +#include "sql_type.h" +#include "item.h" +#include "sql_type_assoc_array.h" +#include "field.h" +#include "sql_class.h" +#include "item.h" +#include "sql_select.h" // Virtual_tmp_table +#include "sp_rcontext.h" +#include "sp_head.h" +#include "sp_instr.h" +#include "sp_type_def.h" + + +/* + Support class to rollback the change list when append_to_log is called. + The following query for example: + INSERT INTO t1 VALUES (first_names(TRIM(nick || ' '))); + + will create a new item during fix_fields. + + The raii in the class suffix denotes that this class will + automatically rollback the change list upon destruction. +*/ +class Item_change_list_savepoint_raii : public Item_change_list_savepoint +{ +private: + THD *m_thd; +public: + Item_change_list_savepoint_raii(THD *thd) + : Item_change_list_savepoint(thd) + , m_thd(thd) + { } + ~Item_change_list_savepoint_raii() + { + rollback(m_thd); + } +}; + + +class Item_field_packable +{ +protected: + Binary_string *m_buffer; + uint m_offset; +public: + Item_field_packable() + : m_buffer(nullptr) + , m_offset(0) + {} + + void set_buffer(Binary_string *buffer) + { + m_buffer= buffer; + } + + void set_offset(uint offset) + { + m_offset= offset; + } + + virtual ~Item_field_packable()= default; + virtual const uchar *unpack() const = 0; + virtual bool pack() = 0; + + uchar *ptr() const + { + return reinterpret_cast(&(*m_buffer)[0]) + m_offset; + } + uint buffer_length() const + { + return m_buffer->alloced_length() - m_offset; + } +}; + + +class Item_field_packable_scalar: public Item_field, + public Item_field_packable +{ +public: + Item_field_packable_scalar(THD *thd, Field *field) + :Item_field(thd, field) + { + } + ~Item_field_packable_scalar()= default; + + const uchar *unpack() const override + { + DBUG_ASSERT(field); + DBUG_ASSERT(m_buffer); + + return field->unpack(field->ptr, ptr(), ptr() + buffer_length()); + } + + bool pack() override + { + DBUG_ASSERT(field); + DBUG_ASSERT(m_buffer); + + auto length= field->packed_col_length(field->ptr, + field->value_length()) + 1; + if (unlikely(m_buffer->realloc(length))) + return true; + + field->pack(ptr(), field->ptr); + return false; + } + + double val_real() override + { + unpack(); + return Item_field::val_real(); + } + longlong val_int() override + { + unpack(); + return Item_field::val_int(); + } + bool val_bool() override + { + unpack(); + return Item_field::val_bool(); + } + my_decimal *val_decimal(my_decimal *dec) override + { + unpack(); + return Item_field::val_decimal(dec); + } + String *val_str(String *str) override + { + unpack(); + return Item_field::val_str(str); + } + void save_result(Field *to) override + { + unpack(); + Item_field::save_result(to); + } + double val_result() override + { + unpack(); + return Item_field::val_result(); + } + longlong val_int_result() override + { + unpack(); + return Item_field::val_int_result(); + } + bool val_native(THD *thd, Native *to) override + { + unpack(); + return Item_field::val_native(thd, to); + } + bool val_native_result(THD *thd, Native *to) override + { + unpack(); + return Item_field::val_native_result(thd, to); + } + String *str_result(String* tmp) override + { + unpack(); + return Item_field::str_result(tmp); + } + my_decimal *val_decimal_result(my_decimal *dec) override + { + unpack(); + return Item_field::val_decimal_result(dec); + } + bool val_bool_result() override + { + unpack(); + return Item_field::val_bool_result(); + } + bool is_null_result() override + { + unpack(); + return Item_field::is_null_result(); + } + bool send(Protocol *protocol, st_value *buffer) override + { + unpack(); + return Item_field::send(protocol, buffer); + } + int save_in_field(Field *field,bool no_conversions) override + { + unpack(); + return Item_field::save_in_field(field, no_conversions); + } +}; + + +class Item_field_packable_row: public Item_field_row, + public Item_field_packable +{ +public: + Item_field_packable_row(THD *thd, Field *field) + :Item_field_row(thd, field) + { + } + ~Item_field_packable_row()= default; + + bool + add_array_of_item_field(THD *thd) + { + DBUG_ASSERT(field); + DBUG_ASSERT(field->virtual_tmp_table()); + + const Virtual_tmp_table &vtable= *field->virtual_tmp_table(); + + DBUG_ASSERT(vtable.s->fields); + DBUG_ASSERT(!arg_count); + + if (alloc_arguments(thd, vtable.s->fields)) + return true; + + for (arg_count= 0; arg_count < vtable.s->fields; arg_count++) + { + auto field= vtable.field[arg_count]; + auto scalar= new (thd->mem_root) Item_field_packable_scalar(thd, field); + + if (!(args[arg_count]= scalar)) + return true; + } + return false; + } + + Item *do_get_copy(THD *thd) const override + { + DBUG_ASSERT(0); + return get_item_copy(thd, this); + } + + static uint packed_col_length(Field *field) + { + DBUG_ASSERT(field); + DBUG_ASSERT(field->virtual_tmp_table()); + + const Virtual_tmp_table &vtable= *field->virtual_tmp_table(); + uint length= 0; + for (uint i= 0; i < vtable.s->fields; i++) + { + auto field= vtable.field[i]; + length+= field->packed_col_length(field->ptr, + field->value_length()) + 1; + } + + return length; + } + + const uchar* unpack() const override + { + DBUG_ASSERT(field); + DBUG_ASSERT(field->virtual_tmp_table()); + DBUG_ASSERT(m_buffer); + + uint offset= 0; + for (uint i= 0; i < arg_count; i++) + { + Item_field_packable *item_elem= + dynamic_cast(args[i]); + DBUG_ASSERT(item_elem); + item_elem->set_buffer(m_buffer); + item_elem->set_offset(offset); + + auto end= item_elem->unpack(); + if (unlikely(!end)) + return nullptr; + offset= end - ptr(); + } + + return ptr() + offset; + } + + bool pack() override + { + DBUG_ASSERT(field); + DBUG_ASSERT(field->virtual_tmp_table()); + DBUG_ASSERT(m_buffer); + + const Virtual_tmp_table &vtable= *field->virtual_tmp_table(); + + uint length= packed_col_length(field); + if (unlikely(m_buffer->realloc(length))) + return true; + + uint offset= 0; + for (uint i= 0; i < arg_count; i++) + { + auto field= vtable.field[i]; + auto end= field->pack(ptr() + offset, field->ptr); + if (unlikely(!end)) + return true; + + offset= end - ptr(); + } + + return false; + } +}; + + +/* + Invalidate the position of a Rewritable_query_parameter object + in the query string. We use this when we want to rewrite nested rqps + which is the case for the associative array methods or element accessors. +*/ +static void invalidate_rqp(const Item *item, void *arg) +{ + DBUG_ASSERT(item); + if (arg && static_cast(arg) == item) + return; + + Rewritable_query_parameter *parg; + if ((parg= + dynamic_cast(const_cast(item)))) + parg->pos_in_query= 0; +} + + +class Item_method_base +{ +protected: + uint m_var_idx; + Lex_ident_sys m_var_name; + const Sp_rcontext_handler *m_rcontext_handler; + THD *m_thd; +public: + Item_method_base(THD *thd) + :m_var_idx(0), + m_rcontext_handler(nullptr), + m_thd(thd) + { } + virtual ~Item_method_base() = default; + + virtual bool init_method(const Lex_ident_sys &item_name, + const Lex_ident_cli_st &query_fragment)= 0; + sp_rcontext *get_rcontext(sp_rcontext *local_ctx) const + { + return m_rcontext_handler->get_rcontext(local_ctx); + } + Item_field *get_variable(sp_rcontext *ctx= nullptr) const + { + if (!ctx) + ctx= m_thd->spcont; + return get_rcontext(ctx)->get_variable(m_var_idx); + } + Item_composite_base *get_composite_field() const + { + Item_field *item= get_variable(m_thd->spcont); + DBUG_ASSERT(item); + return dynamic_cast(item); + } +}; + + +template +class Item_method_func :public T, + public Rewritable_query_parameter, + public Item_method_base +{ + +public: + Item_method_func(THD *thd, Item *arg) + :T(thd, arg), + Rewritable_query_parameter(), + Item_method_base(thd) + { } + Item_method_func(THD *thd) + :T(thd), + Rewritable_query_parameter(), + Item_method_base(thd) + { } + virtual ~Item_method_func() = default; + + bool init_method(const Lex_ident_sys &item_name, + const Lex_ident_cli_st &query_fragment) override + { + DBUG_ASSERT(!item_name.is_null()); + sp_variable *spvar= nullptr; + m_var_name= item_name; + + spvar= m_thd->lex->find_variable(&m_var_name, &m_rcontext_handler); + DBUG_ASSERT(spvar); + + m_var_idx= spvar->offset; + + T::traverse_cond(invalidate_rqp, nullptr, Item::traverse_order::PREFIX); + + pos_in_query= query_fragment.pos() - m_thd->lex->sphead->m_tmp_query; + len_in_query= query_fragment.length; + + return false; + } + + + Rewritable_query_parameter *get_rewritable_query_parameter() override + { return this; } + + + bool append_value_for_log(THD *thd, String *str) + { + StringBuffer str_value_holder(&my_charset_latin1); + Item *item= T::this_item(); + String *str_value= item->type_handler()->print_item_value(thd, item, + &str_value_holder); + return (str_value ? + str->append(*str_value) : + str->append(NULL_clex_str)); + } + + + bool append_for_log(THD *thd, String *str) override + { + Item_change_list_savepoint_raii savepoint(thd); + if (T::fix_fields_if_needed(thd, NULL)) + return true; + + if (limit_clause_param) + return str->append_ulonglong(T::val_uint()); + + Item_field *item=get_variable(thd->spcont); + DBUG_ASSERT(item); + + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer tmp(cs); + + return tmp.append(T::name, &my_charset_utf8mb3_bin) || + str->append(STRING_WITH_LEN("NAME_CONST(")) || + append_query_string(cs, str, tmp.ptr(), tmp.length(), false) || + str->append(',') || + append_value_for_log(thd, str) || str->append(')'); + } + + + void print(String *str, enum_query_type query_type) override + { + if (str->append(m_var_name) || + str->append('@')) + return; + str->qs_append(m_var_idx); + if (str->append('.')) + return; + T::print(str, query_type); + } +}; + + +typedef Item_method_func Item_bool_method; +typedef Item_method_func Item_long_method; +typedef Item_method_func Item_handled_method; + + +class Func_handler_assoc_array_first: + public Item_handled_func::Handler_str +{ +public: + const Type_handler * + return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_string; + } + + + bool fix_length_and_dec(Item_handled_func *item) const override + { + auto var_field= + dynamic_cast(get_composite_field(item)); + DBUG_ASSERT(var_field); + + item->collation.collation= var_field->get_key_field()->charset(); + + return false; + } + + + static Field_composite *get_composite_field(Item *item) + { + auto method= dynamic_cast(item); + DBUG_ASSERT(method); + + auto method_var= method->get_variable(); + DBUG_ASSERT(method_var); + + return dynamic_cast(method_var->field); + } + + + virtual String *val_str(Item_handled_func *item, String *tmp) const override + { + auto var_field= get_composite_field(item); + DBUG_ASSERT(var_field); + + if ((item->null_value= var_field->get_key(tmp, true))) + return NULL; + + return tmp; + } +}; + + +class Func_handler_assoc_array_last: + public Func_handler_assoc_array_first +{ + String *val_str(Item_handled_func *item, String *tmp) const override + { + auto var_field= get_composite_field(item); + DBUG_ASSERT(var_field); + + if ((item->null_value= var_field->get_key(tmp, false))) + return NULL; + + return tmp; + } +}; + + +class Func_handler_assoc_array_next: + public Func_handler_assoc_array_first +{ + String *val_str(Item_handled_func *item, String *tmp) const override + { + DBUG_ASSERT(item->fixed()); + + auto var_field= get_composite_field(item); + DBUG_ASSERT(var_field); + + auto curr_key= item->arguments()[0]->val_str(); + if ((item->null_value= !curr_key || + var_field->get_next_key(curr_key, tmp))) + return NULL; + + return tmp; + } +}; + + +class Func_handler_assoc_array_prior: + public Func_handler_assoc_array_first +{ + String *val_str(Item_handled_func *item, String *tmp) const override + { + DBUG_ASSERT(item->fixed()); + + auto var_field= get_composite_field(item); + DBUG_ASSERT(var_field); + + auto curr_key= item->arguments()[0]->val_str(); + if ((item->null_value= !curr_key || + var_field->get_prior_key(curr_key, tmp))) + return NULL; + + return tmp; + } +}; + + +/* Item_funcs for associative array methods */ + +class Item_func_assoc_array_first :public Item_handled_method +{ +public: + Item_func_assoc_array_first(THD *thd) + :Item_handled_method(thd) {} + bool check_arguments() const override + { + return false; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("first") }; + return name; + } + bool fix_length_and_dec(THD *thd) override + { + static Func_handler_assoc_array_first ha_str_key; + set_func_handler(&ha_str_key); + return m_func_handler->fix_length_and_dec(this); + } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_last :public Item_handled_method +{ +public: + Item_func_assoc_array_last(THD *thd) + :Item_handled_method(thd) {} + bool check_arguments() const override + { + return false; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("last") }; + return name; + } + bool fix_length_and_dec(THD *thd) override; + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_next :public Item_handled_method +{ +public: + Item_func_assoc_array_next(THD *thd, Item *curr_key) + :Item_handled_method(thd, curr_key) {} + bool check_arguments() const override + { + return false; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("next") }; + return name; + } + bool fix_length_and_dec(THD *thd) override; + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_prior :public Item_handled_method +{ +public: + Item_func_assoc_array_prior(THD *thd, Item *curr_key) + :Item_handled_method(thd, curr_key) {} + bool check_arguments() const override + { + return false; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("prior") }; + return name; + } + bool fix_length_and_dec(THD *thd) override; + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_count :public Item_long_method +{ + bool check_arguments() const override + { return arg_count != 0; } +public: + Item_func_assoc_array_count(THD *thd) + :Item_long_method(thd) {} + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("count") }; + return name; + } + longlong val_int() override + { + DBUG_ASSERT(fixed()); + + auto *array= get_composite_field(); + DBUG_ASSERT(array); + + return array->rows(); + } + bool fix_length_and_dec(THD *thd) override + { + decimals= 0; + max_length= 10; + set_maybe_null(); + return FALSE; + } + bool check_vcol_func_processor(void *arg) override + { + return mark_unsupported_function(func_name(), "()", arg, VCOL_IMPOSSIBLE); + } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_exists :public Item_bool_method +{ + bool check_arguments() const override + { return arg_count != 1; } + +public: + Item_func_assoc_array_exists(THD *thd, Item *key) + :Item_bool_method(thd, key) {} + bool val_bool() override + { + DBUG_ASSERT(fixed()); + + if (args[0]->null_value) + return false; + + auto *array= get_composite_field(); + DBUG_ASSERT(array); + + return array->element_by_key(current_thd, args[0]->val_str()) != NULL; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("exists") }; + return name; + } + bool fix_length_and_dec(THD *thd) override + { + decimals= 0; + max_length= 1; + set_maybe_null(); + return FALSE; + } + bool check_vcol_func_processor(void *arg) override + { + return mark_unsupported_function(func_name(), "()", arg, VCOL_IMPOSSIBLE); + } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_delete :public Item_bool_method +{ + bool check_arguments() const override { return arg_count > 1; } + +public: + Item_func_assoc_array_delete(THD *thd) + :Item_bool_method(thd) + {} + Item_func_assoc_array_delete(THD *thd, Item *key) + :Item_bool_method(thd, key) + {} + bool val_bool() override + { + DBUG_ASSERT(fixed()); + + Item_field *item= get_variable(m_thd->spcont); + DBUG_ASSERT(item); + + auto field= dynamic_cast(item-> + field_for_view_update()->field); + if (arg_count == 0) + return field->delete_all_elements(); + else if (arg_count == 1) + return field->delete_element_by_key(args[0]->val_str()); + + return false; + } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("delete") }; + return name; + } + bool fix_length_and_dec(THD *thd) override + { + decimals= 0; + max_length= 1; + set_maybe_null(); + return FALSE; + } + bool check_vcol_func_processor(void *arg) override + { + return mark_unsupported_function(func_name(), "()", arg, VCOL_IMPOSSIBLE); + } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +bool Item_func_assoc_array_last::fix_length_and_dec(THD *thd) +{ + static Func_handler_assoc_array_last ha_str_key; + set_func_handler(&ha_str_key); + return m_func_handler->fix_length_and_dec(this); +} + + +bool Item_func_assoc_array_next::fix_length_and_dec(THD *thd) +{ + static Func_handler_assoc_array_next ha_str_key; + set_func_handler(&ha_str_key); + return m_func_handler->fix_length_and_dec(this); +} + + +bool Item_func_assoc_array_prior::fix_length_and_dec(THD *thd) +{ + static Func_handler_assoc_array_prior ha_str_key; + set_func_handler(&ha_str_key); + return m_func_handler->fix_length_and_dec(this); +} + + +static +Item_method_base *sp_get_assoc_array_key(THD *thd, + List *args, + bool is_first) +{ + if (args) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), is_first ? "FIRST" : "LAST", + "", 0, args->elements); + return NULL; + } + + return is_first ? + (Item_method_base *)new (thd->mem_root) Item_func_assoc_array_first(thd) : + (Item_method_base *)new (thd->mem_root) Item_func_assoc_array_last(thd); +} + + +static +Item_method_base *sp_get_assoc_array_next_or_prior(THD *thd, + List *args, + bool is_next) +{ + if (!args || args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), is_next ? "NEXT" : "PRIOR", + "", 1, args ? args->elements : 0); + return NULL; + } + + Item_args args_item(thd, *args); + return is_next ? (Item_method_base *) + new (thd->mem_root) + Item_func_assoc_array_next(thd, + args_item.arguments()[0]) : + (Item_method_base *) + new (thd->mem_root) + Item_func_assoc_array_prior(thd, + args_item.arguments()[0]); +} + + +static +Item_method_base *sp_get_assoc_array_count(THD *thd, List *args) +{ + if (args) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "COUNT", + "", 0, args->elements); + return NULL; + } + + return new (thd->mem_root) Item_func_assoc_array_count(thd); +} + + +static +Item_method_base *sp_get_assoc_array_exists(THD *thd, List *args) +{ + if (!args || args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "EXISTS", + "", 1, args ? args->elements : 0); + return NULL; + } + + Item_args args_item(thd, *args); + return new (thd->mem_root) + Item_func_assoc_array_exists(thd, args_item.arguments()[0]); +} + + +static +Item_method_base *sp_get_assoc_array_delete(THD *thd, + List *args) +{ + if (args) + { + if (args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "DELETE", + "", 1, args->elements); + return NULL; + } + + Item_args args_item(thd, *args); + return new (thd->mem_root) + Item_func_assoc_array_delete(thd, args_item.arguments()[0]); + } + else + return new (thd->mem_root) Item_func_assoc_array_delete(thd); +} + + +/**************************************************************************** + Field_assoc_array, e.g. for associative array type SP variables +****************************************************************************/ + +/* + The data structure used to store the key-value pairs in the + associative array (TREE) +*/ +struct Assoc_array_data :public Sql_alloc +{ + Assoc_array_data(String &&key, Binary_string &&value) + { + m_key.swap(key); + m_value.swap(value); + } + + String m_key; + Binary_string m_value; +}; + + +static int assoc_array_tree_cmp(void *arg, const void *lhs_arg, + const void *rhs_arg) +{ + const Assoc_array_data *lhs= (const Assoc_array_data *)lhs_arg; + const Assoc_array_data *rhs= (const Assoc_array_data *)rhs_arg; + + auto field= reinterpret_cast(arg); + DBUG_ASSERT(field); + + if (field->type() == MYSQL_TYPE_VARCHAR) + return sortcmp(&lhs->m_key, &rhs->m_key, field->charset()); + + return field->cmp(reinterpret_cast(lhs->m_key.ptr()), + reinterpret_cast(rhs->m_key.ptr())); +} + + +static int assoc_array_tree_del(void *data_arg, TREE_FREE, void*) +{ + DBUG_ASSERT(data_arg); + Assoc_array_data *data= (Assoc_array_data *)data_arg; + + // Explicitly set the key's buffer to NULL to deallocate + // the memory held in it's internal buffer. + data->m_key.set((const char*)NULL, 0, &my_charset_bin); + data->m_value.set((const char*)NULL, 0); + return 0; +} + + +Field_assoc_array::Field_assoc_array(uchar *ptr_arg, + const LEX_CSTRING *field_name_arg) + :Field_composite(ptr_arg, field_name_arg), + m_table(nullptr), + m_table_share(nullptr), + m_def(nullptr), + m_element_field(nullptr) +{ + init_alloc_root(PSI_NOT_INSTRUMENTED, &m_mem_root, 512, 0, MYF(0)); + + m_table_share= (TABLE_SHARE*) alloc_root(&m_mem_root, sizeof(TABLE_SHARE)); + if (!m_table_share) + return; + + bzero((void *)m_table_share, sizeof(TABLE_SHARE)); + + m_table_share->table_cache_key= empty_clex_str; + m_table_share->table_name= Lex_ident_table(empty_clex_str); + + init_tree(&m_tree, 0, 0, + sizeof(Assoc_array_data), assoc_array_tree_cmp, + assoc_array_tree_del, NULL, + MYF(MY_THREAD_SPECIFIC | MY_TREE_WITH_DELETE)); +} + + +Field_assoc_array::~Field_assoc_array() +{ + if (m_table) + m_table->alias.free(); + delete_tree(&m_tree, 0); + + free_root(&m_mem_root, MYF(0)); +} + + +bool Field_assoc_array::sp_prepare_and_store_item(THD *thd, Item **value) +{ + DBUG_ENTER("Field_assoc_array::sp_prepare_and_store_item"); + + if (value[0]->type() == Item::NULL_ITEM) + { + delete_all_elements(); + + DBUG_RETURN(false); + } + + Item *src; + if (!(src= thd->sp_fix_func_item(value)) || + src->cmp_type() != ROW_RESULT || + src->type_handler() != Type_handler_assoc_array::singleton()) + { + my_error(ER_OPERAND_COLUMNS, MYF(0), m_table->s->fields); + DBUG_RETURN(true); + } + + src->bring_value(); + auto composite= dynamic_cast(src); + DBUG_ASSERT(composite); + + delete_all_elements(); + + String src_key; + if (!composite->get_key(&src_key, true)) + { + do + { + Item **src_elem= composite->element_addr_by_key(thd, NULL, &src_key); + if (!src_elem) + goto error; + + if (m_element_field->sp_prepare_and_store_item(thd, src_elem)) + goto error; + + Binary_string pack_buffer; + if (create_element_buffer(thd, &pack_buffer)) + goto error; + + m_item_pack->set_buffer(&pack_buffer); + m_item_pack->pack(); + + String key_copy; + if (copy_and_convert_key(&src_key, key_copy)) + goto error; + + if (insert_element(std::move(key_copy), std::move(pack_buffer))) + goto error; + + set_notnull(); + } while (!composite->get_next_key(&src_key, &src_key)); + } + + DBUG_RETURN(false); + +error: + set_null(); + DBUG_RETURN(true); +} + + +bool Field_assoc_array::insert_element(String &&key, Binary_string &&element) +{ + Assoc_array_data data(std::move(key), std::move(element)); + + if (unlikely(!tree_insert(&m_tree, &data, 0, m_key_field))) + return true; + + data.m_key.release(); + data.m_value.release(); + + return false; +} + + +Item_field *Field_assoc_array::element_by_key(THD *thd, String *key) +{ + if (!key) + return NULL; + + String key_copy; + if (copy_and_convert_key(key, key_copy)) + return nullptr; + + bool is_inserted= false; + Assoc_array_data *tree_data= (Assoc_array_data *) + tree_search(&m_tree, &key_copy, + m_key_field); + if (!tree_data) + { + // Create an element for the key if not found + Binary_string pack_buffer; + if (create_element_buffer(thd, &pack_buffer)) + return nullptr; + + if (insert_element(std::move(key_copy), std::move(pack_buffer))) + return nullptr; + set_notnull(); + + is_inserted= true; + + if (copy_and_convert_key(key, key_copy)) + return nullptr; + tree_data= (Assoc_array_data *) tree_search(&m_tree, &key_copy, + m_key_field); + DBUG_ASSERT(tree_data); + } + + m_item_pack->set_buffer(&tree_data->m_value); + + if (!is_inserted) + m_item_pack->unpack(); + + return dynamic_cast(m_item_pack); +} + + +Item_field *Field_assoc_array::element_by_key(THD *thd, String *key) const +{ + if (!key) + return NULL; + + String key_copy; + if (copy_and_convert_key(key, key_copy)) + return NULL; + + Assoc_array_data *data= (Assoc_array_data *) + tree_search((TREE *)&m_tree, + &key_copy, + m_key_field); + if (!data) + return NULL; + + m_item_pack->set_buffer(&data->m_value); + m_item_pack->unpack(); + + return dynamic_cast(m_item_pack); +} + + +bool Field_assoc_array::copy_and_convert_key(const String *key, + String &key_copy) const +{ + DBUG_ASSERT(key); + + uint errors; + auto &key_def= *m_def->begin(); + + if (unlikely(key_copy.copy(key, key_def.charset, &errors))) + return true; + + if (key_def.type_handler()->field_type() == MYSQL_TYPE_VARCHAR) + { + if (key_copy.length() > key_def.length) + { + my_error(ER_TOO_LONG_KEY, MYF(0), key_def.length); + return true; + } + } + else + { + auto type_handler= dynamic_cast + (key_def.type_handler()); + DBUG_ASSERT(type_handler); + + /* + Convert the key to a number to perform range check + */ + char *endptr; + int error; + + longlong key_ll; + ulonglong key_ull; + + bool is_unsigned= type_handler->is_unsigned(); + auto cs= m_key_field->charset(); + + key_ull= cs->strntoull10rnd(key_copy.ptr(), key_copy.length(), + is_unsigned, &endptr, &error); + key_ll= (longlong) key_ull; + + if (error || (endptr != key_copy.end())) + { + my_error(ER_WRONG_VALUE, MYF(0), "ASSOCIATIVE ARRAY KEY", + key_copy.c_ptr()); + return true; + } + + if (is_unsigned) + { + if (key_ull > type_handler->type_limits_int()->max_unsigned()) + error= 1; + } + else + { + if (key_ll < type_handler->type_limits_int()->min_signed() || + key_ll > type_handler->type_limits_int()->max_signed()) + error= 1; + } + + if (error) + { + my_error(ER_WRONG_VALUE, MYF(0), "ASSOCIATIVE ARRAY KEY", + key_copy.c_ptr()); + return true; + } + + key_copy.length(0); + if (unlikely(key_copy.alloc(8))) + return true; + + if (is_unsigned) + key_copy.q_append_int64((longlong)key_ull); + else + key_copy.q_append_int64(key_ll); + } + + return false; +} + + +bool Field_assoc_array::unpack_key(const Binary_string &key, + String *key_dst) const +{ + auto &key_def= *m_def->begin(); + if (key_def.type_handler()->field_type() == MYSQL_TYPE_VARCHAR) + { + if (key_dst->copy(key.ptr(), key.length(), m_key_field->charset())) + return true; + } + else + { + auto type_handler= dynamic_cast + (key_def.type_handler()); + const bool is_unsigned= type_handler->is_unsigned(); + /* + Reset the string length before appending + */ + key_dst->length(0); + + if (unlikely(key_dst->alloc(type_handler-> + type_limits_int()->char_length()))) + return true; + if (is_unsigned) + { + auto key_val= uint8korr(key.ptr()); + key_dst->qs_append(key_val); + } + else + { + auto key_val= sint8korr(key.ptr()); + key_dst->qs_append_int64(key_val); + } + + key_dst->set_charset(&my_charset_numeric); + } + + return false; +} + + +bool Field_assoc_array::create_fields(THD *thd) +{ + /* + Initialize the element field + */ + auto &value_def= *(++m_def->begin()); + if (value_def.is_column_type_ref()) + { + Column_definition cdef; + if (value_def.column_type_ref()->resolve_type_ref(thd, &cdef)) + return true; + + m_element_field= cdef.make_field(m_table_share, + thd->mem_root, + &empty_clex_str); + } + else + m_element_field= value_def.make_field(m_table_share, + thd->mem_root, + &empty_clex_str); + + if (!m_element_field) + return true; + + if (!(m_table= create_virtual_tmp_table(thd, m_element_field))) + return true; + + Field_row *field_row= dynamic_cast(m_element_field); + if (field_row) + field_row->field_name= field_name; + + /* + Initialize the key field + */ + m_key_def= *m_def->begin(); + + if (m_key_def.type_handler()->field_type() != MYSQL_TYPE_VARCHAR) + { + DBUG_ASSERT(dynamic_cast + (m_key_def.type_handler())); + + if (m_key_def.type_handler()->is_unsigned()) + m_key_def.set_handler(&type_handler_ulonglong); + else + m_key_def.set_handler(&type_handler_slonglong); + + /* + We need the key_def.pack_flag to be valid so that signedness can be + determined + */ + m_key_def.sp_prepare_create_field(thd, thd->mem_root); + } + + m_key_field= m_key_def.make_field(m_table_share, + thd->mem_root, + &empty_clex_str); + if (!m_key_field) + return true; + + return false; +} + + +bool Field_assoc_array::init_element_base(THD *thd) +{ + if (m_element_field) + return false; + + if (unlikely(create_fields(thd))) + return true; + + Field_row *field_row= dynamic_cast(m_element_field); + if (field_row) + { + auto &value_def= *(++m_def->begin()); + if (field_row->row_create_fields(thd, value_def)) + return true; + + auto pack= new (thd->mem_root) + Item_field_packable_row(thd, + m_element_field); + if (pack->add_array_of_item_field(thd)) + return true; + + m_item_pack= pack; + } + else + m_item_pack= new (thd->mem_root) + Item_field_packable_scalar(thd, + m_element_field); + + if (!m_item_pack) + return true; + + m_item= dynamic_cast(m_item_pack); + + return false; +} + + +Item_field * +Field_assoc_array::make_item_field_spvar(THD *thd, + const Spvar_definition &def) +{ + auto item= new (thd->mem_root) Item_field_assoc_array(thd, this); + if (!item) + return nullptr; + + item->set_array_def(thd, def.row_field_definitions()); + + if (init_element_base(thd)) + return nullptr; + + return item; +} + + +bool Field_assoc_array::create_element_buffer(THD *thd, Binary_string *buffer) +{ + DBUG_ASSERT(m_element_field); + DBUG_ASSERT(buffer); + + Field_row *field_row= dynamic_cast(m_element_field); + if (field_row) + { + uint length= Item_field_packable_row::packed_col_length(m_element_field); + return buffer->alloc(length); + } + + uint length= m_element_field->packed_col_length(m_element_field->ptr, + m_element_field->value_length()) + 1; + return buffer->alloc(length); +} + + +Item **Field_assoc_array::element_addr_by_key(THD *thd, String *key) +{ + if (!key) + return NULL; + + String key_copy; + if (copy_and_convert_key(key, key_copy)) + return NULL; + + Assoc_array_data *data= (Assoc_array_data *) + tree_search(&m_tree, + &key_copy, + m_key_field); + if (!data) + return NULL; + + m_item_pack->set_buffer(&data->m_value); + m_item_pack->unpack(); + + return &m_item; +} + + +bool Field_assoc_array::delete_all_elements() +{ + delete_tree(&m_tree, 0); + set_null(); + return false; +} + + +bool Field_assoc_array::delete_element_by_key(String *key) +{ + if (!key) + return false; // We do not care if the key is NULL + + String key_copy; + if (copy_and_convert_key(key, key_copy)) + return NULL; + + (void) tree_delete(&m_tree, &key_copy, 0, m_key_field); + return false; +} + + +uint Field_assoc_array::rows() const +{ + return m_tree.elements_in_tree; +} + + +bool Field_assoc_array::get_key(String *key, bool is_first) +{ + TREE_ELEMENT **last_pos; + TREE_ELEMENT *parents[MAX_TREE_HEIGHT+1]; + + Assoc_array_data *data= (Assoc_array_data *) + tree_search_edge(&m_tree, + parents, + &last_pos, is_first ? + offsetof(TREE_ELEMENT, left) : + offsetof(TREE_ELEMENT, right)); + if (data) + { + unpack_key(data->m_key, key); + return false; + } + + return true; +} + + +bool Field_assoc_array::get_next_key(const String *curr_key, String *next_key) +{ + return get_next_or_prior_key(curr_key, next_key, true); +} + + +bool Field_assoc_array::get_prior_key(const String *curr_key, String *prior_key) +{ + return get_next_or_prior_key(curr_key, prior_key, false); +} + + +bool Field_assoc_array::get_next_or_prior_key(const String *curr_key, + String *new_key, + bool is_next) +{ + DBUG_ASSERT(new_key); + + TREE_ELEMENT **last_pos; + TREE_ELEMENT *parents[MAX_TREE_HEIGHT+1]; + + if (!curr_key) + return true; + + String key_copy; + if (copy_and_convert_key(curr_key, key_copy)) + return true; + + Assoc_array_data *data= (Assoc_array_data *) + tree_search_key(&m_tree, + &key_copy, + parents, + &last_pos, + is_next ? HA_READ_AFTER_KEY : + HA_READ_BEFORE_KEY, + m_key_field); + if (data) + { + unpack_key(data->m_key, new_key); + return false; + } + return true; +} + + +bool Item_field_assoc_array::set_array_def(THD *thd, + Row_definition_list *def) +{ + DBUG_ASSERT(field); + + Field_assoc_array *field_assoc_array= dynamic_cast(field); + if (!field_assoc_array) + return true; + + field_assoc_array->set_array_def(def); + return false; +} + + +bool Item_assoc_array::fix_fields(THD *thd, Item **ref) +{ + DBUG_ASSERT(fixed() == 0); + null_value= 0; + base_flags&= ~item_base_t::MAYBE_NULL; + + Item **arg, **arg_end; + for (arg= args, arg_end= args + arg_count; arg != arg_end ; arg++) + { + if ((*arg)->fix_fields_if_needed(thd, arg)) + return TRUE; + // we can't assign 'item' before, because fix_fields() can change arg + Item *item= *arg; + + base_flags|= (item->base_flags & item_base_t::MAYBE_NULL); + with_flags|= item->with_flags; + } + base_flags|= item_base_t::FIXED; + return FALSE; +} + + +void Item_assoc_array::bring_value() +{ + for (uint i= 0; i < arg_count; i++) + args[i]->bring_value(); +} + + +void Item_assoc_array::print(String *str, enum_query_type query_type) +{ + str->append(m_name, current_thd->variables.character_set_client); + str->append('('); + for (uint i= 0; i < arg_count; i++) + { + if (i) + str->append(','); + + str->append('\''); + str->append(args[i]->name.str, args[i]->name.length); + str->append(STRING_WITH_LEN("'=>")); + args[i]->print(str, query_type); + } + str->append(')'); +} + + +Item *Item_assoc_array::do_build_clone(THD *thd) const +{ + Item **copy_args= static_cast + (alloc_root(thd->mem_root, sizeof(Item *) * arg_count)); + if (unlikely(!copy_args)) + return 0; + for (uint i= 0; i < arg_count; i++) + { + Item *arg_clone= args[i]->build_clone(thd); + if (!arg_clone) + return 0; + copy_args[i]= arg_clone; + } + Item_assoc_array *copy= (Item_assoc_array *) get_copy(thd); + if (unlikely(!copy)) + return 0; + copy->args= copy_args; + return copy; +} + + +uint Item_assoc_array::rows() const +{ + return arg_count; +} + + +bool Item_assoc_array::get_key(String *key, bool is_first) +{ + DBUG_ASSERT(key); + + uint current_arg; + + if (arg_count == 0) + return true; + + if (is_first) + current_arg= 0; + else + current_arg= arg_count - 1; + + key->set(args[current_arg]->name.str, + args[current_arg]->name.length, + &my_charset_bin); + return false; +} + + +bool Item_assoc_array::get_next_key(const String *curr_key, String *next_key) +{ + DBUG_ASSERT(curr_key); + DBUG_ASSERT(next_key); + + /* + The code below is pretty slow, but a constructor is a one time operation + */ + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->name.length == curr_key->length() && + !memcmp(args[i]->name.str, curr_key->ptr(), curr_key->length())) + { + if (i == arg_count - 1) + return true; + next_key->set(args[i + 1]->name.str, + args[i + 1]->name.length, + &my_charset_bin); + return false; + } + } + + return true; +} + + +Item *Item_assoc_array::element_by_key(THD *thd, String *key) +{ + DBUG_ASSERT(key); + + /* + See the comment in get_next_key() about the performance + */ + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->name.length == key->length() && + !memcmp(args[i]->name.str, key->ptr(), key->length())) + return args[i]; + } + + return NULL; +} + + +Item **Item_assoc_array::element_addr_by_key(THD *thd, + Item **addr_arg, + String *key) +{ + /* + See the comment in get_next_key() about the performance + */ + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->name.length == key->length() && + !memcmp(args[i]->name.str, key->ptr(), key->length())) + return &args[i]; + } + + return NULL; +} + + +Item_splocal_assoc_array_base::Item_splocal_assoc_array_base(Item *key) + :m_key(key) +{ + DBUG_ASSERT(m_key); + m_key->traverse_cond(invalidate_rqp, nullptr, Item::traverse_order::PREFIX); +} + + +Item_composite_base * +Item_splocal_assoc_array_element::get_composite_variable(sp_rcontext *ctx) const +{ + return dynamic_cast(get_variable(ctx)); +} + + +Item_splocal_assoc_array_element::Item_splocal_assoc_array_element(THD *thd, + const sp_rcontext_addr &addr, + const Lex_ident_sys &sp_var_name, + Item *key, const Type_handler *handler, + uint pos_in_q, uint len_in_q) + :Item_splocal(thd, addr.rcontext_handler(), &sp_var_name, + addr.offset(), handler, pos_in_q, len_in_q), + Item_splocal_assoc_array_base(key) +{ +} + + +bool Item_splocal_assoc_array_element::fix_fields(THD *thd, Item **ref) +{ + DBUG_ASSERT(fixed() == 0); + + if (m_key->fix_fields_if_needed(thd, &m_key)) + return true; + + if (!m_key->val_str()) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, MYF(0), + m_name.str); + return true; + } + + auto field= get_composite_variable(thd->spcont)->get_composite_field(); + DBUG_ASSERT(field); + + auto item= field->get_element_item(); + DBUG_ASSERT(item); + + set_handler(item->type_handler()); + return fix_fields_from_item(thd, ref, item); +} + + +Item * +Item_splocal_assoc_array_element::this_item() +{ + DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + DBUG_ASSERT(m_key->fixed()); + return get_composite_variable(m_thd->spcont)-> + element_by_key(m_thd, m_key->val_str()); +} + + +const Item * +Item_splocal_assoc_array_element::this_item() const +{ + DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + DBUG_ASSERT(m_key->fixed()); + return get_composite_variable(m_thd->spcont)-> + element_by_key(m_thd, m_key->val_str()); +} + + +Item ** +Item_splocal_assoc_array_element::this_item_addr(THD *thd, Item **ref) +{ + DBUG_ASSERT(m_sp == thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + DBUG_ASSERT(m_key->fixed()); + return get_composite_variable(thd->spcont)-> + element_addr_by_key(m_thd, ref, m_key->val_str()); +} + + +void Item_splocal_assoc_array_element::print(String *str, enum_query_type type) +{ + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); + str->append(prefix); + str->append(&m_name); + str->append('['); + m_key->print(str, type); + str->append(']'); + str->append('@'); + str->qs_append(m_var_idx); + str->append('['); + m_key->print(str, type); + str->append(']'); +} + + +bool Item_splocal_assoc_array_element::set_value(THD *thd, + sp_rcontext *ctx, + Item **it) +{ + LEX_CSTRING key; + if (Type_handler_assoc_array::singleton()-> + key_to_lex_cstring(thd, &m_key, name, key)) + return true; + + return get_rcontext(ctx)->set_variable_composite_by_name(thd, m_var_idx, key, + it); +} + + +Item_splocal_assoc_array_element_field:: +Item_splocal_assoc_array_element_field(THD *thd, + const sp_rcontext_addr &addr, + const Lex_ident_sys &sp_var_name, + Item *key, + const Lex_ident_sys &sp_field_name, + const Type_handler *handler, + uint pos_in_q, uint len_in_q) + :Item_splocal_row_field_by_name(thd, addr.rcontext_handler(), + &sp_var_name, &sp_field_name, + addr.offset(), handler, pos_in_q, len_in_q), + Item_splocal_assoc_array_base(key), + m_element_item(NULL) +{ +} + + +Item_composite_base *Item_splocal_assoc_array_element_field:: +get_composite_variable(sp_rcontext *ctx) const +{ + return dynamic_cast(get_variable(ctx)); +} + + +bool Item_splocal_assoc_array_element_field::fix_fields(THD *thd, Item **ref) +{ + DBUG_ASSERT(fixed() == 0); + + if (m_key->fix_fields_if_needed(thd, &m_key)) + return true; + + if (!m_key->val_str()) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, MYF(0), + m_name.str); + return true; + } + + auto field= get_composite_variable(thd->spcont)->get_composite_field(); + DBUG_ASSERT(field); + + auto element_item= field->get_element_item(); + DBUG_ASSERT(element_item); + + auto element_handler= element_item->type_handler()->to_composite(); + if (!element_handler || + element_handler->get_item_index(thd, + element_item->field_for_view_update(), + m_field_name, + m_field_idx)) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), + m_key->val_str()->c_ptr(), thd_where(thd)); + return true; + } + + Item *item= element_item->element_index(m_field_idx); + DBUG_ASSERT(item); + set_handler(item->type_handler()); + return fix_fields_from_item(thd, ref, item); +} + + +Item * +Item_splocal_assoc_array_element_field::this_item() +{ + DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + + auto elem= get_composite_variable(m_thd->spcont)-> + element_by_key(m_thd, m_key->val_str()); + if (!elem) + return nullptr; + + return elem->element_index(m_field_idx); +} + + +const Item * +Item_splocal_assoc_array_element_field::this_item() const +{ + DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + + auto elem= get_composite_variable(m_thd->spcont)-> + element_by_key(m_thd, m_key->val_str()); + if (!elem) + return nullptr; + + return elem->element_index(m_field_idx); +} + + +Item ** +Item_splocal_assoc_array_element_field::this_item_addr(THD *thd, Item **) +{ + DBUG_ASSERT(m_sp == thd->spcont->m_sp); + DBUG_ASSERT(fixed()); + + + auto elem= get_composite_variable(m_thd->spcont)-> + element_by_key(m_thd, m_key->val_str()); + if (!elem) + return nullptr; + + return elem->addr(m_field_idx); +} + + +void Item_splocal_assoc_array_element_field::print(String *str, + enum_query_type type) +{ + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); + str->append(prefix); + str->append(&m_name); + str->append('['); + m_key->print(str, type); + str->append(']'); + str->append('.'); + str->append(&m_field_name); + str->append('@'); + str->qs_append(m_var_idx); + str->append('['); + m_key->print(str, type); + str->append(']'); + str->append('.'); + str->qs_append(m_field_idx); +} + + +bool Item_splocal_assoc_array_element::append_for_log(THD *thd, String *str) +{ + Item_change_list_savepoint_raii savepoint(thd); + + if (fix_fields_if_needed(thd, NULL)) + return true; + + if (this_item() == NULL) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + m_key->val_str()->c_ptr()); + return true; + } + + if (limit_clause_param) + return str->append_ulonglong(val_uint()); + + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer tmp(cs); + + return tmp.append(name, &my_charset_utf8mb3_bin) || + str->append(STRING_WITH_LEN("NAME_CONST(")) || + append_query_string(cs, str, tmp.ptr(), tmp.length(), false) || + str->append(',') || + append_value_for_log(thd, str) || str->append(')'); +} + + +bool Item_splocal_assoc_array_element_field::append_for_log(THD *thd, + String *str) +{ + Item_change_list_savepoint_raii savepoint(thd); + + if (fix_fields_if_needed(thd, NULL)) + return true; + + if (this_item() == NULL) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + m_key->val_str()->c_ptr()); + return true; + } + + if (limit_clause_param) + return str->append_ulonglong(val_uint()); + + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer tmp(cs); + + return tmp.append(name, &my_charset_utf8mb3_bin) || + str->append(STRING_WITH_LEN("NAME_CONST(")) || + append_query_string(cs, str, tmp.ptr(), tmp.length(), false) || + str->append(',') || + append_value_for_log(thd, str) || str->append(')'); +} + + +class my_var_sp_assoc_array_element: public my_var_sp +{ +protected: + Item *m_key; + // Return the element definition as specified in the TABLE OF clause + const Spvar_definition &get_element_definition(THD *thd) const + { + Item_field *item= thd->get_variable(*this); + const Field_assoc_array *field= + dynamic_cast(item->field); + DBUG_ASSERT(field); + DBUG_ASSERT(field->get_array_def()); + DBUG_ASSERT(field->get_array_def()->elements == 2); + const Row_definition_list *def= field->get_array_def(); + List_iterator it(*const_cast(def)); + it++; // Skip the INDEX BY definition + return *(it++); + }; + +public: + my_var_sp_assoc_array_element(const Lex_ident_sys_st &varname, Item *key, + const sp_rcontext_addr &addr, sp_head *s) + :my_var_sp(varname, addr, + Type_handler_assoc_array::singleton(), s), + m_key(key) + { } + + bool check_assignability(THD *thd, const List &select_list, + bool *assign_as_row) const override + { + const Spvar_definition &table_of= get_element_definition(thd); + /* + Check select_list compatibility depending on whether the assoc element + is a ROW or a scalar data type. + */ + return (*assign_as_row= table_of.row_field_definitions() != nullptr) ? + table_of.row_field_definitions()->elements != select_list.elements : + select_list.elements != 1; + } + + bool set(THD *thd, Item *item) override + { + LEX_CSTRING key; + if (Type_handler_assoc_array::singleton()-> + key_to_lex_cstring(thd, &m_key, name, key)) + return true; + + return get_rcontext(thd->spcont)-> + set_variable_composite_by_name(thd, offset(), key, &item); + } + bool set_row(THD *thd, List &select_list) override + { + Item_row *item_row= new (thd->mem_root) Item_row(thd, select_list); + return set(thd, item_row); + } +}; + + +class my_var_sp_assoc_array_element_field: public my_var_sp_assoc_array_element +{ +protected: + const Lex_ident_sys_st m_field_name; + +public: + my_var_sp_assoc_array_element_field(const Lex_ident_sys_st &varname, + Item *key, + const Lex_ident_sys_st &field_name, + const sp_rcontext_addr &addr, sp_head *s) + :my_var_sp_assoc_array_element(varname, key, addr, s), + m_field_name(field_name) + { } + bool check_assignability(THD *thd, const List &select_list, + bool *assign_as_row) const override + { + const Spvar_definition &table_of= get_element_definition(thd); + + auto row_defs= table_of.row_field_definitions(); + if (unlikely(!row_defs)) + return true; + + uint offset= 0; + auto field_spv= row_defs->find_row_field_by_name(&m_field_name, &offset); + if (unlikely(!field_spv)) + return true; + + // TABLE OF does not support nested ROWs yet + DBUG_ASSERT(field_spv->row_field_definitions() == nullptr); + return select_list.elements != 1; + } + bool set(THD *thd, Item *item) override + { + LEX_CSTRING key; + if (Type_handler_assoc_array::singleton()-> + key_to_lex_cstring(thd, &m_key, name, key)) + return true; + + return get_rcontext(thd->spcont)-> + set_variable_composite_field_by_key(thd, offset(), + key, m_field_name, &item); + } + bool set_row(THD *thd, List &select_list) override + { + DBUG_ASSERT(0); // TABLE OF does not support nested ROWs yet + return true; + } +}; + + +class Type_collection_assoc_array: public Type_collection +{ +public: + bool init(Type_handler_data *data) override + { + return false; + } + const Type_handler *aggregate_for_result(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } + const Type_handler *aggregate_for_comparison(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } + const Type_handler *aggregate_for_min_max(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } + const Type_handler *aggregate_for_num_op(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } +}; + + +static Type_collection_assoc_array type_collection_assoc_array; + + +const Type_collection *Type_handler_assoc_array::type_collection() const +{ + return &type_collection_assoc_array; +} + + +bool Type_handler_assoc_array::Spvar_definition_with_complex_data_types( + Spvar_definition *def) const +{ + /* + No needs to check the "TABLE OF" and "INDEX BY" data types. + An assoc array variable in any cases use memory resources which need + to be freed when a routine execution leaves the DECLARE/BEGIN/END block + declaring this variable. + */ + return true; +} + + +bool Type_handler_assoc_array:: + sp_variable_declarations_finalize(THD *thd, LEX *lex, int nvars, + const Column_definition &def) + const +{ + const sp_type_def_composite2 *spaa= + static_cast + (def.get_attr_const_void_ptr(0)); + DBUG_ASSERT(spaa); + DBUG_ASSERT(spaa->m_def[0]); + DBUG_ASSERT(spaa->m_def[1]); + Spvar_definition *key_def= spaa->m_def[0]; + Spvar_definition *value_def= spaa->m_def[1]; + + // Set the default charset to be the database charset + if (!key_def->charset) + key_def->charset= thd->variables.collation_database; + + value_def= new (thd->mem_root) Spvar_definition(*value_def); + if (value_def->type_handler() == &type_handler_row) + { + if (const sp_type_def_record *sprec= + (sp_type_def_record *)value_def->get_attr_const_void_ptr(0)) + { + if (lex->sphead->row_fill_field_definitions(thd, sprec->field)) + return true; + + value_def->set_row_field_definitions(&type_handler_row, sprec->field); + } + } + + if (lex->sphead->fill_spvar_definition(thd, value_def)) + return true; + + Row_definition_list *aa_def= new (thd->mem_root) Row_definition_list(); + if (unlikely(aa_def == nullptr)) + return true; + + aa_def->push_back(key_def, thd->mem_root); + aa_def->push_back(value_def, thd->mem_root); + + for (uint i= 0 ; i < (uint) nvars ; i++) + { + uint offset= (uint) nvars - 1 - i; + sp_variable *spvar= lex->spcont->get_last_context_variable(offset); + spvar->field_def.set_row_field_definitions(this, aa_def); + if (lex->sphead->fill_spvar_definition(thd, &spvar->field_def, + &spvar->name)) + return true; + } + + return false; +} + + +Field *Type_handler_assoc_array:: + make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, + const LEX_CSTRING *name, + const Record_addr &rec, const Bit_addr &bit, + const Column_definition_attributes *attr, + uint32 flags) const +{ + DBUG_ASSERT(attr->length == 0); + DBUG_ASSERT(f_maybe_null(attr->pack_flag)); + return new (mem_root) Field_assoc_array(rec.ptr(), name); +} + + +String *Type_handler_assoc_array:: + print_item_value(THD *thd, Item *item, String *str) const +{ + DBUG_ASSERT(item->type_handler() == this); + + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer val(cs); + + /* + Only `IS NULL` or `IS NOT NULL` operations are supported on + an associative array. + */ + if (item->is_null()) + str->append(NULL_clex_str); + else + str->append_longlong(1); + + return str; +} + + +Item_splocal * +Type_handler_assoc_array::create_item_functor(THD *thd, + const Lex_ident_sys &varname, + const sp_rcontext_addr &addr, + List *args, + const Lex_ident_sys &member, + const Lex_ident_cli_st &name_cli) + const +{ + if (!args || args->elements != 1 || !args->head()) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "ASSOC_ARRAY_ELEMENT", + ErrConvDQName(thd->lex->sphead).ptr(), + 1, !args ? 0 : args->elements); + return NULL; + } + + Item *key= args->head(); + if (bool(key->with_flags & (item_with_t::WINDOW_FUNC | + item_with_t::FIELD | + item_with_t::SUM_FUNC | + item_with_t::SUBQUERY | + item_with_t::ROWNUM_FUNC))) + { + Item::Print tmp(key, QT_ORDINARY); + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), tmp.c_ptr()); + } + + Query_fragment pos(thd, thd->lex->sphead, name_cli.pos(), name_cli.end()); + if (!member.is_null()) + { + return new (thd->mem_root) + Item_splocal_assoc_array_element_field(thd, addr, varname, key, + member, &type_handler_null, + pos.pos(), pos.length()); + } + + return new (thd->mem_root) + Item_splocal_assoc_array_element(thd, addr, varname, key, + &type_handler_null, + pos.pos(), pos.length()); +} + + +/* + Make instructions for: + assoc_array('key') := expr; + assoc_array('key').member := expr; +*/ + +sp_instr * +Type_handler_assoc_array:: + create_instr_set_assign_functor(THD *thd, + LEX *lex, + const Qualified_ident &ident, + const sp_rcontext_addr &addr, + List *args, + const Lex_ident_sys_st &member, + Item *expr, + const LEX_CSTRING &expr_str) const +{ + using set_element= sp_instr_set_composite_field_by_name; + using set_element_member= sp_instr_set_composite_field_by_key; + + if (!ident.part(1).is_null()) + { + raise_bad_data_type_for_functor(ident); + return nullptr; + } + + if (!args || args->elements != 1 || !args->head()) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "ASSOC_ARRAY KEY", + ErrConvDQName(thd->lex->sphead).ptr(), + 1, args ? args->elements : 0); + return nullptr; + } + + DBUG_ASSERT(args->head()); + + if (member.is_null()) + return new (thd->mem_root) set_element(lex->sphead->instructions(), + lex->spcont, addr, args->head(), + expr, lex, true, expr_str); + + return new (thd->mem_root) set_element_member(lex->sphead->instructions(), + lex->spcont, addr, + args->head(), + member, + expr, lex, true, expr_str); +} + + +Item *Type_handler_assoc_array::create_item_method(THD *thd, + const Lex_ident_sys &a, + const Lex_ident_sys &b, + List *args, + const Lex_ident_cli_st + &query_fragment) const +{ + Item_method_base *item= nullptr; + if (b.length == 5) + { + if (Lex_ident_column(b).streq("COUNT"_Lex_ident_column)) + item= sp_get_assoc_array_count(thd, args); + else if (Lex_ident_column(b).streq("FIRST"_Lex_ident_column)) + item= sp_get_assoc_array_key(thd, args, true); + else if (Lex_ident_column(b).streq("PRIOR"_Lex_ident_column)) + item= sp_get_assoc_array_next_or_prior(thd, args, false); + } + else if (b.length == 4) + { + if (Lex_ident_column(b).streq("LAST"_Lex_ident_column)) + item= sp_get_assoc_array_key(thd, args, false); + else if (Lex_ident_column(b).streq("NEXT"_Lex_ident_column)) + item= sp_get_assoc_array_next_or_prior(thd, args, true); + } + else if (b.length == 6) + { + if (Lex_ident_column(b).streq("EXISTS"_Lex_ident_column)) + item= sp_get_assoc_array_exists(thd, args); + else if (Lex_ident_column(b).streq("DELETE"_Lex_ident_column)) + item= sp_get_assoc_array_delete(thd, args); + } + + if (!item) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), a.str, b.str); + return nullptr; + } + + if (item->init_method(a, query_fragment)) + return nullptr; + + return dynamic_cast(item); +} + + +bool Type_handler_assoc_array::key_to_lex_cstring(THD *thd, + Item **key, + const LEX_CSTRING& name, + LEX_CSTRING& out_key) const +{ + DBUG_ASSERT(key); + DBUG_ASSERT(*key); + + if ((*key)->fix_fields_if_needed(thd, key)) + return true; + + if ((*key)->null_value) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, + MYF(0), + name.str ? name.str : "unknown"); + return true; + } + + auto str= (*key)->val_str(); + if (!str) + return true; + + out_key= str->to_lex_cstring(); + return false; +} + + +Item_field *Type_handler_assoc_array::get_item(THD *thd, + const Item_field *item, + const LEX_CSTRING& name) const +{ + DBUG_ASSERT(item); + + auto item_assoc= dynamic_cast(item); + if (!item_assoc) + return nullptr; + + const Field_composite *field= item_assoc->get_composite_field(); + if (!field) + return nullptr; + + String key(name.str, name.length, &my_charset_bin); + auto elem= field->element_by_key(thd, &key); + if (!elem) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + key.c_ptr()); + return nullptr; + } + + return elem; +} + + +Item_field * +Type_handler_assoc_array::get_or_create_item(THD *thd, + Item_field *item, + const LEX_CSTRING& name) const +{ + DBUG_ASSERT(item); + + auto item_assoc= dynamic_cast(item); + if (!item_assoc) + return nullptr; + + Field_composite *field= item_assoc->get_composite_field(); + if (!field) + return nullptr; + + String key(name.str, name.length, &my_charset_bin); + return field->element_by_key(thd, &key); +} + + +void Type_handler_assoc_array::prepare_for_set(Item_field *item) const +{ + Item_field_packable *item_elem= dynamic_cast(item); + if (!item_elem) + return; + + item_elem->unpack(); +} + + +bool Type_handler_assoc_array::finalize_for_set(Item_field *item) const +{ + Item_field_packable *item_elem= dynamic_cast(item); + if (!item_elem) + return false; + + return item_elem->pack(); +} + + +/* + SELECT 1 INTO spvar(arg); + SELECT 1 INTO spvar(arg).field_name; +*/ +my_var *Type_handler_assoc_array:: + make_outvar_lvalue_functor(THD *thd, const Lex_ident_sys_st &name, + Item *arg, const Lex_ident_sys &field_name, + sp_head *sphead, + const sp_rcontext_addr &addr, + bool validate_only) const +{ + auto spvar= thd->lex->spcont->get_pvariable(addr); + DBUG_ASSERT(spvar); + auto def= spvar->field_def.row_field_definitions(); + DBUG_ASSERT(def); + DBUG_ASSERT(def->elements == 2); + + if (!field_name.str) // SELECT .. INTO spvar_assoc_array('key'); + { + if (validate_only) + return nullptr; // e.g. EXPLAIN SELECT .. INTO spvar_assoc_array('key'); + return new (thd->mem_root) my_var_sp_assoc_array_element(name, arg, addr, + sphead); + } + + // SELECT .. INTO spvar_assoc_array('key').field; + List_iterator it(*const_cast(def)); + it++; // Skip the INDEX BY definition + const Spvar_definition &table_of= *(it++); // The TABLE OF definition + + uint field_offset= 0; + if (!table_of.row_field_definitions()) + { + table_of.type_handler()-> + raise_bad_data_type_for_functor(Qualified_ident(name), field_name); + return nullptr; + } + + if (!table_of.row_field_definitions()->find_row_field_by_name(&field_name, + &field_offset)) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), field_name.str, name.str); + return nullptr; + } + + if (validate_only) + return nullptr; // e.g. EXPLAIN SELECT..INTO spvar_assoc_array('key').field; + return new (thd->mem_root) my_var_sp_assoc_array_element_field(name, arg, + field_name, + addr, + sphead); +} + + +// assoc_array_var:= assoc_array_type('key1'=>'val1', 'key2'=>'val2') +Item *Type_handler_assoc_array:: + make_typedef_constructor_item(THD *thd, const sp_type_def &def, + List *args) const +{ + if (unlikely(args == NULL)) + return new (thd->mem_root) Item_assoc_array(thd, def.get_name()); + + if (unlikely(sp_check_assoc_array_args(def, *args))) + return nullptr; + + return new (thd->mem_root) Item_assoc_array(thd, def.get_name(), *args); +} + + +/************************************************************************/ + +static Type_handler_assoc_array type_handler_assoc_array; + + +const Type_handler_assoc_array *Type_handler_assoc_array::singleton() +{ + return &type_handler_assoc_array; +} + + +static struct st_mariadb_data_type plugin_descriptor_type_assoc_array= +{ + MariaDB_DATA_TYPE_INTERFACE_VERSION, + &type_handler_assoc_array +}; + + +maria_declare_plugin(type_assoc_array) +{ + MariaDB_DATA_TYPE_PLUGIN, // the plugin type (see include/mysql/plugin.h) + &plugin_descriptor_type_assoc_array, // a pointer to the plugin descriptor + "associative_array", // plugin name + "Rakuten Securities", // plugin author + "Data type ASSOCIATIVE_ARRAY",// the plugin description + PLUGIN_LICENSE_GPL, // the plugin license (see include/mysql/plugin.h) + 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(see include/mysql/plugin.h)*/ +} +maria_declare_plugin_end; diff --git a/plugin/type_assoc_array/sql_type_assoc_array.h b/plugin/type_assoc_array/sql_type_assoc_array.h new file mode 100644 index 00000000000..34903527e67 --- /dev/null +++ b/plugin/type_assoc_array/sql_type_assoc_array.h @@ -0,0 +1,462 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#ifndef SQL_TYPE_ASSOC_ARRAY_INCLUDED +#define SQL_TYPE_ASSOC_ARRAY_INCLUDED + +#include "sql_type_composite.h" +#include "field_composite.h" +#include "item_composite.h" +#include "sp_type_def.h" + + +class Item_field_packable; + + +/* + Special handler associative arrays +*/ +class Type_handler_assoc_array: public Type_handler_composite +{ +public: + static const Type_handler_assoc_array *singleton(); + +public: + bool has_methods() const override { return true; } + bool has_functors() const override { return true; } + bool is_complex() const override + { + /* + Have an assoc array variable generate an sp_instr_destruct_variable + instruction in the end of the DECLARE/BEGIN/END block declaring + the variable. + */ + return true; + } + const Type_collection *type_collection() const override; + const Type_handler *type_handler_for_comparison() const override + { + return singleton(); + } + bool Spvar_definition_with_complex_data_types(Spvar_definition *def) + const override; + bool Column_definition_set_attributes(THD *thd, + Column_definition *def, + const Lex_field_type_st &attr, + column_definition_type_t type) + const override + { + /* + Disallow wrong use of associative_array: + CREATE TABLE t1 (a ASSOCIATIVE_ARRAY); + CREATE FUNCTION .. RETURN ASSOCIATEIVE ARRAY ..; + */ + if (!def->get_attr_const_void_ptr(0)) + { + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), name().ptr()); + return true; + } + return Type_handler_composite::Column_definition_set_attributes(thd, def, + attr, + type); + } + + bool sp_variable_declarations_finalize(THD *thd, + LEX *lex, int nvars, + const Column_definition &def) + const override; + + Field *make_table_field_from_def(TABLE_SHARE *share, + MEM_ROOT *mem_root, + const LEX_CSTRING *name, + const Record_addr &addr, + const Bit_addr &bit, + const Column_definition_attributes *attr, + uint32 flags) const override; + Item *make_const_item_for_comparison(THD *, Item *src, const Item *cmp) const + override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + +protected: + static bool lex_ident_col_eq(Lex_ident_column *a, Lex_ident_column *b) + { + return a->streq(*b); + } + + bool sp_check_assoc_array_args(const sp_type_def &def, List &args) const + { + List names; + + List_iterator it(args); + for (Item *item= it++; item; item= it++) + { + /* + Make sure all value have keys: + assoc_array_type('key1'=>'val1', 'key2'=>'val2') -- correct + assoc_array_type('val1' , 'val2' ) -- wrong + */ + if (unlikely(!item->is_explicit_name())) + { + my_error(ER_NEED_NAMED_ASSOCIATION, MYF(0), def.get_name()); + return true; + } + + /* + Make sure keys are unique in: + assoc_array_type('key1'=>'val1', 'key2'=>'val2') + */ + if (unlikely(names.add_unique(&item->name, lex_ident_col_eq))) + { + my_error(ER_DUP_UNKNOWN_IN_INDEX, MYF(0), item->name.str); + return true; + } + } + + return false; + } + +public: + /* + SELECT 1 INTO spvar(arg); + SELECT 1 INTO spvar(arg).field_name; + */ + my_var *make_outvar_lvalue_functor(THD *thd, const Lex_ident_sys_st &name, + Item *arg, + const Lex_ident_sys &opt_field, + sp_head *sphead, + const sp_rcontext_addr &addr, + bool validate_only) const override; + // assoc_array_var:= assoc_array_type('key1'=>'val1', 'key2'=>'val2') + Item *make_typedef_constructor_item(THD *thd, const sp_type_def &def, + List *args) const override; + + Item_cache *Item_get_cache(THD *thd, const Item *item) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + bool set_comparator_func(THD *thd, Arg_comparator *cmp) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + cmp_item *make_cmp_item(THD *thd, CHARSET_INFO *cs) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + in_vector *make_in_vector(THD *thd, const Item_func_in *f, uint nargs) const + override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + bool Item_func_in_fix_comparator_compatible_types(THD *thd, + Item_func_in *) const + override + { + MY_ASSERT_UNREACHABLE(); + return false; + } + + String *print_item_value(THD *thd, Item *item, String *str) const override; + + virtual Item_splocal *create_item_functor(THD *thd, + const Lex_ident_sys &varname, + const sp_rcontext_addr &addr, + List *args, + const Lex_ident_sys &member, + const Lex_ident_cli_st &name_cli) + const override; + sp_instr *create_instr_set_assign_functor(THD *thd, LEX *lex, + const Qualified_ident &ident, + const sp_rcontext_addr &addr, + List *params, + const Lex_ident_sys_st &field_name, + Item *item, + const LEX_CSTRING &expr_str) + const override; + virtual + Item *create_item_method(THD *thd, + const Lex_ident_sys &ca, + const Lex_ident_sys &cb, + List *args, + const Lex_ident_cli_st &query_fragment) + const override; + + bool key_to_lex_cstring(THD *thd, + Item **key, + const LEX_CSTRING& name, + LEX_CSTRING& out_key) const override; + + bool get_item_index(THD *thd, + const Item_field *item, + const LEX_CSTRING& name, + uint& idx) const override + { + DBUG_ASSERT(0); + return true; + } + Item_field *get_item(THD *thd, + const Item_field *item, + const LEX_CSTRING& name) const override; + Item_field *get_or_create_item(THD *thd, + Item_field *item, + const LEX_CSTRING& name) const override; + + void prepare_for_set(Item_field *item) const override; + bool finalize_for_set(Item_field *item) const override; +}; + + +class Field_assoc_array final :public Field_composite +{ +protected: + MEM_ROOT m_mem_root; + TREE m_tree; + + TABLE *m_table; + TABLE_SHARE *m_table_share; + Row_definition_list *m_def; + + Field *m_key_field; + Field *m_element_field; + Item_field_packable *m_item_pack; + Item *m_item; + + /* + Modified copy of the key definition + */ + Spvar_definition m_key_def; +public: + Field_assoc_array(uchar *ptr_arg, + const LEX_CSTRING *field_name_arg); + ~Field_assoc_array(); + + void set_array_def(Row_definition_list *def) + { + DBUG_ASSERT(def); + DBUG_ASSERT(def->elements == 2); + m_def= def; + } + + const Row_definition_list *get_array_def() const + { + return m_def; + } + + Item_field *make_item_field_spvar(THD *thd, + const Spvar_definition &def) override; + + bool sp_prepare_and_store_item(THD *thd, Item **value) override; + + uint rows() const override; + bool get_key(String *key, bool is_first) override; + bool get_next_key(const String *curr_key, String *next_key) override; + bool get_prior_key(const String *curr_key, String *prior_key) override; + Item_field *element_by_key(THD *thd, String *key) override; + Item_field *element_by_key(THD *thd, String *key) const override; + Item **element_addr_by_key(THD *thd, String *key) override; + bool delete_all_elements() override; + bool delete_element_by_key(String *key) override; + void expr_event_handler(THD *thd, expr_event_t event) override + { + if ((bool) (event & expr_event_t::DESTRUCT_ANY)) + { + delete_all_elements(); + set_null(); + return; + } + DBUG_ASSERT(0); + } + + Item *get_element_item() const override { return m_item; } + Field *get_key_field() const { return m_key_field; } + +protected: + bool copy_and_convert_key(const String *key, String &key_copy) const; + bool unpack_key(const Binary_string &key, String *key_dst) const; + bool create_fields(THD *thd); + + /* + Initialize the element base Field and Item_field for the + associative array. + */ + bool init_element_base(THD *thd); + + bool create_element_buffer(THD *thd, Binary_string *buffer); + bool insert_element(String &&key, Binary_string &&element); + bool get_next_or_prior_key(const String *curr_key, + String *new_key, + bool is_next); +}; + + +/** + Item_field for the associative array data type +*/ +class Item_field_assoc_array: public Item_field, public Item_composite_base +{ +public: + Item_field_assoc_array(THD *thd, Field *field) + :Item_field(thd, field) + { } + + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + + const Type_handler *type_handler() const override + { return Type_handler_assoc_array::singleton(); } + + bool set_array_def(THD *thd, Row_definition_list *def); + + uint rows() const override + { + return get_composite_field()->rows(); + } + bool get_key(String *key, bool is_first) override + { + return get_composite_field()->get_key(key, is_first); + } + bool get_next_key(const String *curr_key, String *next_key) override + { + return get_composite_field()->get_next_key(curr_key, next_key); + } + Item *element_by_key(THD *thd, String *key) override + { + return ((const Field_composite *)get_composite_field())-> + element_by_key(thd, key); + } + Item **element_addr_by_key(THD *thd, Item **ref, String *key) override + { + return get_composite_field()->element_addr_by_key(thd, key); + } + Field_composite *get_composite_field() const override + { + return dynamic_cast(field); + } +}; + + +class Item_assoc_array: public Item_composite +{ +protected: + Lex_ident_column m_name; + +public: + Item_assoc_array(THD *thd, const Lex_ident_column &name) + :Item_composite(thd), + m_name(name) + { } + + Item_assoc_array(THD *thd, const Lex_ident_column &name, List &list) + :Item_composite(thd, list), + m_name(name) + { } + + const Type_handler *type_handler() const override + { + return Type_handler_assoc_array::singleton(); + } + + Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src, + const Tmp_field_param *param) override + { + return NULL; + } + + uint rows() const override; + bool get_key(String *key, bool is_first) override; + bool get_next_key(const String *curr_key, String *next_key) override; + Item *element_by_key(THD *thd, String *key) override; + Item **element_addr_by_key(THD *thd, Item **addr_arg, String *key) override; + + bool fix_fields(THD *thd, Item **ref) override; + void bring_value() override; + void print(String *str, enum_query_type query_type) override; + + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override; +}; + + +class Item_splocal_assoc_array_base :public Item_composite_base +{ +protected: + Item *m_key; + +public: + Item_splocal_assoc_array_base(Item *key); +}; + + +class Item_splocal_assoc_array_element :public Item_splocal, + public Item_splocal_assoc_array_base +{ +protected: + bool set_value(THD *thd, sp_rcontext *ctx, Item **it) override; +public: + Item_splocal_assoc_array_element(THD *thd, + const sp_rcontext_addr &addr, + const Lex_ident_sys &sp_var_name, + Item *key, const Type_handler *handler, + uint pos_in_q= 0, uint len_in_q= 0); + bool fix_fields(THD *thd, Item **) override; + Item *this_item() override; + const Item *this_item() const override; + Item **this_item_addr(THD *thd, Item **) override; + bool append_for_log(THD *thd, String *str) override; + void print(String *str, enum_query_type query_type) override; + + Item *do_get_copy(THD *) const override { return nullptr; } + Item *do_build_clone(THD *thd) const override { return nullptr; } + + Item_composite_base *get_composite_variable(sp_rcontext *ctx) const; +}; + + +class Item_splocal_assoc_array_element_field : + public Item_splocal_row_field_by_name, + public Item_splocal_assoc_array_base +{ +protected: + Item_field *m_element_item; +public: + Item_splocal_assoc_array_element_field(THD *thd, + const sp_rcontext_addr &addr, + const Lex_ident_sys &sp_var_name, + Item *key, + const Lex_ident_sys &sp_field_name, + const Type_handler *handler, + uint pos_in_q= 0, uint len_in_q= 0); + bool fix_fields(THD *thd, Item **) override; + Item *this_item() override; + const Item *this_item() const override; + Item **this_item_addr(THD *thd, Item **) override; + bool append_for_log(THD *thd, String *str) override; + void print(String *str, enum_query_type query_type) override; + + Item_composite_base *get_composite_variable(sp_rcontext *ctx) const; +}; + + +#endif /* SQL_TYPE_ASSOC_ARRAY_INCLUDED */ diff --git a/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.result b/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.result new file mode 100644 index 00000000000..e22da37bbff --- /dev/null +++ b/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.result @@ -0,0 +1,35 @@ +# +# MDEV-34319 DECLARE TYPE type_name IS RECORD (..) with scalar members in stored routines +# +# +# Demonstrate UDT field type for associative array element +# +SET sql_mode=ORACLE; +DECLARE +TYPE uuids_t IS TABLE OF UUID INDEX BY INTEGER; +uuids uuids_t; +BEGIN +uuids(1):= 'e7a69166-a557-4bbe-ab4d-d390114b51fa'; +SELECT uuids(1); +END; +$$ +uuids(1) +e7a69166-a557-4bbe-ab4d-d390114b51fa +# +# Demonstrate UDT field type for associative array RECORD field +# +SET sql_mode=ORACLE; +DECLARE +TYPE rec_t IS RECORD ( +a INT, +b UUID +); +TYPE uuids_t IS TABLE OF rec_t INDEX BY INTEGER; +uuids uuids_t; +BEGIN +uuids(1):= rec_t(1, 'e7a69166-a557-4bbe-ab4d-d390114b51fa'); +SELECT uuids(1).a,uuids(1).b; +END; +$$ +uuids(1).a uuids(1).b +1 e7a69166-a557-4bbe-ab4d-d390114b51fa diff --git a/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.test b/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.test new file mode 100644 index 00000000000..15d3c877fa1 --- /dev/null +++ b/plugin/type_uuid/mysql-test/type_uuid/type_uuid_sp_assoc_array.test @@ -0,0 +1,38 @@ +--echo # +--echo # MDEV-34319 DECLARE TYPE type_name IS RECORD (..) with scalar members in stored routines +--echo # + + +--echo # +--echo # Demonstrate UDT field type for associative array element +--echo # +SET sql_mode=ORACLE; +DELIMITER $$; +DECLARE + TYPE uuids_t IS TABLE OF UUID INDEX BY INTEGER; + uuids uuids_t; +BEGIN + uuids(1):= 'e7a69166-a557-4bbe-ab4d-d390114b51fa'; + SELECT uuids(1); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Demonstrate UDT field type for associative array RECORD field +--echo # +SET sql_mode=ORACLE; +DELIMITER $$; +DECLARE + TYPE rec_t IS RECORD ( + a INT, + b UUID + ); + TYPE uuids_t IS TABLE OF rec_t INDEX BY INTEGER; + uuids uuids_t; +BEGIN + uuids(1):= rec_t(1, 'e7a69166-a557-4bbe-ab4d-d390114b51fa'); + SELECT uuids(1).a,uuids(1).b; +END; +$$ +DELIMITER ;$$ diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 86906fcfe75..741bc7074ab 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -196,6 +196,8 @@ SET (SQL_SOURCE opt_hints_parser.cc opt_hints_parser.h scan_char.h opt_hints.cc opt_hints.h sql_type_row.cc + sql_type_composite.cc sql_type_composite.h + item_composite.cc item_composite.h ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h ${GEN_SOURCES} diff --git a/sql/field.h b/sql/field.h index ea90b5fa37c..f1c6b6c23b7 100644 --- a/sql/field.h +++ b/sql/field.h @@ -44,6 +44,7 @@ class Item_bool_func; class Item_equal; class Virtual_tmp_table; class Qualified_column_ident; +class Qualified_ident; class Table_ident; class SEL_ARG; class RANGE_OPT_PARAM; @@ -5722,6 +5723,7 @@ public: - variables with explicit data types: DECLARE a INT; - variables with data type references: DECLARE a t1.a%TYPE; - ROW type variables + - Associative arrays Notes: - Scalar variables have m_field_definitions==NULL. @@ -5757,6 +5759,14 @@ public: m_cursor_rowtype_offset(0), m_row_field_definitions(NULL) { } + Spvar_definition(const Column_definition &col_def) + : Column_definition(col_def), + m_column_type_ref(NULL), + m_table_rowtype_ref(NULL), + m_cursor_rowtype_ref(false), + m_cursor_rowtype_offset(0), + m_row_field_definitions(NULL) + { } const Type_handler *type_handler() const { return Type_handler_hybrid_field_type::type_handler(); @@ -5812,7 +5822,8 @@ public: } uint is_row() const { - return m_row_field_definitions != NULL; + return m_row_field_definitions != NULL && + type_handler() == &type_handler_row; } // Check if "this" defines a ROW variable with n elements uint is_row(uint n) const @@ -5824,10 +5835,11 @@ public: { return m_row_field_definitions; } - void set_row_field_definitions(Row_definition_list *list) + void set_row_field_definitions(const Type_handler *th, + Row_definition_list *list) { DBUG_ASSERT(list); - set_handler(&type_handler_row); + set_handler(th); m_row_field_definitions= list; } diff --git a/sql/field_composite.h b/sql/field_composite.h new file mode 100644 index 00000000000..d906fac3a86 --- /dev/null +++ b/sql/field_composite.h @@ -0,0 +1,73 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#ifndef FIELD_COMPOSITE_INCLUDED +#define FIELD_COMPOSITE_INCLUDED + +#include "field.h" + +class Field_composite: public Field_null +{ +public: + Field_composite(uchar *ptr_arg, const LEX_CSTRING *field_name_arg) + :Field_null(ptr_arg, 0, Field::NONE, field_name_arg, &my_charset_bin) + {} + en_fieldtype tmp_engine_column_type(bool use_packed_rows) const override + { + DBUG_ASSERT(0); + return Field::tmp_engine_column_type(use_packed_rows); + } + enum_conv_type rpl_conv_type_from(const Conv_source &source, + const Relay_log_info *rli, + const Conv_param ¶m) const override + { + DBUG_ASSERT(0); + return CONV_TYPE_IMPOSSIBLE; + } + + virtual uint rows() const { return 0; } + virtual bool get_key(String *key, bool is_first) { return true; } + virtual bool get_next_key(const String *curr_key, String *next_key) + { + return true; + } + virtual bool get_prior_key(const String *curr_key, String *prior_key) + { + return true; + } + virtual Item_field *element_by_key(THD *thd, String *key) { return NULL; } + virtual Item_field *element_by_key(THD *thd, String *key) const + { + return NULL; + } + virtual Item **element_addr_by_key(THD *thd, String *key) { return NULL; } + virtual bool delete_all_elements() { return true; } + virtual bool delete_element_by_key(String *key) { return true; } + + /* + Retrieve the Item representing the element of the composite field. + Note that the item here may not represent a particular element but + it does contain the information about the element type. + + Use element_by_key() to retrieve the item representing a particular + element of the composite field. + */ + virtual Item *get_element_item() const { return NULL; } +}; + +#endif /* FIELD_COMPOSITE_INCLUDED */ diff --git a/sql/item_composite.cc b/sql/item_composite.cc new file mode 100644 index 00000000000..177be226f37 --- /dev/null +++ b/sql/item_composite.cc @@ -0,0 +1,31 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#include "my_global.h" +#include "item.h" +#include "item_composite.h" + +void Item_composite::illegal_method_call(const char *method) +{ + DBUG_ENTER("Item_composite::illegal_method_call"); + DBUG_PRINT("error", ("!!! %s method was called for %s", + method, type_handler()->name().ptr())); + DBUG_ASSERT(0); + my_error(ER_OPERAND_COLUMNS, MYF(0), 1); + DBUG_VOID_RETURN; +} diff --git a/sql/item_composite.h b/sql/item_composite.h new file mode 100644 index 00000000000..bdb6152e0ca --- /dev/null +++ b/sql/item_composite.h @@ -0,0 +1,103 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#ifndef ITEM_COMPOSITE_INCLUDED +#define ITEM_COMPOSITE_INCLUDED + +#include "item.h" + +class Field_composite; + +class Item_composite_base +{ +public: + virtual ~Item_composite_base() = default; + + // For associative arrays + /// Returns the number of columns for the elements of the array + virtual uint rows() const { return 1; } + virtual bool get_key(String *key, bool is_first) { return true; } + virtual bool get_next_key(const String *curr_key, String *next_key) + { + return true; + } + virtual Item *element_by_key(THD *thd, String *key) { return nullptr; } + virtual Item **element_addr_by_key(THD *thd, Item **addr_arg, String *key) + { + return addr_arg; + } + + /* + Get the composite field for the item, when applicable. + */ + virtual Field_composite *get_composite_field() const { return nullptr; } +}; + + +class Item_composite: public Item_fixed_hybrid, + protected Item_args, + public Item_composite_base +{ +public: + Item_composite(THD *thd, List &list) + :Item_fixed_hybrid(thd), Item_args(thd, list) + { } + Item_composite(THD *thd, Item_args *other) + :Item_fixed_hybrid(thd), Item_args(thd, other) + { } + Item_composite(THD *thd) + :Item_fixed_hybrid(thd) + { } + + enum Type type() const override { return ROW_ITEM; } + + void illegal_method_call(const char *); + + void make_send_field(THD *thd, Send_field *) override + { + illegal_method_call((const char*)"make_send_field"); + }; + double val_real() override + { + illegal_method_call((const char*)"val"); + return 0; + }; + longlong val_int() override + { + illegal_method_call((const char*)"val_int"); + return 0; + }; + String *val_str(String *) override + { + illegal_method_call((const char*)"val_str"); + return 0; + }; + my_decimal *val_decimal(my_decimal *) override + { + illegal_method_call((const char*)"val_decimal"); + return 0; + }; + bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override + { + illegal_method_call((const char*)"get_date"); + return true; + } +}; + + +#endif /* ITEM_COMPOSITE_INCLUDED */ diff --git a/sql/item_func.cc b/sql/item_func.cc index 47ab96dd7a1..2c6372cbbf0 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4812,7 +4812,7 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref) break; case ROW_RESULT: DBUG_ASSERT(0); - set_handler(&type_handler_row); + set_handler(args[0]->type_handler()); break; } if (thd->lex->current_select) diff --git a/sql/item_func.h b/sql/item_func.h index 57ac861242c..c9bbcb543eb 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -826,6 +826,8 @@ public: protected: const Handler *m_func_handler; public: + Item_handled_func(THD *thd) + :Item_func(thd), m_func_handler(NULL) { } Item_handled_func(THD *thd, Item *a) :Item_func(thd, a), m_func_handler(NULL) { } Item_handled_func(THD *thd, Item *a, Item *b) diff --git a/sql/item_row.cc b/sql/item_row.cc index d877bf605b9..abba9db8a96 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -25,15 +25,6 @@ #include "sql_class.h" // THD, set_var.h: THD #include "set_var.h" -void Item_row::illegal_method_call(const char *method) -{ - DBUG_ENTER("Item_row::illegal_method_call"); - DBUG_PRINT("error", ("!!! %s method was called for row item", method)); - DBUG_ASSERT(0); - my_error(ER_OPERAND_COLUMNS, MYF(0), 1); - DBUG_VOID_RETURN; -} - bool Item_row::fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed() == 0); diff --git a/sql/item_row.h b/sql/item_row.h index 012bc0f29f0..5c66c73d2ab 100644 --- a/sql/item_row.h +++ b/sql/item_row.h @@ -1,6 +1,8 @@ #ifndef ITEM_ROW_INCLUDED #define ITEM_ROW_INCLUDED +#include "item_composite.h" + /* Copyright (c) 2002, 2013, Oracle and/or its affiliates. @@ -33,8 +35,7 @@ Item which stores (x,y,...) and ROW(x,y,...). Note that this can be recursive: ((x,y),(z,t)) is a ROW of ROWs. */ -class Item_row: public Item_fixed_hybrid, - private Item_args, +class Item_row: public Item_composite, private Used_tables_and_const_cache { table_map not_null_tables_cache; @@ -45,53 +46,22 @@ class Item_row: public Item_fixed_hybrid, bool with_null; public: Item_row(THD *thd, List &list) - :Item_fixed_hybrid(thd), Item_args(thd, list), + :Item_composite(thd, list), not_null_tables_cache(0), with_null(0) { } Item_row(THD *thd, Item_row *row) - :Item_fixed_hybrid(thd), Item_args(thd, static_cast(row)), + :Item_composite(thd, static_cast(row)), Used_tables_and_const_cache(), not_null_tables_cache(0), with_null(0) { } - enum Type type() const override { return ROW_ITEM; }; const Type_handler *type_handler() const override { return &type_handler_row; } Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src, const Tmp_field_param *param) override { return NULL; // Check with Vicentiu why it's called for Item_row } - void illegal_method_call(const char *); bool is_null() override { return null_value; } - void make_send_field(THD *thd, Send_field *) override - { - illegal_method_call((const char*)"make_send_field"); - }; - double val_real() override - { - illegal_method_call((const char*)"val"); - return 0; - }; - longlong val_int() override - { - illegal_method_call((const char*)"val_int"); - return 0; - }; - String *val_str(String *) override - { - illegal_method_call((const char*)"val_str"); - return 0; - }; - my_decimal *val_decimal(my_decimal *) override - { - illegal_method_call((const char*)"val_decimal"); - return 0; - }; - bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override - { - illegal_method_call((const char*)"get_date"); - return true; - } bool fix_fields(THD *thd, Item **ref) override; void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge) override; diff --git a/sql/lex.h b/sql/lex.h index f58a8afd649..41a34ef7389 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -55,6 +55,7 @@ SYMBOL symbols[] = { { "<<", SYM(SHIFT_LEFT)}, { ">>", SYM(SHIFT_RIGHT)}, { "<=>", SYM(EQUAL_SYM)}, + { "=>", SYM(ARROW_SYM)}, { "ACCOUNT", SYM(ACCOUNT_SYM)}, { "ACTION", SYM(ACTION)}, { "ADD", SYM(ADD)}, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index ee13a1c501a..1cdde6161a6 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12328,3 +12328,9 @@ ER_WARN_MALFORMED_HINT eng "Hint %s is ignored as malformed" ER_WARN_HINTS_ON_INSERT_PART_OF_INSERT_SELECT eng "Optimizer hints at the INSERT part of a INSERT..SELECT statement are not supported" +ER_ASSOC_ARRAY_ELEM_NOT_FOUND + eng "Element not found with key '%s'" +ER_NULL_FOR_ASSOC_ARRAY_INDEX + eng "NULL key used for associative array '%s'" +ER_NEED_NAMED_ASSOCIATION + eng "Initializing %s requires named association" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 2a78f1cb002..0704580dc1b 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3856,14 +3856,14 @@ sp_head::set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, if (!(val= adjust_assignment_source(thd, val, NULL))) return true; - sp_instr_set_row_field_by_name *sp_set= - new (thd->mem_root) sp_instr_set_row_field_by_name(instructions(), - spcont, rh, - spv->offset, - *field_name, - val, - lex, true, - value_query); + sp_instr_set_composite_field_by_name *sp_set= + new (thd->mem_root) sp_instr_set_composite_field_by_name(instructions(), + spcont, rh, + spv->offset, + *field_name, + val, + lex, true, + value_query); return sp_set == NULL || add_instr(sp_set); } @@ -3982,7 +3982,7 @@ bool sp_head::spvar_fill_row(THD *thd, sp_variable *spvar, Row_definition_list *defs) { - spvar->field_def.set_row_field_definitions(defs); + spvar->field_def.set_row_field_definitions(&type_handler_row, defs); spvar->field_def.field_name= spvar->name; if (fill_spvar_definition(thd, &spvar->field_def)) return true; @@ -4042,7 +4042,7 @@ bool sp_head::spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, if (!(ref= new (thd->mem_root) Qualified_column_ident(thd, &db, &table, &column))) return true; - + def->set_column_type_ref(ref); m_flags|= sp_head::HAS_COLUMN_TYPE_REFS; @@ -4050,6 +4050,35 @@ bool sp_head::spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, } +bool sp_head::spvar_def_fill_rowtype_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &table) +{ + Table_ident *ref; + if (!(ref= new (thd->mem_root) Table_ident(&table))) + return true; + + def->set_table_rowtype_ref(ref); + m_flags|= sp_head::HAS_COLUMN_TYPE_REFS; + + return false; +} + + +bool sp_head::spvar_def_fill_rowtype_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &db, + const LEX_CSTRING &table) +{ + Table_ident *ref; + if (!(ref= new (thd->mem_root) Table_ident(thd, &db, &table, false))) + return true; + + def->set_table_rowtype_ref(ref); + m_flags|= sp_head::HAS_COLUMN_TYPE_REFS; + + return false; +} + + bool sp_head::spvar_fill_table_rowtype_reference(THD *thd, sp_variable *spvar, const LEX_CSTRING &table) diff --git a/sql/sp_head.h b/sql/sp_head.h index 330fd4f73ac..61df5876525 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -825,6 +825,11 @@ public: const LEX_CSTRING &db, const LEX_CSTRING &table, const LEX_CSTRING &column); + bool spvar_def_fill_rowtype_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &table); + bool spvar_def_fill_rowtype_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &db, + const LEX_CSTRING &table); void set_c_chistics(const st_sp_chistics &chistics); void set_info(longlong created, longlong modified, diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 55dd8bda5ba..f3b97f261a9 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -1313,13 +1313,23 @@ sp_instr_set_row_field::print(String *str) /* - sp_instr_set_field_by_name class functions + sp_instr_set_composite_field_by_name class functions */ int -sp_instr_set_row_field_by_name::exec_core(THD *thd, uint *nextp) +sp_instr_set_composite_field_by_name::exec_core(THD *thd, uint *nextp) { - int res= get_rcontext(thd)->set_variable_row_field_by_name(thd, m_offset, + if (m_key) + { + auto var= get_rcontext(thd)->get_variable(m_offset); + auto handler= var->type_handler()->to_composite(); + DBUG_ASSERT(handler); + + if (handler->key_to_lex_cstring(thd, &m_key, var->name, m_field_name)) + return true; + } + + int res= get_rcontext(thd)->set_variable_composite_by_name(thd, m_offset, m_field_name, &m_value); *nextp= m_ip + 1; @@ -1328,30 +1338,93 @@ sp_instr_set_row_field_by_name::exec_core(THD *thd, uint *nextp) void -sp_instr_set_row_field_by_name::print(String *str) +sp_instr_set_composite_field_by_name::print(String *str) { /* set name.field@offset["field"] ... */ - size_t rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3 + 2; + /* set name.field["key"] ... */ sp_variable *var= m_ctx->find_variable(m_offset); const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); DBUG_ASSERT(var); - DBUG_ASSERT(var->field_def.is_table_rowtype_ref() || - var->field_def.is_cursor_rowtype_ref()); + DBUG_ASSERT(dynamic_cast(var->type_handler())); - rsrv+= var->name.length + 2 * m_field_name.length + prefix->length; - if (str->reserve(rsrv)) - return; - str->qs_append(STRING_WITH_LEN("set ")); - str->qs_append(prefix); - str->qs_append(&var->name); - str->qs_append('.'); - str->qs_append(&m_field_name); - str->qs_append('@'); - str->qs_append(m_offset); - str->qs_append("[\"",2); - str->qs_append(&m_field_name); - str->qs_append("\"]",2); - str->qs_append(' '); + str->append(STRING_WITH_LEN("set ")); + str->append(prefix); + str->append(&var->name); + + if (!m_key) + { + str->append('.'); + str->append(&m_field_name); + } + + str->append('@'); + str->append_ulonglong(m_offset); + + if (!m_key) + { + str->append(STRING_WITH_LEN("[\"")); + str->append(&m_field_name); + str->append(STRING_WITH_LEN("\"]")); + } + else + { + str->append('['); + m_key->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); + str->append(']'); + } + + str->append(' '); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); +} + + +/* + sp_instr_set_composite_field_by_key class functions +*/ + +int +sp_instr_set_composite_field_by_key::exec_core(THD *thd, uint *nextp) +{ + auto var= get_rcontext(thd)->get_variable(m_offset); + auto handler= var->type_handler()->to_composite(); + DBUG_ASSERT(handler); + + LEX_CSTRING key; + if (handler->key_to_lex_cstring(thd, &m_key, var->name, key)) + return true; + + int res= get_rcontext(thd)->set_variable_composite_field_by_key(thd, + m_offset, + key, + m_field_name, + &m_value); + *nextp= m_ip + 1; + return res; +} + + +void +sp_instr_set_composite_field_by_key::print(String *str) +{ + sp_variable *var= m_ctx->find_variable(m_offset); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); + DBUG_ASSERT(var); + DBUG_ASSERT(dynamic_cast(var->type_handler())); + + str->append(STRING_WITH_LEN("set ")); + str->append(prefix); + str->append(&var->name); + str->append('@'); + str->append_ulonglong(m_offset); + str->append('['); + m_key->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); + str->append(']'); + str->append('.'); + str->append(&m_field_name); + str->append(' '); m_value->print(str, enum_query_type(QT_ORDINARY | QT_ITEM_ORIGINAL_FUNC_NULLIF)); } diff --git a/sql/sp_instr.h b/sql/sp_instr.h index 00642d7c3be..8cb96e44726 100644 --- a/sql/sp_instr.h +++ b/sql/sp_instr.h @@ -749,35 +749,100 @@ public: structure of "rec". It gets resolved at run time, during the corresponding sp_instr_cursor_copy_struct::exec_core(). - So sp_instr_set_row_field_by_name searches for ROW fields by name, + So sp_instr_set_composite_field_by_name searches for ROW fields by name, while sp_instr_set_row_field (see above) searches for ROW fields by index. -*/ -class sp_instr_set_row_field_by_name : public sp_instr_set + Additionally, this class is used for assignments of associative arrays + by key: + DECLARE + TYPE t IS TABLE OF rec_t INDEX BY VARCHAR2(20); + arr t; + BEGIN + arr('key'):= rec_t(10, 20); -- This instruction + END; +*/ +class sp_instr_set_composite_field_by_name : public sp_instr_set { - // Prevent use of this - sp_instr_set_row_field_by_name(const sp_instr_set_row_field &); - void operator=(sp_instr_set_row_field_by_name &); - const LEX_CSTRING m_field_name; + using SELF= sp_instr_set_composite_field_by_name; + sp_instr_set_composite_field_by_name(const SELF &); + void operator=(SELF &); + LEX_CSTRING m_field_name; + Item* m_key; public: - - sp_instr_set_row_field_by_name(uint ip, sp_pcontext *ctx, - const Sp_rcontext_handler *rh, - uint offset, const LEX_CSTRING &field_name, - Item *val, - LEX *lex, bool lex_resp, - const LEX_CSTRING &value_query) + sp_instr_set_composite_field_by_name(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, + uint offset, + const LEX_CSTRING &field_name, + Item *val, + LEX *lex, bool lex_resp, + const LEX_CSTRING &value_query) : sp_instr_set(ip, ctx, rh, offset, val, lex, lex_resp, value_query), - m_field_name(field_name) + m_field_name(field_name), + m_key(nullptr) + {} + sp_instr_set_composite_field_by_name(uint ip, sp_pcontext *ctx, + const sp_rcontext_addr &addr, + Item* key, Item *val, + LEX *lex, bool lex_resp, + const LEX_CSTRING &value_query) + : sp_instr_set(ip, ctx, + addr.rcontext_handler(), + addr.offset(), val, lex, + lex_resp, value_query), + m_key(key) {} - virtual ~sp_instr_set_row_field_by_name() = default; + virtual ~sp_instr_set_composite_field_by_name() = default; int exec_core(THD *thd, uint *nextp) override; void print(String *str) override; -}; // class sp_instr_set_field_by_name : public sp_instr_set +}; // class sp_instr_set_composite_field_by_name : public sp_instr_set + + +/* + This class handles assignments of non scalar associative array's element + assignments. + + DECLARE + TYPE t IS TABLE OF rec_t INDEX BY VARCHAR2(20); + arr t; + BEGIN + arr('key'):= rec_t(10, 20); + arr('key').field:= 30; -- This instruction + END; +*/ +class sp_instr_set_composite_field_by_key : public sp_instr_set +{ + using SELF= sp_instr_set_composite_field_by_key; + sp_instr_set_composite_field_by_key(const SELF &); + void operator=(SELF &); + Item* m_key; + const LEX_CSTRING m_field_name; + +public: + sp_instr_set_composite_field_by_key(uint ip, sp_pcontext *ctx, + const sp_rcontext_addr &addr, + Item* key, + const LEX_CSTRING &field_name, + Item *val, + LEX *lex, bool lex_resp, + const LEX_CSTRING &value_query) + : sp_instr_set(ip, ctx, + addr.rcontext_handler(), + addr.offset(), val, lex, + lex_resp, value_query), + m_key(key), + m_field_name(field_name) + {} + + virtual ~sp_instr_set_composite_field_by_key() = default; + + int exec_core(THD *thd, uint *nextp) override; + + void print(String *str) override; +}; // class sp_instr_set_composite_field_by_key : public sp_instr_set /** diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc index f0f41f2cb4b..139d871c247 100644 --- a/sql/sp_pcontext.cc +++ b/sql/sp_pcontext.cc @@ -458,6 +458,21 @@ sp_type_def *sp_pcontext::find_type_def(const LEX_CSTRING &name, } +bool sp_type_def_list::type_defs_add_composite2(THD *thd, + const Lex_ident_column &name, + const Type_handler *th, + Spvar_definition *key, + Spvar_definition *value) +{ + auto p= new (thd->mem_root) sp_type_def_composite2(name, th, key, value); + + if (p == nullptr) + return true; + + return m_type_defs.append(p); +} + + sp_condition_value * sp_pcontext::find_declared_or_predefined_condition(THD *thd, const LEX_CSTRING *name) diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index de0c2268a52..e7b41e70d66 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -22,6 +22,7 @@ #include "field.h" // Create_field #include "sql_array.h" // Dynamic_array #include "sp_type_def.h" +#include "sp_rcontext_handler.h" /// This class represents a stored program variable or a parameter @@ -343,30 +344,6 @@ public: }; -/////////////////////////////////////////////////////////////////////////// - -/// This class represents 'DECLARE RECORD' statement. - -class sp_record : public Sql_alloc -{ -public: - /// Name of the record. - Lex_ident_column name; - Row_definition_list *field; - -public: - sp_record(const Lex_ident_column &name_arg, Row_definition_list *prmfield) - :Sql_alloc(), - name(name_arg), - field(prmfield) - { } - bool eq_name(const LEX_CSTRING *str) const - { - return name.streq(*str); - } -}; - - /////////////////////////////////////////////////////////////////////////// /// The class represents parse-time context, which keeps track of declared @@ -565,6 +542,11 @@ public: void declare_var_boundary(uint n) { m_pboundary= n; } + const sp_variable *get_pvariable(const sp_rcontext_addr &addr) const + { + return addr.rcontext_handler()->get_pvariable(this, addr.offset()); + } + ///////////////////////////////////////////////////////////////////////// // CASE expressions. ///////////////////////////////////////////////////////////////////////// @@ -773,6 +755,23 @@ public: return type_defs_add_record(thd, name, field); } + ///////////////////////////////////////////////////////////////////////// + // Composites, e.g. associative arrays. + ///////////////////////////////////////////////////////////////////////// + bool type_defs_declare_composite2(THD *thd, + const Lex_ident_column &name, + const Type_handler *th, + Spvar_definition *key, + Spvar_definition *value) + { + if (unlikely(find_type_def(name, true))) + { + my_error(ER_SP_DUP_DECL, MYF(0), name.str); + return true; + } + return type_defs_add_composite2(thd, name, th, key, value); + } + private: /// Constructor for a tree node. /// @param prev the parent parsing context diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 0ad0ff6a13a..86bc06aee11 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -33,6 +33,12 @@ Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; Sp_rcontext_handler_statement sp_rcontext_handler_statement; +const sp_variable * +Sp_rcontext_handler_local::get_pvariable(const sp_pcontext *pctx, uint i) const +{ + return pctx->find_variable(i); +} + sp_cursor *Sp_rcontext_handler::get_open_cursor_or_error(THD *thd, const sp_rcontext_ref &ref) { @@ -49,6 +55,14 @@ sp_rcontext *Sp_rcontext_handler_local::get_rcontext(sp_rcontext *ctx) const return ctx; } +const sp_variable * +Sp_rcontext_handler_package_body::get_pvariable(const sp_pcontext *pctx, uint i) + const +{ + DBUG_ASSERT(0); + return nullptr; +} + sp_rcontext *Sp_rcontext_handler_package_body::get_rcontext(sp_rcontext *ctx) const { return ctx->m_sp->m_parent->m_rcontext; @@ -663,7 +677,10 @@ int sp_rcontext::set_variable(THD *thd, uint idx, Item **value) { DBUG_ENTER("sp_rcontext::set_variable"); DBUG_ASSERT(value); - DBUG_RETURN(thd->sp_eval_expr(m_var_table->field[idx], value)); + + auto handler= get_variable(idx)->type_handler()->to_composite(); + DBUG_RETURN(thd->sp_eval_expr(m_var_table->field[idx], value) || + (handler && handler->finalize_for_set(get_variable(idx)))); } @@ -677,18 +694,6 @@ int sp_rcontext::set_variable_row_field(THD *thd, uint var_idx, uint field_idx, } -int sp_rcontext::set_variable_row_field_by_name(THD *thd, uint var_idx, - const LEX_CSTRING &field_name, - Item **value) -{ - DBUG_ENTER("sp_rcontext::set_variable_row_field_by_name"); - uint field_idx; - if (find_row_field_by_name_or_error(&field_idx, var_idx, field_name)) - DBUG_RETURN(1); - DBUG_RETURN(set_variable_row_field(thd, var_idx, field_idx, value)); -} - - int sp_rcontext::set_variable_row(THD *thd, uint var_idx, List &items) { DBUG_ENTER("sp_rcontext::set_variable_row"); @@ -709,6 +714,64 @@ Virtual_tmp_table *sp_rcontext::virtual_tmp_table_for_row(uint var_idx) } +int sp_rcontext::set_variable_composite_by_name(THD *thd, uint var_idx, + const LEX_CSTRING &name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_composite_by_name"); + DBUG_ASSERT(get_variable(var_idx)->type() == Item::FIELD_ITEM); + DBUG_ASSERT(get_variable(var_idx)->cmp_type() == ROW_RESULT); + + DBUG_RETURN(set_variable_composite_by_name(thd, get_variable(var_idx), + name, value)); +} + + +int sp_rcontext::set_variable_composite_by_name(THD *thd, Item_field *composite, + const LEX_CSTRING &name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_composite_by_name"); + + auto handler= composite->type_handler()->to_composite(); + DBUG_ASSERT(handler); + + auto elem= handler->get_or_create_item(thd, composite, name); + if (!elem) + DBUG_RETURN(1); + + DBUG_RETURN(thd->sp_eval_expr(elem->field, value) || + handler->finalize_for_set(elem)); +} + + +int +sp_rcontext::set_variable_composite_field_by_key(THD *thd, + uint var_idx, + const LEX_CSTRING &elem_name, + const LEX_CSTRING &field_name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_composite_field_by_key"); + DBUG_ASSERT(value); + + DBUG_ASSERT(get_variable(var_idx)->type() == Item::FIELD_ITEM); + + auto composite= get_variable(var_idx); + auto handler= composite->type_handler()->to_composite(); + DBUG_ASSERT(handler); + + auto elem= handler->get_item(thd, composite, elem_name); + if (!elem) + DBUG_RETURN(1); + + handler->prepare_for_set(elem); + + DBUG_RETURN(set_variable_composite_by_name(thd, elem, field_name, value) || + handler->finalize_for_set(elem)); +} + + bool sp_rcontext::find_row_field_by_name_or_error(uint *field_idx, uint var_idx, const LEX_CSTRING &field_name) diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 46db7b4128f..484d0e000ff 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -209,11 +209,18 @@ public: int set_variable(THD *thd, uint var_idx, Item **value); int set_variable_row_field(THD *thd, uint var_idx, uint field_idx, Item **value); - int set_variable_row_field_by_name(THD *thd, uint var_idx, - const LEX_CSTRING &field_name, - Item **value); int set_variable_row(THD *thd, uint var_idx, List &items); + int set_variable_composite_field_by_key(THD *thd, + uint var_idx, + const LEX_CSTRING &elem_name, + const LEX_CSTRING &field_name, + Item **value); + int set_variable_composite_by_name(THD *thd, uint var_idx, + const LEX_CSTRING &name, + Item **value); + int set_variable_composite_by_name(THD *thd, Item_field *composite, + const LEX_CSTRING &name, Item **value); int set_parameter(THD *thd, uint var_idx, Item **value) { DBUG_ASSERT(var_idx < argument_count()); diff --git a/sql/sp_rcontext_handler.h b/sql/sp_rcontext_handler.h index 4640b1a2f47..0361b205746 100644 --- a/sql/sp_rcontext_handler.h +++ b/sql/sp_rcontext_handler.h @@ -18,6 +18,7 @@ class sp_rcontext; +class sp_pcontext; class sp_cursor; /** @@ -73,6 +74,11 @@ public: name with a package body variable. */ virtual const LEX_CSTRING *get_name_prefix() const= 0; + + // Find a parse time SP variable + virtual const sp_variable *get_pvariable(const sp_pcontext *pctx, + uint offset) const= 0; + /** At execution time THD->spcont points to the run-time context (sp_rcontext) of the currently executed routine. @@ -95,6 +101,8 @@ class Sp_rcontext_handler_local final :public Sp_rcontext_handler { public: const LEX_CSTRING *get_name_prefix() const override; + const sp_variable *get_pvariable(const sp_pcontext *pctx, + uint offset) const override; sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; Item_field *get_variable(THD *thd, uint offset) const override; sp_cursor *get_cursor(THD *thd, uint offset) const override; @@ -111,6 +119,8 @@ class Sp_rcontext_handler_package_body final :public Sp_rcontext_handler { public: const LEX_CSTRING *get_name_prefix() const override; + const sp_variable *get_pvariable(const sp_pcontext *pctx, + uint offset) const override; sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; Item_field *get_variable(THD *thd, uint offset) const override; sp_cursor *get_cursor(THD *thd, uint offset) const override @@ -145,6 +155,12 @@ public: DBUG_ASSERT(0); // There are no session wide SP variables yet. return nullptr; } + const sp_variable *get_pvariable(const sp_pcontext *pctx, + uint offset) const override + { + DBUG_ASSERT(0); + return nullptr; + } sp_cursor *get_cursor(THD *thd, uint offset) const override; sp_cursor *get_cursor_by_ref(THD *thd, const sp_rcontext_addr &ref, bool for_open) const override; diff --git a/sql/sp_type_def.h b/sql/sp_type_def.h index da47003fcef..c158d1bdef4 100644 --- a/sql/sp_type_def.h +++ b/sql/sp_type_def.h @@ -72,6 +72,25 @@ public: }; +/* + This class represents 'DECLARE TYPE .. TABLE OF' statement. +*/ +class sp_type_def_composite2 : public sp_type_def +{ +public: + Spvar_definition *m_def[2]; + +public: + sp_type_def_composite2(const Lex_ident_column &name_arg, + const Type_handler *th, + Spvar_definition *key_def_arg, + Spvar_definition *value_def_arg) + :sp_type_def(name_arg, th), + m_def{key_def_arg, value_def_arg} + { } +}; + + class sp_type_def_list { protected: @@ -92,6 +111,11 @@ public: bool type_defs_add_record(THD *thd, const Lex_ident_column &name, Row_definition_list *field); + bool type_defs_add_composite2(THD *thd, + const Lex_ident_column &name, + const Type_handler *th, + Spvar_definition *key, + Spvar_definition *value); }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 9db15e662b4..5f577b20300 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -8949,6 +8949,51 @@ bool Qualified_column_ident::append_to(THD *thd, String *str) const } +Qualified_ident::Qualified_ident(THD *thd, const Lex_ident_cli_st &a) + :m_cli_pos(a.pos()), + m_spvar(nullptr), + m_defined_parts(1) +{ + m_parts[0]= Lex_ident_sys(thd, &a); + m_parts[1]= m_parts[2]= Lex_ident_sys(); +} + + +Qualified_ident::Qualified_ident(THD *thd, const Lex_ident_cli_st &a, + const Lex_ident_sys_st &b) + :m_cli_pos(a.pos()), + m_spvar(nullptr), + m_defined_parts(2) +{ + m_parts[0]= Lex_ident_sys(thd, &a); + m_parts[1]= b; + m_parts[2]= Lex_ident_sys(); +} + + +Qualified_ident::Qualified_ident(THD *thd, const Lex_ident_cli_st &a, + const Lex_ident_sys_st &b, + const Lex_ident_sys_st &c) + :m_cli_pos(a.pos()), + m_spvar(nullptr), + m_defined_parts(3) +{ + m_parts[0]= Lex_ident_sys(thd, &a); + m_parts[1]= b; + m_parts[2]= c; +} + + +Qualified_ident::Qualified_ident(const Lex_ident_sys_st &a) + :m_cli_pos(nullptr), + m_spvar(nullptr), + m_defined_parts(1) +{ + m_parts[0]= a; + m_parts[1]= m_parts[2]= Lex_ident_sys(); +} + + #endif /* !defined(MYSQL_CLIENT) */ diff --git a/sql/sql_class.h b/sql/sql_class.h index a98998c7a18..0e7608674d1 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -7401,6 +7401,60 @@ public: }; +class Qualified_ident: public Sql_alloc +{ +protected: + const char *m_cli_pos; + Lex_ident_sys_st m_parts[3]; + sp_variable *m_spvar; + uint m_defined_parts; +public: + Qualified_ident(THD *thd, const Lex_ident_cli_st &a); + Qualified_ident(THD *thd, + const Lex_ident_cli_st &a, + const Lex_ident_sys_st &b); + Qualified_ident(THD *thd, + const Lex_ident_cli_st &a, + const Lex_ident_sys_st &b, + const Lex_ident_sys_st &c); + + Qualified_ident(const Lex_ident_sys_st &a); + /* + Returns the position of the first character of the identifier in + the client string. + */ + const char *pos() const + { + return m_cli_pos; + } + + sp_variable *spvar() const + { + return m_spvar; + } + + const Lex_ident_sys_st &part(uint i) const + { + DBUG_ASSERT(i < array_elements(m_parts)); + return m_parts[i]; + } + + uint defined_parts() const + { + return m_defined_parts; + } + + void set_cli_pos(const char *pos) + { + m_cli_pos= pos; + } + void set_spvar(sp_variable *spvar) + { + m_spvar= spvar; + } +}; + + // this is needed for user_vars hash class user_var_entry: public Type_handler_hybrid_field_type { diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e0c50e2a4d1..b48cc9b534f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -6709,7 +6709,7 @@ bool LEX::sf_return_fill_definition(const Lex_field_type_st &def) bool LEX::sf_return_fill_definition_row(Row_definition_list *def) { - sphead->m_return_field_def.set_row_field_definitions(def); + sphead->m_return_field_def.set_row_field_definitions(&type_handler_row, def); return sphead->fill_spvar_definition(thd, &sphead->m_return_field_def) || sphead->row_fill_field_definitions(thd, def); } @@ -6914,7 +6914,7 @@ LEX::sp_variable_declarations_copy_type_finalize_internal(THD *thd, int nvars, if (fields) { DBUG_ASSERT(ref.type_handler() == &type_handler_row); - spvar->field_def.set_row_field_definitions(fields); + spvar->field_def.set_row_field_definitions(&type_handler_row, fields); } spvar->field_def.field_name= spvar->name; } @@ -6959,6 +6959,41 @@ bool LEX::sp_variable_declarations_finalize(THD *thd, int nvars, } +/* + Make instructions for: + var('key') := expr; + var('key').member := expr; +*/ +bool LEX::sp_set_assign_lvalue_function(THD *thd, + const Qualified_ident *ident, + List *args, + const Lex_ident_sys_st &field_name, + Item *item, const LEX_CSTRING &expr_str) +{ + DBUG_ASSERT(ident); + DBUG_ASSERT(item); + + sp_pcontext *ctx; + const Sp_rcontext_handler *rh; + + sp_variable *spv= find_variable(&ident->part(0), &ctx, &rh); + if (!spv->type_handler()->has_functors()) + { + spv->type_handler()->raise_bad_data_type_for_functor(*ident); + return true; + } + + const sp_rcontext_addr addr(rh, spv->offset); + item= sphead->adjust_assignment_source(thd, item, nullptr); + + sp_instr *i= spv->type_handler()-> + create_instr_set_assign_functor(thd, this, *ident, addr, + args, field_name, + item, expr_str); + return !i || sphead->add_instr(i); +} + + bool LEX::sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *dflt_value_item, @@ -8717,7 +8752,45 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, } -my_var *LEX::create_outvar(THD *thd,const Lex_ident_sys_st &name) +/* + Generate an Item for expressions of these types: + 1. varname(args) + 2. varname(args).member + + @param thd - Current thd + @param name - The variable name. It's known to be an existing variable. + @param args - The list of arguments + @param member - The member name. If member.is_null() then it's + an expression of the type #1, otherwise of the type #2. + @param name_cli - The query fragment for the entire expression, + starting from 'ident' and ending after ')' or 'field'. +*/ +Item_splocal * +LEX::create_item_functor(THD *thd, + const Lex_ident_sys &varname, List *args, + const Lex_ident_sys &member, + const Lex_ident_cli_st &name_cli) +{ + DBUG_ASSERT(!varname.is_null()); + const Sp_rcontext_handler *rh; + sp_variable *spv= find_variable(&varname, &rh); + DBUG_ASSERT(spv); + DBUG_ASSERT(spv->type_handler()->has_functors()); + const sp_rcontext_addr addr(rh, spv->offset); + Item_splocal *item= spv->type_handler()->create_item_functor(thd, varname, + addr, args, + member, + name_cli); +#ifdef DBUG_ASSERT_EXISTS + if (item) + item->m_sp= sphead; +#endif + + return item; +} + + +my_var *LEX::create_outvar(THD *thd, const Lex_ident_sys_st &name) { const Sp_rcontext_handler *rh; sp_variable *spv; @@ -8753,6 +8826,55 @@ my_var *LEX::create_outvar(THD *thd, } +/* + In a statement like: + SELECT val INTO spvar(key); -- if field_name.length == 0 or + SELECT val INTO spvar(key).field; -- if field_name.length > 0 + validate the INTO expression and optionally create a my_var instance. + + spvar is a structured variable, such as an assoc array. + We don't support other kinds of lvalue functions yet. + + @param thd - Current thd + @param name - The SP variable name + @param key - The argument (e.g. an assoc array key value) + @param field_name - The field name to be used in the expression + spvar(key).field . + + @returns - The pointer to a new my_var created or nullptr. + * nullptr if the INTO expression is not a correct + lvalue expression. + * nullptr if LEX::result is NULL. + * nullptr if EOM happened (e.g. during "new"). + * A pointer to a new my_var instance if + LEX::result is not NULL and the lvalue expression + spvar(key) is correct. +*/ +my_var *LEX::create_outvar_lvalue_function(THD *thd, + const Lex_ident_sys_st &name, + Item *key, + const Lex_ident_sys &opt_field_name) +{ + DBUG_ASSERT(key); + // So far we support only data type functors as lvalue functions. + const Sp_rcontext_handler *rh; + sp_variable *t; + if (unlikely(!(t= find_variable(&name, &rh)))) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str); + return NULL; + } + + const sp_rcontext_addr addr(rh, t->offset); + my_var *var= t->type_handler()->make_outvar_lvalue_functor(thd, name, key, + opt_field_name, + sphead, addr, + !result); + DBUG_ASSERT(var || thd->is_error() || !result); + return var; +} + + Item *LEX::create_item_func_nextval(THD *thd, Table_ident *table_ident) { TABLE_LIST *table; @@ -8832,11 +8954,22 @@ Item *LEX::create_item_ident(THD *thd, Lex_ident_sys a(thd, ca), b(thd, cb); if (a.is_null() || b.is_null()) return NULL; // OEM - if ((spv= find_variable(&a, &rh)) && - (spv->field_def.is_row() || + if ((spv= find_variable(&a, &rh))) + { + if (spv->field_def.is_row() || spv->field_def.is_table_rowtype_ref() || - spv->field_def.is_cursor_rowtype_ref())) - return create_item_spvar_row_field(thd, rh, &a, &b, spv, start, end); + spv->field_def.is_cursor_rowtype_ref()) + return create_item_spvar_row_field(thd, rh, &a, &b, spv, start, end); + if (spv->type_handler()->has_methods()) + { + const Lex_ident_sys sys_a(thd, ca), sys_b(thd, cb); + const Lex_ident_cli query_fragment(start, end - start); + if (sys_a.is_null() || sys_b.is_null()) + return nullptr; // EOM + return spv->type_handler()->create_item_method(thd, sys_a, sys_b, + NULL, query_fragment); + } + } if ((thd->variables.sql_mode & MODE_ORACLE) && b.length == 7) { @@ -9153,6 +9286,22 @@ bool LEX::set_variable(const Lex_ident_sys_st *name1, } +bool LEX::set_variable(const Qualified_ident *ident, + Item *item, const LEX_CSTRING &expr_str) +{ + if (unlikely(ident->part(2).length)) + { + thd->parse_error(ER_SYNTAX_ERROR, ident->pos()); + return true; + } + + if (ident->part(1).length) + return set_variable(&ident->part(0), &ident->part(1), item, expr_str); + + return set_variable(&ident->part(0), item, expr_str); +} + + bool LEX::set_default_system_variable(enum_var_type var_type, const Lex_ident_sys_st *name, Item *val) @@ -9911,7 +10060,7 @@ bool LEX::call_statement_start(THD *thd, DBUG_ASSERT(db->str); Identifier_chain2 q_pkg_proc(*pkg, *proc); sp_name *spname; - + value_list.empty(); sql_command= SQLCOM_CALL; const Lex_ident_db_normalized dbn= thd->to_ident_db_normalized_with_error(*db); @@ -9937,6 +10086,61 @@ bool LEX::call_statement_start(THD *thd, } +bool LEX::call_statement_start(THD *thd, const Qualified_ident *ident) +{ + if (ident->part(2).length) + return call_statement_start(thd, &ident->part(0), + &ident->part(1), &ident->part(2)); + else if (ident->part(1).length) + return call_statement_start(thd, &ident->part(0), &ident->part(1)); + + return call_statement_start(thd, &ident->part(0)); +} + + +bool LEX::call_statement_start_or_lvalue_assign(THD *thd, + Qualified_ident *ident) +{ + sp_variable *spv; + if (spcont && + (spv= spcont->find_variable(&ident->part(0), false)) && + (likely(spv->field_def.type_handler()->has_methods()))) + { + ident->set_spvar(spv); + + thd->where= THD_WHERE::USE_WHERE_STRING; + thd->where_str= "SPVAR LVALUE METHOD"; + return false; + } + + // Direct procedure call (without the CALL keyword) + if (unlikely(call_statement_start(thd, ident))) + return true; + + thd->where= THD_WHERE::USE_WHERE_STRING; + thd->where_str= "CALL"; + + return false; +} + + +bool LEX::assoc_assign_start(THD *thd, Qualified_ident *ident) +{ + if (unlikely(ident->spvar() == NULL)) + { + thd->parse_error(); + return true; + } + + LEX *lex= this; + lex->set_stmt_init(); + if (sp_create_assignment_lex(thd, ident->pos())) + return true; + + return false; +} + + sp_package *LEX::get_sp_package() const { return sphead ? sphead->get_package() : NULL; @@ -10304,6 +10508,36 @@ Item *LEX::make_item_func_call_generic(THD *thd, } +Item *LEX::make_item_func_or_method_call(THD *thd, + const Lex_ident_cli_st &ca, + const Lex_ident_cli_st &cb, + List *args, + const Lex_ident_cli_st &query_fragment) +{ + DBUG_ASSERT(!thd->is_error()); + const Lex_ident_sys sys_a(thd, &ca), sys_b(thd, &cb); + if (sys_a.is_null() || sys_b.is_null()) + return nullptr; // EOM + sp_variable *spv; + if (spcont && + (spv= spcont->find_variable(&sys_a, false)) && + spv->type_handler()->has_methods()) + { + if (Item *item= spv->type_handler()->create_item_method(thd, + sys_a, sys_b, args, + query_fragment)) + { + item->set_name(thd, query_fragment, thd->charset()); + return item; + } + DBUG_ASSERT(thd->is_error()); + return nullptr; + } + + return make_item_func_call_generic(thd, &ca, &cb, args); +} + + Item *LEX::make_item_func_call_generic(THD *thd, const Lex_ident_db &db, const Lex_ident_routine &name, @@ -12454,6 +12688,13 @@ bool LEX::stmt_alter_procedure_start(sp_name *name) Spvar_definition *LEX::row_field_name(THD *thd, const Lex_ident_sys_st &name) +{ + return init_spvar_definition(thd, name); +} + + +Spvar_definition *LEX::init_spvar_definition(THD *thd, + const Lex_ident_sys_st &name) { Spvar_definition *res; if (unlikely(check_string_char_length(&name, 0, NAME_CHAR_LEN, @@ -12548,6 +12789,41 @@ bool LEX::set_cast_type_udt(Lex_cast_type_st *type, } +bool LEX::set_field_type_composite(Lex_field_type_st *type, + const LEX_CSTRING &name, + bool with_collection, + bool *is_composite) +{ + DBUG_ASSERT(type); + DBUG_ASSERT(is_composite); + + sp_type_def *composite= NULL; + + *is_composite= false; + if (spcont) + { + if ((composite= spcont->find_type_def(name, false))) + { + if (with_collection || + likely(composite->type_handler() == &type_handler_row)) + { + type->set(composite->type_handler(), NULL); + last_field->set_attr_const_void_ptr(0, composite); + } + else + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "nested associative arrays"); + return true; + } + + *is_composite= true; + } + } + + return false; +} + + bool sp_expr_lex::sp_repeat_loop_finalize(THD *thd) { uint ip= sphead->instructions(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 1e65112c16d..875758f3bc3 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -165,6 +165,7 @@ class sp_variable; class sp_fetch_target; class sp_expr_lex; class sp_assignment_lex; +class sp_type_def; class partition_info; class Event_parse_data; class set_var_base; @@ -3914,6 +3915,11 @@ public: const Lex_ident_sys_st *db, const Lex_ident_sys_st *pkg, const Lex_ident_sys_st *proc); + bool call_statement_start(THD *thd, const Qualified_ident *ident); + bool call_statement_start_or_lvalue_assign(THD *thd, + Qualified_ident *ident); + + bool assoc_assign_start(THD *thd, Qualified_ident *ident); sp_variable *find_variable(const LEX_CSTRING *name, sp_pcontext **ctx, const Sp_rcontext_handler **rh) const; @@ -3937,6 +3943,8 @@ public: bool set_variable(const Lex_ident_sys_st *name1, const Lex_ident_sys_st *name2, Item *item, const LEX_CSTRING &expr_str); + bool set_variable(const Qualified_ident *ident, Item *item, + const LEX_CSTRING &expr_str); void sp_variable_declarations_init(THD *thd, int nvars); bool sp_variable_declarations_finalize(THD *thd, int nvars, const Column_definition *cdef, @@ -3944,10 +3952,11 @@ public: const LEX_CSTRING &expr_str); bool sp_variable_declarations_set_default(THD *thd, int nvars, Item *def, const LEX_CSTRING &expr_str); - bool sp_variable_declarations_rec_finalize(THD *thd, int nvars, - Row_definition_list *src_row, - Item *def, - const LEX_CSTRING &expr_str); + bool sp_set_assign_lvalue_function(THD *thd, + const Qualified_ident *ident, + List *params, + const Lex_ident_sys_st &field_name, + Item *item, const LEX_CSTRING &expr_str); bool sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *def, @@ -4177,6 +4186,17 @@ public: return nullptr; } + /* + Create items of this kind: + SELECT name(args); -- e.g. spvar_assoc_array('key') + SELECT name(args).member; -- e.g. spvar_assoc_array('key').member + */ + Item_splocal *create_item_functor(THD *thd, + const Lex_ident_sys &name, + List *args, + const Lex_ident_sys &member, + const Lex_ident_cli_st &query_fragment); + /* Create an item for "NEXT VALUE FOR sequence_name" */ @@ -4274,6 +4294,12 @@ public: Item *make_item_func_call_native_or_parse_error(THD *thd, Lex_ident_cli_st &name, List *args); + Item *make_item_func_or_method_call(THD *thd, + const Lex_ident_cli_st &ident0, + const Lex_ident_cli_st &ident1, + List *args, + const Lex_ident_cli_st &query_fragment); + my_var *create_outvar(THD *thd, const Lex_ident_sys_st &name); /* @@ -4287,6 +4313,10 @@ public: const Lex_ident_sys_st &var_name, const Lex_ident_sys_st &field_name); + my_var *create_outvar_lvalue_function(THD *thd, const Lex_ident_sys_st &name, + Item *arg, + const Lex_ident_sys &opt_field_name); + bool is_trigger_new_or_old_reference(const LEX_CSTRING *name) const; Item *create_and_link_Item_trigger_field(THD *thd, const LEX_CSTRING *name, @@ -4949,6 +4979,8 @@ public: sp_condition_value *stmt_signal_value(const Lex_ident_sys_st &ident); Spvar_definition *row_field_name(THD *thd, const Lex_ident_sys_st &name); + Spvar_definition *init_spvar_definition(THD *thd, + const Lex_ident_sys_st &name); bool set_field_type_udt(Lex_field_type_st *type, const LEX_CSTRING &name, @@ -4956,6 +4988,11 @@ public: bool set_cast_type_udt(Lex_cast_type_st *type, const LEX_CSTRING &name); + bool set_field_type_composite(Lex_field_type_st *type, + const LEX_CSTRING &name, + bool with_collection, + bool *is_composite); + bool map_data_type(const Lex_ident_sys_st &schema, Lex_field_type_st *type) const; diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 8015051ac5b..1c465f3ea21 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -800,6 +800,15 @@ void Binary_string::qs_append(ulonglong i) } +void Binary_string::qs_append_int64(longlong i) +{ + char *buff= Ptr + str_length; + char *end= longlong10_to_str(i, buff, -10); + ASSERT_LENGTH((size_t) (end-buff)); + str_length+= (uint32) (end-buff); +} + + bool Binary_string::copy_printable_hhhh(CHARSET_INFO *to_cs, CHARSET_INFO *from_cs, const char *from, diff --git a/sql/sql_string.h b/sql/sql_string.h index 698c9960c04..d650ef49462 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -392,6 +392,12 @@ public: int4store(Ptr + str_length, n); str_length += 4; } + void q_append_int64(const longlong n) + { + ASSERT_LENGTH(8); + int8store(Ptr + str_length, n); + str_length += 8; + } void q_append(double d) { ASSERT_LENGTH(8); @@ -475,6 +481,8 @@ public: str_length+= (uint32) (end-buff); } + void qs_append_int64(longlong i); + /* Mark variable thread specific it it's not allocated already */ inline void set_thread_specific() { diff --git a/sql/sql_type.cc b/sql/sql_type.cc index bd62399ea2d..e14b4ed2222 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -123,8 +123,6 @@ bool DTCollation::merge_collation(Sql_used *used, } -Named_type_handler type_handler_row("row"); - Named_type_handler type_handler_null("null"); Named_type_handler type_handler_bool("boolean"); @@ -217,55 +215,6 @@ bool Type_handler::is_traditional_scalar_type() const } -class Type_collection_row: public Type_collection -{ -public: - bool init(Type_handler_data *data) override - { - return false; - } - const Type_handler *aggregate_for_result(const Type_handler *a, - const Type_handler *b) - const override - { - return NULL; - } - const Type_handler *aggregate_for_comparison(const Type_handler *a, - const Type_handler *b) - const override - { - /* - Allowed combinations: - ROW+ROW, NULL+ROW, ROW+NULL - */ - DBUG_ASSERT(a == &type_handler_row || a == &type_handler_null); - DBUG_ASSERT(b == &type_handler_row || b == &type_handler_null); - DBUG_ASSERT(a == &type_handler_row || b == &type_handler_row); - return &type_handler_row; - } - const Type_handler *aggregate_for_min_max(const Type_handler *a, - const Type_handler *b) - const override - { - return NULL; - } - const Type_handler *aggregate_for_num_op(const Type_handler *a, - const Type_handler *b) - const override - { - return NULL; - } -}; - - -static Type_collection_row type_collection_row; - -const Type_collection *Type_handler_row::type_collection() const -{ - return &type_collection_row; -} - - bool Type_handler_data::init() { return type_collection_geometry.init(this); @@ -1771,11 +1720,6 @@ const Type_handler *Type_handler_timestamp_common::type_handler_for_comparison() } -const Type_handler *Type_handler_row::type_handler_for_comparison() const -{ - return &type_handler_row; -} - /***************************************************************************/ const Type_handler * @@ -3128,19 +3072,6 @@ bool Type_handler_null:: return false; } -bool Type_handler_row:: - Column_definition_prepare_stage1(THD *thd, - MEM_ROOT *mem_root, - Column_definition *def, - column_definition_type_t type, - const Column_derived_attributes - *derived_attr) - const -{ - def->charset= &my_charset_bin; - def->create_length_to_internal_length_null(); - return false; -} bool Type_handler_temporal_result:: Column_definition_prepare_stage1(THD *thd, @@ -3446,24 +3377,6 @@ bool Type_handler_bit:: } -/*************************************************************************/ -bool Type_handler_row::Spvar_definition_with_complex_data_types( - Spvar_definition *def) const -{ - if (def->row_field_definitions()) - { - List_iterator it(*(def->row_field_definitions())); - Spvar_definition *member; - while ((member= it++)) - { - if (member->type_handler()->is_complex()) - return true; - } - } - return false; -} - - /*************************************************************************/ bool Type_handler::Key_part_spec_init_primary(Key_part_spec *part, const Column_definition &def, @@ -3699,6 +3612,23 @@ my_var *Type_handler::make_outvar_field(THD *thd, } +/* + SELECT 1 INTO spvar(arg); + SELECT 1 INTO spvar(arg).field; +*/ +my_var *Type_handler::make_outvar_lvalue_functor(THD *thd, + const Lex_ident_sys_st &name, + Item *arg, + const Lex_ident_sys &opt_field, + sp_head *sphead, + const sp_rcontext_addr &addr, + bool validate_only) const +{ + raise_bad_data_type_for_functor(Qualified_ident(name)); + return nullptr; +} + + /*************************************************************************/ Field *Type_handler::make_and_init_table_field(MEM_ROOT *root, const LEX_CSTRING *name, @@ -4393,13 +4323,6 @@ Type_handler_bit::Bit_decimal_notation_int_digits_by_nbits(uint nbits) /*************************************************************************/ -void Type_handler_row::Item_update_null_value(Item *item) const -{ - DBUG_ASSERT(0); - item->null_value= true; -} - - void Type_handler_time_common::Item_update_null_value(Item *item) const { MYSQL_TIME ltime; @@ -4514,12 +4437,6 @@ int Type_handler_bool::Item_save_in_field(Item *item, Field *field, /***********************************************************************/ -bool Type_handler_row:: -set_comparator_func(THD *thd, Arg_comparator *cmp) const -{ - return cmp->set_cmp_func_row(thd); -} - bool Type_handler_int_result:: set_comparator_func(THD *thd, Arg_comparator *cmp) const { @@ -4660,12 +4577,6 @@ bool Type_handler_numeric:: /*************************************************************************/ -Item_cache * -Type_handler_row::Item_get_cache(THD *thd, const Item *item) const -{ - return new (thd->mem_root) Item_cache_row(thd); -} - Item_cache * Type_handler_int_result::Item_get_cache(THD *thd, const Item *item) const { @@ -5379,6 +5290,32 @@ Type_handler::Item_func_hybrid_field_type_val_ref(THD *thd, } +void Type_handler:: +raise_bad_data_type_for_functor(const Qualified_ident &ident, + const Lex_ident_sys &field) const +{ + DBUG_ASSERT(ident.defined_parts() > 0 && ident.defined_parts() <= 3); + + char param[MYSQL_ERRMSG_SIZE]; + uint used= 0; + for (uint i= 0; i < ident.defined_parts() && used < sizeof(param); i++) + { + used+= my_snprintf(param + used, + sizeof(param) - used, + "%sQ.", + ident.part(i).str); + } + used-= 1; + if (!field.str) + my_snprintf(param + used, sizeof(param) - used, "(..)"); + else + my_snprintf(param + used, sizeof(param) - used, "(..).%sQ", field.str); + + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + name().ptr(), param); +} + + /************************************************************************/ void Type_handler_decimal_result::Item_get_date(THD *thd, Item *item, Temporal::Warn *warn, @@ -5927,14 +5864,6 @@ bool Type_handler_string_result:: } -longlong Type_handler_row:: - Item_func_between_val_int(Item_func_between *func) const -{ - DBUG_ASSERT(0); - func->null_value= true; - return 0; -} - longlong Type_handler_string_result:: Item_func_between_val_int(Item_func_between *func) const { @@ -6004,12 +5933,6 @@ cmp_item *Type_handler_string_result::make_cmp_item(THD *thd, return new (thd->mem_root) cmp_item_sort_string(cs); } -cmp_item *Type_handler_row::make_cmp_item(THD *thd, - CHARSET_INFO *cs) const -{ - return new (thd->mem_root) cmp_item_row; -} - cmp_item *Type_handler_time_common::make_cmp_item(THD *thd, CHARSET_INFO *cs) const { @@ -6096,13 +6019,6 @@ Type_handler_timestamp_common::make_in_vector(THD *thd, } -in_vector *Type_handler_row::make_in_vector(THD *thd, - const Item_func_in *func, - uint nargs) const -{ - return new (thd->mem_root) in_row(thd, nargs, 0); -} - /***************************************************************************/ bool Type_handler_string_result:: @@ -6173,14 +6089,6 @@ bool Type_handler_temporal_result:: } -bool Type_handler_row::Item_func_in_fix_comparator_compatible_types(THD *thd, - Item_func_in *func) const -{ - return func->compatible_types_row_bisection_possible() ? - func->fix_for_row_comparison_using_bisection(thd) : - func->fix_for_row_comparison_using_cmp_items(thd); -} - /***************************************************************************/ String *Type_handler_string_result:: @@ -6449,32 +6357,6 @@ bool Type_handler_timestamp_common:: /***************************************************************************/ -/** - Get a string representation of the Item value. - See sql_type.h for details. -*/ -String *Type_handler_row:: - print_item_value(THD *thd, Item *item, String *str) const -{ - CHARSET_INFO *cs= thd->variables.character_set_client; - StringBuffer val(cs); - str->append(STRING_WITH_LEN("ROW(")); - for (uint i= 0 ; i < item->cols(); i++) - { - if (i > 0) - str->append(','); - Item *elem= item->element_index(i); - String *tmp= elem->type_handler()->print_item_value(thd, elem, &val); - if (tmp) - str->append(*tmp); - else - str->append(NULL_clex_str); - } - str->append(')'); - return str; -} - - /** Get a string representation of the Item value, using the character string format with its charset and collation, e.g. @@ -6513,6 +6395,18 @@ String *Type_handler_numeric:: } +String *Type_handler_bool:: + print_item_value(THD *thd, Item *item, String *str) const +{ + DBUG_ASSERT(item->fixed()); + bool b=item->val_bool(); + if (item->null_value) + return 0; + str->set_int(b, item->unsigned_flag, item->collation.collation); + return str; +} + + String *Type_handler:: print_item_value_temporal(THD *thd, Item *item, String *str, const Name &type_name, String *buf) const @@ -6567,14 +6461,6 @@ String *Type_handler_timestamp_common:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_round_fix_length_and_dec(Item_func_round *item) const -{ - DBUG_ASSERT(0); - return false; -} - - bool Type_handler_int_result:: Item_func_round_fix_length_and_dec(Item_func_round *item) const { @@ -6685,14 +6571,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) const -{ - DBUG_ASSERT(0); - return false; -} - - bool Type_handler_int_result:: Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) const { @@ -6797,14 +6675,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_abs_fix_length_and_dec(Item_func_abs *item) const -{ - DBUG_ASSERT(0); - return false; -} - - bool Type_handler_int_result:: Item_func_abs_fix_length_and_dec(Item_func_abs *item) const { @@ -6855,14 +6725,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_neg_fix_length_and_dec(Item_func_neg *item) const -{ - DBUG_ASSERT(0); - return false; -} - - bool Type_handler_int_result:: Item_func_neg_fix_length_and_dec(Item_func_neg *item) const { @@ -7070,14 +6932,6 @@ bool Type_handler:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_plus_fix_length_and_dec(Item_func_plus *item) const -{ - DBUG_ASSERT(0); - return true; -} - - bool Type_handler_int_result:: Item_func_plus_fix_length_and_dec(Item_func_plus *item) const { @@ -7119,14 +6973,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_minus_fix_length_and_dec(Item_func_minus *item) const -{ - DBUG_ASSERT(0); - return true; -} - - bool Type_handler_int_result:: Item_func_minus_fix_length_and_dec(Item_func_minus *item) const { @@ -7168,14 +7014,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_mul_fix_length_and_dec(Item_func_mul *item) const -{ - DBUG_ASSERT(0); - return true; -} - - bool Type_handler_int_result:: Item_func_mul_fix_length_and_dec(Item_func_mul *item) const { @@ -7217,14 +7055,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_div_fix_length_and_dec(Item_func_div *item) const -{ - DBUG_ASSERT(0); - return true; -} - - bool Type_handler_int_result:: Item_func_div_fix_length_and_dec(Item_func_div *item) const { @@ -7266,14 +7096,6 @@ bool Type_handler_string_result:: /***************************************************************************/ -bool Type_handler_row:: - Item_func_mod_fix_length_and_dec(Item_func_mod *item) const -{ - DBUG_ASSERT(0); - return true; -} - - bool Type_handler_int_result:: Item_func_mod_fix_length_and_dec(Item_func_mod *item) const { @@ -7588,15 +7410,6 @@ bool Type_handler_null:: } -bool Type_handler_row:: - Item_save_in_value(THD *thd, Item *item, st_value *value) const -{ - DBUG_ASSERT(0); - value->m_type= DYN_COL_NULL; - return true; -} - - bool Type_handler_int_result:: Item_save_in_value(THD *thd, Item *item, st_value *value) const { @@ -7657,18 +7470,6 @@ bool Type_handler_time_common:: /***************************************************************************/ -bool Type_handler_row:: - Item_param_set_from_value(THD *thd, - Item_param *param, - const Type_all_attributes *attr, - const st_value *val) const -{ - DBUG_ASSERT(0); - param->set_null(); - return true; -} - - bool Type_handler_real_result:: Item_param_set_from_value(THD *thd, Item_param *param, @@ -7937,38 +7738,6 @@ Item *Type_handler_temporal_with_date:: } -Item *Type_handler_row:: - make_const_item_for_comparison(THD *thd, Item *item, const Item *cmp) const -{ - if (item->type() == Item::ROW_ITEM && cmp->type() == Item::ROW_ITEM) - { - /* - Substitute constants only in Item_row's. Don't affect other Items - with ROW_RESULT (eg Item_singlerow_subselect). - - For such Items more optimal is to detect if it is constant and replace - it with Item_row. This would optimize queries like this: - SELECT * FROM t1 WHERE (a,b) = (SELECT a,b FROM t2 LIMIT 1); - */ - Item_row *item_row= (Item_row*) item; - Item_row *comp_item_row= (Item_row*) cmp; - uint col; - /* - If item and comp_item are both Item_row's and have same number of cols - then process items in Item_row one by one. - We can't ignore NULL values here as this item may be used with <=>, in - which case NULL's are significant. - */ - DBUG_ASSERT(item->result_type() == cmp->result_type()); - DBUG_ASSERT(item_row->cols() == comp_item_row->cols()); - col= item_row->cols(); - while (col-- > 0) - resolve_const_item(thd, item_row->addr(col), - comp_item_row->element_index(col)); - } - return NULL; -} - /***************************************************************************/ /* @@ -8403,19 +8172,6 @@ void Type_handler_typelib::Item_param_set_param_func(Item_param *param, /***************************************************************************/ -Field *Type_handler_row:: - make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, - const LEX_CSTRING *name, - const Record_addr &rec, const Bit_addr &bit, - const Column_definition_attributes *attr, - uint32 flags) const -{ - DBUG_ASSERT(attr->length == 0); - DBUG_ASSERT(f_maybe_null(attr->pack_flag)); - return new (mem_root) Field_row(rec.ptr(), name); -} - - Field *Type_handler_olddecimal:: make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, const LEX_CSTRING *name, @@ -9146,14 +8902,6 @@ Type_handler_hex_hybrid::cast_to_int_type_handler() const /***************************************************************************/ -bool Type_handler_row::Item_eq_value(THD *thd, const Type_cmp_attributes *attr, - Item *a, Item *b) const -{ - DBUG_ASSERT(0); - return false; -} - - bool Type_handler_int_result::Item_eq_value(THD *thd, const Type_cmp_attributes *attr, Item *a, Item *b) const @@ -9646,13 +9394,6 @@ bool Type_handler_datetime_common::validate_implicit_default_value(THD *thd, /***************************************************************************/ -const Name & Type_handler_row::default_value() const -{ - DBUG_ASSERT(0); - static Name def(STRING_WITH_LEN("")); - return def; -} - const Name & Type_handler_numeric::default_value() const { static Name def(STRING_WITH_LEN("0")); diff --git a/sql/sql_type.h b/sql/sql_type.h index 4067aebc95d..84ff6ef3650 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -35,6 +35,7 @@ C_MODE_START C_MODE_END class Field; +class Qualified_ident; class Column_definition; class Column_definition_attributes; class Key_part_spec; @@ -79,6 +80,7 @@ class Item_func_mul; class Item_func_div; class Item_func_mod; class Item_type_holder; +class Item_splocal; class cmp_item; class in_vector; class Type_handler_data; @@ -98,8 +100,10 @@ class Conv_source; class ST_FIELD_INFO; class Type_collection; class Create_func; +class Type_handler_composite; class sp_type_def; class sp_head; +class sp_instr; class my_var; #define my_charset_numeric my_charset_latin1 @@ -4135,6 +4139,15 @@ public: return false; } + /* + Convert "this" to a composite type handler. + Scalar type handlers return nullptr meaning that they are not composite. + */ + virtual const Type_handler_composite *to_composite() const + { + return nullptr; + } + virtual bool partition_field_check(const LEX_CSTRING &field_name, Item *) const { @@ -4185,6 +4198,12 @@ public: virtual bool can_return_extract_source(interval_type type) const; virtual bool is_bool_type() const { return false; } virtual bool is_general_purpose_string_type() const { return false; } + virtual bool has_methods() const { return false; } + /* + If an SP variable supports: spvar(expr_list). + For example, assoc arrays support: spvar_assoc_array('key') + */ + virtual bool has_functors() const { return false; } virtual bool has_null_predicate() const { return true; } virtual decimal_digits_t Item_time_precision(THD *thd, Item *item) const; virtual decimal_digits_t Item_datetime_precision(THD *thd, Item *item) const; @@ -4409,6 +4428,17 @@ public: const Lex_ident_sys_st &field, sp_head *sphead, bool validate_only) const; + /* + SELECT 1 INTO spvar(arg); + SELECT 1 INTO spvar(arg).field; + */ + virtual my_var *make_outvar_lvalue_functor(THD *thd, + const Lex_ident_sys_st &name, + Item *arg, + const Lex_ident_sys &opt_field, + sp_head *sphead, + const sp_rcontext_addr &addr, + bool validate_only) const; virtual void Column_definition_attributes_frm_pack(const Column_definition_attributes *at, uchar *buff) const; @@ -4627,6 +4657,50 @@ public: return nullptr; } virtual Item_copy *create_item_copy(THD *thd, Item *item) const; + /* + Create an Item for an expression of this kind: + SELECT spvar(args); -- e.g. spvar_assoc_array('key') + SELECT spvar(args).field; -- e.g. spvar_assoc_array('key').field + */ + virtual Item_splocal *create_item_functor(THD *thd, + const Lex_ident_sys &a, + const sp_rcontext_addr &addr, + List *item_list, + const Lex_ident_sys &b, + const Lex_ident_cli_st &name) + const + { + DBUG_ASSERT(0); // Should have checked has_functors(). + return nullptr; + } + /* + Generate instructions for: + spvar(args) := expr; -- e.g. spvar_assoc_array('key') := 10; + spvar(args).member := expr; -- e.g. spvar_assoc_array('key').field:= 10; + */ + virtual + sp_instr *create_instr_set_assign_functor(THD *thd, LEX *lex, + const Qualified_ident &ident, + const sp_rcontext_addr &addr, + List *args, + const Lex_ident_sys_st &member, + Item *item, + const LEX_CSTRING &expr_str) const + { + DBUG_ASSERT(0); // Should have checked has_functors(). + return nullptr; + } + virtual Item *create_item_method(THD *thd, + const Lex_ident_sys &ca, + const Lex_ident_sys &cb, + List *args, + const Lex_ident_cli_st &query_fragment) + const + { + DBUG_ASSERT(0); // Should have checked has_methods(). + return nullptr; + } + virtual int cmp_native(const Native &a, const Native &b) const { MY_ASSERT_UNREACHABLE(); @@ -4778,6 +4852,10 @@ public: Item_func_mod_fix_length_and_dec(Item_func_mod *func) const= 0; virtual const Vers_type_handler *vers() const { return NULL; } + + void raise_bad_data_type_for_functor(const Qualified_ident &ident, + const Lex_ident_sys &field= + Lex_ident_sys()) const; }; @@ -5076,14 +5154,53 @@ public: }; -class Type_limits_int +class Type_range_int +{ + const longlong m_min_signed; + const longlong m_max_signed; + const ulonglong m_max_unsigned; +public: + Type_range_int(longlong min_signed, longlong max_signed, + ulonglong max_unsigned) + :m_min_signed(min_signed), m_max_signed(max_signed), + m_max_unsigned(max_unsigned) + { } + longlong min_signed() const { return m_min_signed; } + longlong max_signed() const { return m_max_signed; } + ulonglong max_unsigned() const { return m_max_unsigned; } + + static Type_range_int range8() + { + return Type_range_int(INT_MIN8, INT_MAX8, UINT_MAX8); + } + static Type_range_int range16() + { + return Type_range_int(INT_MIN16, INT_MAX16, UINT_MAX16); + } + static Type_range_int range24() + { + return Type_range_int(INT_MIN24, INT_MAX24, UINT_MAX24); + } + static Type_range_int range32() + { + return Type_range_int(INT_MIN32, INT_MAX32, UINT_MAX32); + } + static Type_range_int range64() + { + return Type_range_int(INT_MIN64, INT_MAX64, UINT64_MAX); + } +}; + + +class Type_limits_int: public Type_range_int { private: uint32 m_precision; uint32 m_char_length; public: - Type_limits_int(uint32 prec, uint32 nchars) - :m_precision(prec), m_char_length(nchars) + Type_limits_int(uint32 prec, uint32 nchars, const Type_range_int &range) + :Type_range_int(range), + m_precision(prec), m_char_length(nchars) { } uint32 precision() const { return m_precision; } uint32 char_length() const { return m_char_length; } @@ -5098,7 +5215,7 @@ class Type_limits_uint8: public Type_limits_int { public: Type_limits_uint8() - :Type_limits_int(MAX_TINYINT_WIDTH, MAX_TINYINT_WIDTH) + :Type_limits_int(MAX_TINYINT_WIDTH, MAX_TINYINT_WIDTH, range8()) { } }; @@ -5107,7 +5224,7 @@ class Type_limits_sint8: public Type_limits_int { public: Type_limits_sint8() - :Type_limits_int(MAX_TINYINT_WIDTH, MAX_TINYINT_WIDTH + 1) + :Type_limits_int(MAX_TINYINT_WIDTH, MAX_TINYINT_WIDTH + 1, range8()) { } }; @@ -5120,7 +5237,7 @@ class Type_limits_uint16: public Type_limits_int { public: Type_limits_uint16() - :Type_limits_int(MAX_SMALLINT_WIDTH, MAX_SMALLINT_WIDTH) + :Type_limits_int(MAX_SMALLINT_WIDTH, MAX_SMALLINT_WIDTH, range16()) { } }; @@ -5129,7 +5246,7 @@ class Type_limits_sint16: public Type_limits_int { public: Type_limits_sint16() - :Type_limits_int(MAX_SMALLINT_WIDTH, MAX_SMALLINT_WIDTH + 1) + :Type_limits_int(MAX_SMALLINT_WIDTH, MAX_SMALLINT_WIDTH + 1, range16()) { } }; @@ -5142,7 +5259,7 @@ class Type_limits_uint24: public Type_limits_int { public: Type_limits_uint24() - :Type_limits_int(MAX_MEDIUMINT_WIDTH, MAX_MEDIUMINT_WIDTH) + :Type_limits_int(MAX_MEDIUMINT_WIDTH, MAX_MEDIUMINT_WIDTH, range24()) { } }; @@ -5151,7 +5268,7 @@ class Type_limits_sint24: public Type_limits_int { public: Type_limits_sint24() - :Type_limits_int(MAX_MEDIUMINT_WIDTH - 1, MAX_MEDIUMINT_WIDTH) + :Type_limits_int(MAX_MEDIUMINT_WIDTH - 1, MAX_MEDIUMINT_WIDTH, range24()) { } }; @@ -5164,7 +5281,7 @@ class Type_limits_uint32: public Type_limits_int { public: Type_limits_uint32() - :Type_limits_int(MAX_INT_WIDTH, MAX_INT_WIDTH) + :Type_limits_int(MAX_INT_WIDTH, MAX_INT_WIDTH, range32()) { } }; @@ -5174,7 +5291,7 @@ class Type_limits_sint32: public Type_limits_int { public: Type_limits_sint32() - :Type_limits_int(MAX_INT_WIDTH, MAX_INT_WIDTH + 1) + :Type_limits_int(MAX_INT_WIDTH, MAX_INT_WIDTH + 1, range32()) { } }; @@ -5186,7 +5303,8 @@ public: class Type_limits_uint64: public Type_limits_int { public: - Type_limits_uint64(): Type_limits_int(MAX_BIGINT_WIDTH, MAX_BIGINT_WIDTH) + Type_limits_uint64() + :Type_limits_int(MAX_BIGINT_WIDTH, MAX_BIGINT_WIDTH, range64()) { } }; @@ -5195,7 +5313,7 @@ class Type_limits_sint64: public Type_limits_int { public: Type_limits_sint64() - :Type_limits_int(MAX_BIGINT_WIDTH - 1, MAX_BIGINT_WIDTH) + :Type_limits_int(MAX_BIGINT_WIDTH - 1, MAX_BIGINT_WIDTH, range64()) { } }; @@ -5817,6 +5935,7 @@ public: Item_cache *Item_get_cache(THD *thd, const Item *item) const override; int Item_save_in_field(Item *item, Field *field, bool no_conversions) const override; + String *print_item_value(THD *thd, Item *item, String *str) const override; }; @@ -7615,7 +7734,7 @@ class Named_type_handler : public TypeHandler { Type_handler::set_name(Name(n, static_cast(strlen(n)))); } }; -extern Named_type_handler type_handler_row; +extern const Type_handler_composite &type_handler_row; extern Named_type_handler type_handler_null; extern Named_type_handler type_handler_float; diff --git a/sql/sql_type_composite.cc b/sql/sql_type_composite.cc new file mode 100644 index 00000000000..34c45d13c1f --- /dev/null +++ b/sql/sql_type_composite.cc @@ -0,0 +1,211 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#include "sql_type.h" +#include "sql_type_composite.h" +#include "item.h" +#include "item_cmpfunc.h" +#include "sp_head.h" + + +const Name & Type_handler_composite::default_value() const +{ + DBUG_ASSERT(0); + static Name def(STRING_WITH_LEN("")); + return def; +} + + +bool Type_handler_composite:: + sp_variable_declarations_row_finalize(THD *thd, LEX *lex, int nvars, + Row_definition_list *row) +{ + DBUG_ASSERT(row); + /* + Prepare all row fields. + Note, we do it only one time outside of the below loop. + The converted list in "row" is further reused by all variable + declarations processed by the current call. + Example: + DECLARE + a, b, c ROW(x VARCHAR(10) CHARACTER SET utf8); + BEGIN + ... + END; + */ + if (lex->sphead->row_fill_field_definitions(thd, row)) + return true; + + for (uint i= 0 ; i < (uint) nvars ; i++) + { + uint offset= (uint) nvars - 1 - i; + sp_variable *spvar= lex->spcont->get_last_context_variable(offset); + spvar->field_def.set_row_field_definitions(&type_handler_row, row); + if (lex->sphead->fill_spvar_definition(thd, &spvar->field_def, + &spvar->name)) + return true; + } + return false; +} + + +bool Type_handler_composite:: + Column_definition_prepare_stage1(THD *thd, + MEM_ROOT *mem_root, + Column_definition *def, + column_definition_type_t type, + const Column_derived_attributes + *derived_attr) + const +{ + def->charset= &my_charset_bin; + def->create_length_to_internal_length_null(); + return false; +} + + +bool Type_handler_composite::Item_eq_value(THD *thd, + const Type_cmp_attributes *attr, + Item *a, Item *b) const +{ + DBUG_ASSERT(0); + return false; +} + + +bool Type_handler_composite:: + Item_save_in_value(THD *thd, Item *item, st_value *value) const +{ + DBUG_ASSERT(0); + value->m_type= DYN_COL_NULL; + return true; +} + + +bool Type_handler_composite:: + Item_param_set_from_value(THD *thd, + Item_param *param, + const Type_all_attributes *attr, + const st_value *val) const +{ + DBUG_ASSERT(0); + param->set_null(); + return true; +} + + +void Type_handler_composite::Item_update_null_value(Item *item) const +{ + DBUG_ASSERT(0); + item->null_value= true; +} + + +longlong Type_handler_composite:: + Item_func_between_val_int(Item_func_between *func) const +{ + DBUG_ASSERT(0); + func->null_value= true; + return 0; +} + + +bool Type_handler_composite:: + Item_func_round_fix_length_and_dec(Item_func_round *item) const +{ + DBUG_ASSERT(0); + return false; +} + + +bool Type_handler_composite:: + Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) const +{ + DBUG_ASSERT(0); + return false; +} + + +bool Type_handler_composite:: + Item_func_abs_fix_length_and_dec(Item_func_abs *item) const +{ + DBUG_ASSERT(0); + return false; +} + + +bool Type_handler_composite:: + Item_func_neg_fix_length_and_dec(Item_func_neg *item) const +{ + DBUG_ASSERT(0); + return false; +} + + +bool Type_handler_composite:: + Item_func_plus_fix_length_and_dec(Item_func_plus *item) const +{ + DBUG_ASSERT(0); + return true; +} + + +bool Type_handler_composite:: + Item_func_minus_fix_length_and_dec(Item_func_minus *item) const +{ + DBUG_ASSERT(0); + return true; +} + + +bool Type_handler_composite:: + Item_func_mul_fix_length_and_dec(Item_func_mul *item) const +{ + DBUG_ASSERT(0); + return true; +} + + +bool Type_handler_composite:: + Item_func_div_fix_length_and_dec(Item_func_div *item) const +{ + DBUG_ASSERT(0); + return true; +} + + +bool Type_handler_composite:: + Item_func_mod_fix_length_and_dec(Item_func_mod *item) const +{ + DBUG_ASSERT(0); + return true; +} + + +bool Type_handler_composite:: + Item_hybrid_func_fix_attributes(THD *thd, + const LEX_CSTRING &opname, + Type_handler_hybrid_field_type *, + Type_all_attributes *atrr, + Item **items, uint nitems) + const +{ + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + name().ptr(), opname.str); + return true; +} diff --git a/sql/sql_type_composite.h b/sql/sql_type_composite.h new file mode 100644 index 00000000000..952e36c7398 --- /dev/null +++ b/sql/sql_type_composite.h @@ -0,0 +1,425 @@ +/* + Copyright (c) 2025, Rakuten Securities + Copyright (c) 2025, MariaDB plc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + 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 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-1335 USA +*/ +#ifndef SQL_TYPE_COMPOSITE_INCLUDED +#define SQL_TYPE_COMPOSITE_INCLUDED + +#include "sql_type.h" + +class Item_splocal; +class Field_composite; +class Item_composite; +class Item_field; +class Row_definition_list; + +class Type_handler_composite: public Type_handler +{ +public: + static bool sp_variable_declarations_row_finalize(THD *thd, LEX *lex, + int nvars, + Row_definition_list *row); +public: + virtual ~Type_handler_composite() = default; + const Name &default_value() const override; + bool validate_implicit_default_value(THD *, const Column_definition &) + const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + const Type_handler_composite *to_composite() const override + { + return this; + } + bool is_scalar_type() const override { return false; } + bool can_return_int() const override { return false; } + bool can_return_decimal() const override { return false; } + bool can_return_real() const override { return false; } + bool can_return_str() const override { return false; } + bool can_return_text() const override { return false; } + bool can_return_date() const override { return false; } + bool can_return_time() const override { return false; } + enum_field_types field_type() const override + { + MY_ASSERT_UNREACHABLE(); + return MYSQL_TYPE_NULL; + }; + protocol_send_type_t protocol_send_type() const override + { + MY_ASSERT_UNREACHABLE(); + return PROTOCOL_SEND_STRING; + } + Item_result result_type() const override + { + return ROW_RESULT; + } + Item_result cmp_type() const override + { + return ROW_RESULT; + } + enum_dynamic_column_type dyncol_type(const Type_all_attributes *) + const override + { + MY_ASSERT_UNREACHABLE(); + return DYN_COL_NULL; + } + int stored_field_cmp_to_item(THD *, Field *, Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + bool subquery_type_allows_materialization(const Item *, const Item *, bool) + const override + { + MY_ASSERT_UNREACHABLE(); + return false; + } + Field *make_num_distinct_aggregator_field(MEM_ROOT *, const Item *) const + override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + Field *make_conversion_table_field(MEM_ROOT *, TABLE *, uint, const Field *) + const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + bool Column_definition_fix_attributes(Column_definition *) const override + { + return false; + } + void Column_definition_reuse_fix_attributes(THD *, Column_definition *, + const Field *) const override + { + MY_ASSERT_UNREACHABLE(); + } + bool Column_definition_prepare_stage1(THD *thd, + MEM_ROOT *mem_root, + Column_definition *c, + column_definition_type_t type, + const Column_derived_attributes + *derived_attr) + const override; + bool Column_definition_redefine_stage1(Column_definition *, + const Column_definition *, + const handler *) + const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Column_definition_prepare_stage2(Column_definition *, handler *, + ulonglong) const override + { + return false; + } + Field *make_table_field(MEM_ROOT *, const LEX_CSTRING *, const Record_addr &, + const Type_all_attributes &, TABLE_SHARE *) + const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + void make_sort_key_part(uchar *to, Item *item, + const SORT_FIELD_ATTR *sort_field, + String *tmp) const override + { + MY_ASSERT_UNREACHABLE(); + } + uint make_packed_sort_key_part(uchar *, Item *, const SORT_FIELD_ATTR *, + String *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + void sort_length(THD *, const Type_std_attributes *, SORT_FIELD_ATTR *) + const override + { + MY_ASSERT_UNREACHABLE(); + } + uint32 max_display_length(const Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + uint32 max_display_length_for_field(const Conv_source &) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + uint32 calc_pack_length(uint32) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + bool Item_eq_value(THD *thd, const Type_cmp_attributes *attr, + Item *a, Item *b) const override; + decimal_digits_t Item_decimal_precision(const Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return DECIMAL_MAX_PRECISION; + } + bool Item_save_in_value(THD *thd, Item *item, st_value *value) const + override; + bool Item_param_set_from_value(THD *thd, + Item_param *param, + const Type_all_attributes *attr, + const st_value *value) const override; + bool Item_send(Item *, Protocol *, st_value *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + void Item_update_null_value(Item *item) const override; + int Item_save_in_field(Item *, Field *, bool) const override + { + MY_ASSERT_UNREACHABLE(); + return 1; + } + bool can_change_cond_ref_to_const(Item_bool_func2 *, Item *, Item *, + Item_bool_func2 *, Item *, Item *) + const override + { + MY_ASSERT_UNREACHABLE(); + return false; + } + Item_copy *create_item_copy(THD *, Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + bool Item_hybrid_func_fix_attributes(THD *thd, + const LEX_CSTRING &name, + Type_handler_hybrid_field_type *, + Type_all_attributes *atrr, + Item **items, uint nitems) + const override; + bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_sum_sum_fix_length_and_dec(Item_sum_sum *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_sum_avg_fix_length_and_dec(Item_sum_avg *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_sum_variance_fix_length_and_dec(Item_sum_variance *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_val_bool(Item *item) const override + { + MY_ASSERT_UNREACHABLE(); + return false; + } + void Item_get_date(THD *, Item *, Temporal::Warn *, MYSQL_TIME *ltime, + date_mode_t) const override + { + MY_ASSERT_UNREACHABLE(); + set_zero_time(ltime, MYSQL_TIMESTAMP_NONE); + } + longlong Item_val_int_signed_typecast(Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + longlong Item_val_int_unsigned_typecast(Item *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + String *Item_func_hex_val_str_ascii(Item_func_hex *, String *) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + String *Item_func_hybrid_field_type_val_str(Item_func_hybrid_field_type *, + String *) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + double Item_func_hybrid_field_type_val_real(Item_func_hybrid_field_type *) + const override + { + MY_ASSERT_UNREACHABLE(); + return 0.0; + } + longlong Item_func_hybrid_field_type_val_int(Item_func_hybrid_field_type *) + const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + my_decimal *Item_func_hybrid_field_type_val_decimal( + Item_func_hybrid_field_type *, + my_decimal *) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + void Item_func_hybrid_field_type_get_date(THD *, + Item_func_hybrid_field_type *, + Temporal::Warn *, + MYSQL_TIME *ltime, + date_mode_t) const override + { + MY_ASSERT_UNREACHABLE(); + set_zero_time(ltime, MYSQL_TIMESTAMP_NONE); + } + + String *Item_func_min_max_val_str(Item_func_min_max *, String *) const + override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + double Item_func_min_max_val_real(Item_func_min_max *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + longlong Item_func_min_max_val_int(Item_func_min_max *) const override + { + MY_ASSERT_UNREACHABLE(); + return 0; + } + my_decimal *Item_func_min_max_val_decimal(Item_func_min_max *, + my_decimal *) const override + { + MY_ASSERT_UNREACHABLE(); + return nullptr; + } + bool Item_func_min_max_get_date(THD *, Item_func_min_max*, MYSQL_TIME *, + date_mode_t) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_func_between_fix_length_and_dec(Item_func_between *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + longlong Item_func_between_val_int(Item_func_between *func) const override; + bool Item_func_round_fix_length_and_dec(Item_func_round *) const override; + bool Item_func_int_val_fix_length_and_dec(Item_func_int_val *) const + override; + bool Item_func_abs_fix_length_and_dec(Item_func_abs *) const override; + bool Item_func_neg_fix_length_and_dec(Item_func_neg *) const override; + + bool Item_func_signed_fix_length_and_dec(Item_func_signed *) const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_double_typecast_fix_length_and_dec(Item_double_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_float_typecast_fix_length_and_dec(Item_float_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_decimal_typecast_fix_length_and_dec(Item_decimal_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_char_typecast_fix_length_and_dec(Item_char_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_time_typecast_fix_length_and_dec(Item_time_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_date_typecast_fix_length_and_dec(Item_date_typecast *) const + override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + bool Item_datetime_typecast_fix_length_and_dec(Item_datetime_typecast *) + const override + { + MY_ASSERT_UNREACHABLE(); + return true; + } + + bool Item_func_plus_fix_length_and_dec(Item_func_plus *) const override; + bool Item_func_minus_fix_length_and_dec(Item_func_minus *) const override; + bool Item_func_mul_fix_length_and_dec(Item_func_mul *) const override; + bool Item_func_div_fix_length_and_dec(Item_func_div *) const override; + bool Item_func_mod_fix_length_and_dec(Item_func_mod *) const override; + + virtual bool key_to_lex_cstring(THD *thd, Item **key, + const LEX_CSTRING& name, + LEX_CSTRING& out_key) const + { + return false; + } + + /* + Get the index of the item with the given name in the composite item. + + This is only implemented for composite items that have a fixed number of + fields, such as ROWs. + */ + virtual bool get_item_index(THD *thd, const Item_field *item, + const LEX_CSTRING& name, uint& idx) const = 0; + virtual Item_field *get_item(THD *thd, const Item_field *item, + const LEX_CSTRING& name) const = 0; + virtual Item_field *get_or_create_item(THD *thd, Item_field *item, + const LEX_CSTRING& name) const = 0; + + virtual void prepare_for_set(Item_field *item) const + { + return; + } + virtual bool finalize_for_set(Item_field *item) const + { + return false; + } +}; + +#endif /* SQL_TYPE_COMPOSITE_INCLUDED */ diff --git a/sql/sql_type_row.cc b/sql/sql_type_row.cc index 72ca930f879..c372c467392 100644 --- a/sql/sql_type_row.cc +++ b/sql/sql_type_row.cc @@ -23,39 +23,60 @@ #include "field.h" #include "sp_rcontext.h" #include "sp_type_def.h" -#include "sp_head.h" -bool Type_handler_row:: - sp_variable_declarations_row_finalize(THD *thd, LEX *lex, int nvars, - Row_definition_list *row) +class Type_collection_row: public Type_collection { - DBUG_ASSERT(row); - /* - Prepare all row fields. - Note, we do it only one time outside of the below loop. - The converted list in "row" is further reused by all variable - declarations processed by the current call. - Example: - DECLARE - a, b, c ROW(x VARCHAR(10) CHARACTER SET utf8); - BEGIN - ... - END; - */ - if (lex->sphead->row_fill_field_definitions(thd, row)) - return true; - - for (uint i= 0 ; i < (uint) nvars ; i++) +public: + bool init(Type_handler_data *data) override { - uint offset= (uint) nvars - 1 - i; - sp_variable *spvar= lex->spcont->get_last_context_variable(offset); - spvar->field_def.set_row_field_definitions(row); - if (lex->sphead->fill_spvar_definition(thd, &spvar->field_def, - &spvar->name)) - return true; + return false; } - return false; + const Type_handler *aggregate_for_result(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } + const Type_handler *aggregate_for_comparison(const Type_handler *a, + const Type_handler *b) + const override + { + /* + Allowed combinations: + ROW+ROW, NULL+ROW, ROW+NULL + */ + DBUG_ASSERT(a == &type_handler_row || a == &type_handler_null); + DBUG_ASSERT(b == &type_handler_row || b == &type_handler_null); + DBUG_ASSERT(a == &type_handler_row || b == &type_handler_row); + return &type_handler_row; + } + const Type_handler *aggregate_for_min_max(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } + const Type_handler *aggregate_for_num_op(const Type_handler *a, + const Type_handler *b) + const override + { + return NULL; + } +}; + + +static Type_collection_row type_collection_row; + +const Type_collection *Type_handler_row::type_collection() const +{ + return &type_collection_row; +} + + +const Type_handler *Type_handler_row::type_handler_for_comparison() const +{ + return &type_handler_row; } @@ -170,6 +191,23 @@ Item_field *Field_row::make_item_field_spvar(THD *thd, } +bool Type_handler_row::Spvar_definition_with_complex_data_types( + Spvar_definition *def) const +{ + if (def->row_field_definitions() && def->is_row()) + { + List_iterator it(*(def->row_field_definitions())); + Spvar_definition *member; + while ((member= it++)) + { + if (member->type_handler()->is_complex()) + return true; + } + } + return false; +} + + bool Type_handler_row:: sp_variable_declarations_finalize(THD *thd, LEX *lex, int nvars, @@ -188,10 +226,114 @@ Type_handler_row:: // TYPE row_t IS RECORD Row_definition_list *row= rec->field->deep_copy(thd); return row == nullptr || - Type_handler_row::sp_variable_declarations_row_finalize(thd, - lex, - nvars, - row); + Type_handler_composite::sp_variable_declarations_row_finalize(thd, + lex, + nvars, + row); +} + + +bool Type_handler_row:: +set_comparator_func(THD *thd, Arg_comparator *cmp) const +{ + return cmp->set_cmp_func_row(thd); +} + +Item_cache * +Type_handler_row::Item_get_cache(THD *thd, const Item *item) const +{ + return new (thd->mem_root) Item_cache_row(thd); +} + +cmp_item *Type_handler_row::make_cmp_item(THD *thd, + CHARSET_INFO *cs) const +{ + return new (thd->mem_root) cmp_item_row; +} + +in_vector *Type_handler_row::make_in_vector(THD *thd, + const Item_func_in *func, + uint nargs) const +{ + return new (thd->mem_root) in_row(thd, nargs, 0); +} + +bool Type_handler_row::Item_func_in_fix_comparator_compatible_types(THD *thd, + Item_func_in *func) const +{ + return func->compatible_types_row_bisection_possible() ? + func->fix_for_row_comparison_using_bisection(thd) : + func->fix_for_row_comparison_using_cmp_items(thd); +} + +/** + Get a string representation of the Item value. + See sql_type.h for details. +*/ +String *Type_handler_row:: + print_item_value(THD *thd, Item *item, String *str) const +{ + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer val(cs); + str->append(STRING_WITH_LEN("ROW(")); + for (uint i= 0 ; i < item->cols(); i++) + { + if (i > 0) + str->append(','); + Item *elem= item->element_index(i); + String *tmp= elem->type_handler()->print_item_value(thd, elem, &val); + if (tmp) + str->append(*tmp); + else + str->append(NULL_clex_str); + } + str->append(')'); + return str; +} + + +Item *Type_handler_row:: + make_const_item_for_comparison(THD *thd, Item *item, const Item *cmp) const +{ + if (item->type() == Item::ROW_ITEM && cmp->type() == Item::ROW_ITEM) + { + /* + Substitute constants only in Item_row's. Don't affect other Items + with ROW_RESULT (eg Item_singlerow_subselect). + + For such Items more optimal is to detect if it is constant and replace + it with Item_row. This would optimize queries like this: + SELECT * FROM t1 WHERE (a,b) = (SELECT a,b FROM t2 LIMIT 1); + */ + Item_row *item_row= (Item_row*) item; + Item_row *comp_item_row= (Item_row*) cmp; + uint col; + /* + If item and comp_item are both Item_row's and have same number of cols + then process items in Item_row one by one. + We can't ignore NULL values here as this item may be used with <=>, in + which case NULL's are significant. + */ + DBUG_ASSERT(item->result_type() == cmp->result_type()); + DBUG_ASSERT(item_row->cols() == comp_item_row->cols()); + col= item_row->cols(); + while (col-- > 0) + resolve_const_item(thd, item_row->addr(col), + comp_item_row->element_index(col)); + } + return NULL; +} + +Field *Type_handler_row:: + make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, + const LEX_CSTRING *name, + const Record_addr &rec, const Bit_addr &bit, + const Column_definition_attributes *attr, + uint32 flags) const +{ + DBUG_ASSERT(attr->length == 0); + DBUG_ASSERT(f_maybe_null(attr->pack_flag)); + return new (mem_root) Field_row(rec.ptr(), name); } @@ -209,15 +351,40 @@ Item *Type_handler_row::make_typedef_constructor_item(THD *thd, } -bool Type_handler_row:: - Item_hybrid_func_fix_attributes(THD *thd, - const LEX_CSTRING &opname, - Type_handler_hybrid_field_type *, - Type_all_attributes *atrr, - Item **items, uint nitems) - const +bool Type_handler_row::get_item_index(THD *thd, + const Item_field *item, + const LEX_CSTRING& name, + uint& idx) const { - my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), - name().ptr(), opname.str); - return true; + auto item_row= + dynamic_cast(const_cast (item)); + DBUG_ASSERT(item_row); + + auto vtable= item_row->field->virtual_tmp_table(); + if (!vtable) + return true; + + return vtable->sp_find_field_by_name_or_error(&idx, + item_row->field->field_name, + name); } + + +Item_field *Type_handler_row::get_item(THD *thd, + const Item_field *item, + const LEX_CSTRING& name) const +{ + auto item_row= + dynamic_cast(const_cast (item)); + DBUG_ASSERT(item_row); + + uint field_idx; + if (get_item_index(thd, item_row, name, field_idx)) + return nullptr; + + return item_row->element_index(field_idx)->field_for_view_update(); +} + +Named_type_handler type_handler_row_internal("row"); +const Type_handler_composite &type_handler_row= + type_handler_row_internal; diff --git a/sql/sql_type_row.h b/sql/sql_type_row.h index 555232533b5..c249a95de29 100644 --- a/sql/sql_type_row.h +++ b/sql/sql_type_row.h @@ -20,127 +20,26 @@ #define SQL_TYPE_ROW_INCLUDED +#include "sql_type_composite.h" + class Row_definition_list; /* Special handler for ROW */ -class Type_handler_row: public Type_handler +class Type_handler_row: public Type_handler_composite { -public: - static bool sp_variable_declarations_row_finalize(THD *thd, LEX *lex, - int nvars, - Row_definition_list *row); public: virtual ~Type_handler_row() = default; - const Name &default_value() const override; - bool validate_implicit_default_value(THD *, const Column_definition &) - const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } const Type_collection *type_collection() const override; - bool is_scalar_type() const override { return false; } - bool can_return_int() const override { return false; } - bool can_return_decimal() const override { return false; } - bool can_return_real() const override { return false; } - bool can_return_str() const override { return false; } - bool can_return_text() const override { return false; } - bool can_return_date() const override { return false; } - bool can_return_time() const override { return false; } - enum_field_types field_type() const override - { - MY_ASSERT_UNREACHABLE(); - return MYSQL_TYPE_NULL; - }; - protocol_send_type_t protocol_send_type() const override - { - MY_ASSERT_UNREACHABLE(); - return PROTOCOL_SEND_STRING; - } - Item_result result_type() const override - { - return ROW_RESULT; - } - Item_result cmp_type() const override - { - return ROW_RESULT; - } - enum_dynamic_column_type dyncol_type(const Type_all_attributes *) - const override - { - MY_ASSERT_UNREACHABLE(); - return DYN_COL_NULL; - } const Type_handler *type_handler_for_comparison() const override; bool has_null_predicate() const override { return false; } - int stored_field_cmp_to_item(THD *, Field *, Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - bool subquery_type_allows_materialization(const Item *, const Item *, bool) - const override - { - MY_ASSERT_UNREACHABLE(); - return false; - } - Field *make_num_distinct_aggregator_field(MEM_ROOT *, const Item *) const - override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - Field *make_conversion_table_field(MEM_ROOT *, TABLE *, uint, const Field *) - const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - bool Column_definition_fix_attributes(Column_definition *) const override - { - return false; - } - void Column_definition_reuse_fix_attributes(THD *, Column_definition *, - const Field *) const override - { - MY_ASSERT_UNREACHABLE(); - } - bool Column_definition_prepare_stage1(THD *thd, - MEM_ROOT *mem_root, - Column_definition *c, - column_definition_type_t type, - const Column_derived_attributes - *derived_attr) - const override; - bool Column_definition_redefine_stage1(Column_definition *, - const Column_definition *, - const handler *) - const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Column_definition_prepare_stage2(Column_definition *, handler *, - ulonglong) const override - { - return false; - } bool Spvar_definition_with_complex_data_types(Spvar_definition *def) const override; bool sp_variable_declarations_finalize(THD *thd, LEX *lex, int nvars, const Column_definition &def) const override; - - Field *make_table_field(MEM_ROOT *, const LEX_CSTRING *, const Record_addr &, - const Type_all_attributes &, TABLE_SHARE *) - const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } Field *make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, const LEX_CSTRING *name, @@ -148,62 +47,6 @@ public: const Bit_addr &bit, const Column_definition_attributes *attr, uint32 flags) const override; - void make_sort_key_part(uchar *to, Item *item, - const SORT_FIELD_ATTR *sort_field, - String *tmp) const override - { - MY_ASSERT_UNREACHABLE(); - } - uint make_packed_sort_key_part(uchar *, Item *, const SORT_FIELD_ATTR *, - String *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - void sort_length(THD *, const Type_std_attributes *, SORT_FIELD_ATTR *) - const override - { - MY_ASSERT_UNREACHABLE(); - } - uint32 max_display_length(const Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - uint32 max_display_length_for_field(const Conv_source &) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - uint32 calc_pack_length(uint32) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - bool Item_eq_value(THD *thd, const Type_cmp_attributes *attr, - Item *a, Item *b) const override; - decimal_digits_t Item_decimal_precision(const Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return DECIMAL_MAX_PRECISION; - } - bool Item_save_in_value(THD *thd, Item *item, st_value *value) const - override; - bool Item_param_set_from_value(THD *thd, - Item_param *param, - const Type_all_attributes *attr, - const st_value *value) const override; - bool Item_send(Item *, Protocol *, st_value *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - void Item_update_null_value(Item *item) const override; - int Item_save_in_field(Item *, Field *, bool) const override - { - MY_ASSERT_UNREACHABLE(); - return 1; - } // SELECT 1,2,3 INTO spvar_row; my_var *make_outvar(THD *thd, const Lex_ident_sys_st &name, @@ -218,216 +61,31 @@ public: sp_head *sphead, bool validate_only) const override; String *print_item_value(THD *thd, Item *item, String *str) const override; - bool can_change_cond_ref_to_const(Item_bool_func2 *, Item *, Item *, - Item_bool_func2 *, Item *, Item *) - const override - { - MY_ASSERT_UNREACHABLE(); - return false; - } Item *make_const_item_for_comparison(THD *, Item *src, const Item *cmp) const override; Item *make_typedef_constructor_item(THD *thd, const sp_type_def &def, List *arg_list) const override; Item_cache *Item_get_cache(THD *thd, const Item *item) const override; - Item_copy *create_item_copy(THD *, Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } bool set_comparator_func(THD *thd, Arg_comparator *cmp) const override; - bool Item_hybrid_func_fix_attributes(THD *thd, - const LEX_CSTRING &name, - Type_handler_hybrid_field_type *, - Type_all_attributes *atrr, - Item **items, uint nitems) - const override; - bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_sum_sum_fix_length_and_dec(Item_sum_sum *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_sum_avg_fix_length_and_dec(Item_sum_avg *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_sum_variance_fix_length_and_dec(Item_sum_variance *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_val_bool(Item *item) const override - { - MY_ASSERT_UNREACHABLE(); - return false; - } - void Item_get_date(THD *, Item *, Temporal::Warn *, MYSQL_TIME *ltime, - date_mode_t) const override - { - MY_ASSERT_UNREACHABLE(); - set_zero_time(ltime, MYSQL_TIMESTAMP_NONE); - } - longlong Item_val_int_signed_typecast(Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - longlong Item_val_int_unsigned_typecast(Item *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - String *Item_func_hex_val_str_ascii(Item_func_hex *, String *) const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - String *Item_func_hybrid_field_type_val_str(Item_func_hybrid_field_type *, - String *) const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - double Item_func_hybrid_field_type_val_real(Item_func_hybrid_field_type *) - const override - { - MY_ASSERT_UNREACHABLE(); - return 0.0; - } - longlong Item_func_hybrid_field_type_val_int(Item_func_hybrid_field_type *) - const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - my_decimal *Item_func_hybrid_field_type_val_decimal( - Item_func_hybrid_field_type *, - my_decimal *) const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - void Item_func_hybrid_field_type_get_date(THD *, - Item_func_hybrid_field_type *, - Temporal::Warn *, - MYSQL_TIME *ltime, - date_mode_t) const override - { - MY_ASSERT_UNREACHABLE(); - set_zero_time(ltime, MYSQL_TIMESTAMP_NONE); - } - - String *Item_func_min_max_val_str(Item_func_min_max *, String *) const - override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - double Item_func_min_max_val_real(Item_func_min_max *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - longlong Item_func_min_max_val_int(Item_func_min_max *) const override - { - MY_ASSERT_UNREACHABLE(); - return 0; - } - my_decimal *Item_func_min_max_val_decimal(Item_func_min_max *, - my_decimal *) const override - { - MY_ASSERT_UNREACHABLE(); - return nullptr; - } - bool Item_func_min_max_get_date(THD *, Item_func_min_max*, MYSQL_TIME *, - date_mode_t) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_func_between_fix_length_and_dec(Item_func_between *) const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - longlong Item_func_between_val_int(Item_func_between *func) const override; cmp_item *make_cmp_item(THD *thd, CHARSET_INFO *cs) const override; in_vector *make_in_vector(THD *thd, const Item_func_in *f, uint nargs) const override; bool Item_func_in_fix_comparator_compatible_types(THD *thd, Item_func_in *) const override; - bool Item_func_round_fix_length_and_dec(Item_func_round *) const override; - bool Item_func_int_val_fix_length_and_dec(Item_func_int_val *) const - override; - bool Item_func_abs_fix_length_and_dec(Item_func_abs *) const override; - bool Item_func_neg_fix_length_and_dec(Item_func_neg *) const override; - - bool Item_func_signed_fix_length_and_dec(Item_func_signed *) const override + bool get_item_index(THD *thd, + const Item_field *item, + const LEX_CSTRING& name, + uint& idx) const override; + Item_field *get_item(THD *thd, + const Item_field *item, + const LEX_CSTRING& name) const override; + Item_field *get_or_create_item(THD *thd, + Item_field *item, + const LEX_CSTRING& name) const override { - MY_ASSERT_UNREACHABLE(); - return true; + return get_item(thd, const_cast(item), name); } - bool Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_double_typecast_fix_length_and_dec(Item_double_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_float_typecast_fix_length_and_dec(Item_float_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_decimal_typecast_fix_length_and_dec(Item_decimal_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_char_typecast_fix_length_and_dec(Item_char_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_time_typecast_fix_length_and_dec(Item_time_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_date_typecast_fix_length_and_dec(Item_date_typecast *) const - override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - bool Item_datetime_typecast_fix_length_and_dec(Item_datetime_typecast *) - const override - { - MY_ASSERT_UNREACHABLE(); - return true; - } - - bool Item_func_plus_fix_length_and_dec(Item_func_plus *) const override; - bool Item_func_minus_fix_length_and_dec(Item_func_minus *) const override; - bool Item_func_mul_fix_length_and_dec(Item_func_mul *) const override; - bool Item_func_div_fix_length_and_dec(Item_func_div *) const override; - bool Item_func_mod_fix_length_and_dec(Item_func_mod *) const override; }; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6843e2b018c..4e5b2632669 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -280,6 +280,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() Table_ident *table; Qualified_column_ident *qualified_column_ident; Optimizer_hint_parser_output *opt_hints; + Qualified_ident *qualified_ident; char *simple_string; const char *const_simple_string; chooser_compare_func_creator boolfunc2creator; @@ -447,6 +448,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token SET_VAR /* OPERATOR */ %token SHIFT_LEFT /* OPERATOR */ %token SHIFT_RIGHT /* OPERATOR */ +%token ARROW_SYM /* OPERATOR */ /* @@ -1338,6 +1340,11 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); sp_block_label sp_control_label opt_place opt_db udt_name +%ifdef ORACLE +%type + assoc_name +%endif + %type IDENT_sys ident @@ -1400,6 +1407,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type optionally_qualified_column_ident +%ifdef ORACLE +%type + optionally_qualified_directly_assignable + direct_call_or_lvalue_assign +%endif + %type remember_name remember_end remember_tok_start @@ -1424,7 +1437,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type json_on_response %type field_type field_type_all field_type_all_builtin - field_type_all_with_record + field_type_all_with_composites qualified_field_type field_type_numeric field_type_string @@ -1433,6 +1446,15 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); field_type_misc json_table_field_type +%ifdef ORACLE +%type assoc_array_table_types +%type assoc_array_index_type +%type field_type_all_with_record assoc_array_index_types +%endif + +%type + opt_object_member_access + %type binary opt_binary @@ -1576,6 +1598,20 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); ident_list ident_list_arg opt_expr_list execute_using execute_params + opt_sp_cparam_list + opt_sp_cparams + sp_cparams + +%ifdef ORACLE +%type + parenthesized_opt_sp_cparams + opt_parenthesized_opt_sp_cparams +%endif + +%ifdef ORACLE +%type named_expr +%type named_expr_list +%endif %type sp_cursor_stmt_lex @@ -1990,6 +2026,7 @@ rule: %type reserved_keyword_udt_param_type %else %type set_assign +%type set_assign_lvalue_function %type sp_opt_inout %type sp_tail_standalone %type sp_labelable_stmt @@ -2010,6 +2047,7 @@ rule: %type sp_block_statements_and_exceptions %type sp_instr_addr %type opt_exception_clause exception_handlers +%type typed_ident %endif ORACLE %% @@ -3349,27 +3387,43 @@ call: /* CALL parameters */ opt_sp_cparam_list: - /* Empty */ + /* Empty */ { $$= nullptr; } | '(' { thd->where= THD_WHERE::USE_WHERE_STRING; thd->where_str= "CALL"; } opt_sp_cparams ')' + { + $$= $3; + } ; + +%ifdef ORACLE +opt_parenthesized_opt_sp_cparams: + /* Empty */ { $$= nullptr; } + | parenthesized_opt_sp_cparams { $$= $1; } + ; + +parenthesized_opt_sp_cparams: + '(' opt_sp_cparams ')' { $$= $2; } + ; +%endif + + opt_sp_cparams: - /* Empty */ - | sp_cparams + /* Empty */ { $$= nullptr; } + | sp_cparams { $$= $1; } ; sp_cparams: sp_cparams ',' expr { - Lex->value_list.push_back($3, thd->mem_root); + ($$= $1)->push_back($3, thd->mem_root); } | expr { - Lex->value_list.push_back($1, thd->mem_root); + ($$= &Lex->value_list)->push_back($1, thd->mem_root); } ; @@ -3501,8 +3555,9 @@ optionally_qualified_column_ident: row_field_definition: row_field_name field_type { - Lex->last_field->set_attributes(thd, $2, - COLUMN_DEFINITION_ROUTINE_LOCAL); + if (Lex->last_field->set_attributes(thd, $2, + COLUMN_DEFINITION_ROUTINE_LOCAL)) + MYSQL_YYABORT; } ; @@ -3527,8 +3582,9 @@ row_type_body: rec_field_definition: row_field_name field_type { - Lex->last_field->set_attributes(thd, $2, - COLUMN_DEFINITION_ROUTINE_LOCAL); + if (Lex->last_field->set_attributes(thd, $2, + COLUMN_DEFINITION_ROUTINE_LOCAL)) + MYSQL_YYABORT; } | rec_field_definition_anchored ; @@ -3577,10 +3633,11 @@ sp_decl_idents_init_vars: sp_decl_variable_list: sp_decl_idents_init_vars - field_type_all_with_record + field_type_all_with_composites { - Lex->last_field->set_attributes(thd, $2, - COLUMN_DEFINITION_ROUTINE_LOCAL); + if (Lex->last_field->set_attributes(thd, $2, + COLUMN_DEFINITION_ROUTINE_LOCAL)) + MYSQL_YYABORT; } sp_opt_default { @@ -6247,8 +6304,9 @@ field_spec: field_type_or_serial: qualified_field_type { - Lex->last_field->set_attributes(thd, $1, - COLUMN_DEFINITION_TABLE_FIELD); + if (Lex->last_field->set_attributes(thd, $1, + COLUMN_DEFINITION_TABLE_FIELD)) + MYSQL_YYABORT; } field_def { @@ -6475,6 +6533,7 @@ field_type_all: } ; +%ifdef ORACLE field_type_all_with_record: field_type_all_builtin { @@ -6482,19 +6541,36 @@ field_type_all_with_record: } | udt_name float_options srid_option { - sp_type_def *sprec = NULL; - if (Lex->spcont) - sprec = Lex->spcont->find_type_def($1, false); + bool is_composite= false; + if (unlikely(Lex->set_field_type_composite(&$$, $1, false, + &is_composite))) + MYSQL_YYABORT; - if (sprec == NULL) + if (!is_composite) { if (Lex->set_field_type_udt(&$$, $1, $2)) MYSQL_YYABORT; } - else + } + ; +%endif + +field_type_all_with_composites: + field_type_all_builtin + { + Lex->map_data_type(Lex_ident_sys(), &($$= $1)); + } + | udt_name float_options srid_option + { + bool is_composite= false; + if (unlikely(Lex->set_field_type_composite(&$$, $1, true, + &is_composite))) + MYSQL_YYABORT; + + if (!is_composite) { - $$.set(&type_handler_row, NULL); - Lex->last_field->set_attr_const_void_ptr(0, sprec); + if (Lex->set_field_type_udt(&$$, $1, $2)) + MYSQL_YYABORT; } } ; @@ -10953,13 +11029,15 @@ function_call_generic: $$= udf; #endif } - opt_udf_expr_list ')' + opt_udf_expr_list ')' opt_object_member_access { const Type_handler *h; Create_func *builder; Item *item= NULL; const sp_type_def *tdef= NULL; const Lex_ident_sys ident(thd, &$1); + sp_variable *spv= NULL; + bool allow_field_accessor= false; if (unlikely(ident.is_null() || Lex_ident_routine::check_name_with_error(ident))) @@ -10991,6 +11069,22 @@ function_call_generic: { item= tdef->make_constructor_item(thd, $4); } + else if (Lex->spcont && + (spv= Lex->spcont->find_variable(&ident, false)) && + spv->type_handler()->has_functors()) + { + const char *end= $6.str ? $6.end() : $5.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, + ident2, name_cli))) + MYSQL_YYABORT; + item->set_name(thd, $1.pos(), end - $1.pos(), thd->charset()); + + // Only allow 'result accessors' for associative arrays + allow_field_accessor= true; + } else { #ifdef HAVE_DLOPEN @@ -11015,6 +11109,13 @@ function_call_generic: } } + if ($6.str && !allow_field_accessor) + { + Lex_ident_sys field_sys(thd, &$6); + my_error(ER_BAD_FIELD_ERROR, MYF(0), field_sys.str, ident.str); + MYSQL_YYABORT; + } + if (unlikely(! ($$= item))) MYSQL_YYABORT; } @@ -11038,7 +11139,10 @@ function_call_generic: } | ident_cli '.' ident_cli '(' opt_expr_list ')' { - if (unlikely(!($$= Lex->make_item_func_call_generic(thd, &$1, &$3, $5)))) + const Lex_ident_cli pos($1.pos(), $6.pos() - $1.pos() + 1); + if (unlikely(!($$= Lex->make_item_func_or_method_call(thd, $1, + $3, $5, + pos)))) MYSQL_YYABORT; } | ident_cli '.' ident_cli '.' ident_cli '(' opt_expr_list ')' @@ -11085,6 +11189,11 @@ function_call_generic: */ ; +opt_object_member_access: + /* empty */ { $$= Lex_ident_cli((const char *)nullptr, 0); } + | '.' ident_cli { $$= $2; } + ; + fulltext_options: opt_natural_language_mode opt_query_expansion { $$= $1 | $2; } @@ -11105,6 +11214,9 @@ opt_query_expansion: opt_udf_expr_list: /* empty */ { $$= NULL; } | udf_expr_list { $$= $1; } +%ifdef ORACLE + | named_expr_list { $$= $1; } +%endif ; udf_expr_list: @@ -11901,16 +12013,18 @@ json_table_column_type: { Lex_field_type_st type; type.set(&type_handler_slong); - Lex->last_field->set_attributes(thd, type, - COLUMN_DEFINITION_TABLE_FIELD); + if (Lex->last_field->set_attributes(thd, type, + COLUMN_DEFINITION_TABLE_FIELD)) + MYSQL_YYABORT; Lex->json_table->m_cur_json_table_column-> set(Json_table_column::FOR_ORDINALITY); } | json_table_field_type PATH_SYM json_text_literal json_opt_on_empty_or_error { - Lex->last_field->set_attributes(thd, $1, - COLUMN_DEFINITION_TABLE_FIELD); + if (Lex->last_field->set_attributes(thd, $1, + COLUMN_DEFINITION_TABLE_FIELD)) + MYSQL_YYABORT; if (Lex->json_table->m_cur_json_table_column-> set(thd, Json_table_column::PATH, $3, $1.charset_collation_attrs())) @@ -11920,8 +12034,9 @@ json_table_column_type: } | json_table_field_type EXISTS PATH_SYM json_text_literal { - Lex->last_field->set_attributes(thd, $1, - COLUMN_DEFINITION_TABLE_FIELD); + if (Lex->last_field->set_attributes(thd, $1, + COLUMN_DEFINITION_TABLE_FIELD)) + MYSQL_YYABORT; if (Lex->json_table->m_cur_json_table_column-> set(thd, Json_table_column::EXISTS_PATH, $4, $1.charset_collation_attrs())) @@ -13358,6 +13473,15 @@ select_outvar: if (unlikely(!($$= Lex->create_outvar(thd, $1, $3)) && Lex->result)) MYSQL_YYABORT; } + | ident '(' expr ')' opt_object_member_access + { + auto field= $5.str ? Lex_ident_sys(thd, &$5) : Lex_ident_sys(); + if (unlikely(($5.str && !field.str) || + (!($$= Lex->create_outvar_lvalue_function(thd, $1, $3, + field)) && + Lex->result))) + MYSQL_YYABORT; + } ; into: @@ -19282,46 +19406,56 @@ sp_unlabeled_block_not_atomic: statement: verb_clause | set_assign + | set_assign_lvalue_function + ; + +direct_call_or_lvalue_assign: + optionally_qualified_directly_assignable + { + if (Lex->call_statement_start_or_lvalue_assign(thd, $$= $1)) + MYSQL_YYABORT; + } + ; + +direct_call_statement: + direct_call_or_lvalue_assign opt_parenthesized_opt_sp_cparams + { + if (unlikely($1->spvar())) + { + thd->parse_error(ER_SYNTAX_ERROR, $1->pos()); + MYSQL_YYABORT; + } + + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } + ; + +set_assign_lvalue_function: + direct_call_or_lvalue_assign parenthesized_opt_sp_cparams + opt_object_member_access SET_VAR + { + if (unlikely(Lex->assoc_assign_start(thd, $1))) + MYSQL_YYABORT; + } + set_expr_or_default + { + const Lex_ident_sys member= $3.str ? Lex_ident_sys(thd, &$3) : + Lex_ident_sys(); + if (unlikely($3.str && member.is_null()) || + unlikely(Lex->sp_set_assign_lvalue_function(thd, $1, $2, + member, + $6.expr, + $6.expr_str)) || + unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY, + false))) + MYSQL_YYABORT; + } ; sp_statement: statement - | ident_cli_directly_assignable - { - // Direct procedure call (without the CALL keyword) - Lex_ident_sys tmp(thd, &$1); - if (unlikely(!tmp.str) || - unlikely(Lex->call_statement_start(thd, &tmp))) - MYSQL_YYABORT; - } - opt_sp_cparam_list - { - if (Lex->check_cte_dependencies_and_resolve_references()) - MYSQL_YYABORT; - } - | ident_cli_directly_assignable '.' ident - { - Lex_ident_sys tmp(thd, &$1); - if (unlikely(!tmp.str) || - unlikely(Lex->call_statement_start(thd, &tmp, &$3))) - MYSQL_YYABORT; - } - opt_sp_cparam_list - { - if (Lex->check_cte_dependencies_and_resolve_references()) - MYSQL_YYABORT; - } - | ident_cli_directly_assignable '.' ident '.' ident - { - Lex_ident_sys tmp(thd, &$1); - if (unlikely(Lex->call_statement_start(thd, &tmp, &$3, &$5))) - MYSQL_YYABORT; - } - opt_sp_cparam_list - { - if (Lex->check_cte_dependencies_and_resolve_references()) - MYSQL_YYABORT; - } + | direct_call_statement ; sp_if_then_statements: @@ -19565,39 +19699,38 @@ ident_cli_directly_assignable: ; +optionally_qualified_directly_assignable: + ident_cli_directly_assignable + { + if (unlikely(!($$= new (thd->mem_root) Qualified_ident(thd, $1)))) + MYSQL_YYABORT; + } + | ident_cli_directly_assignable '.' ident + { + if (unlikely(!($$= new (thd->mem_root) Qualified_ident(thd, $1, + $3)))) + MYSQL_YYABORT; + } + | ident_cli_directly_assignable '.' ident '.' ident + { + if (unlikely(!($$= new (thd->mem_root) Qualified_ident(thd, $1, + $3, $5)))) + MYSQL_YYABORT; + } + ; + + set_assign: - ident_cli_directly_assignable SET_VAR + optionally_qualified_directly_assignable SET_VAR { LEX *lex=Lex; lex->set_stmt_init(); - if (sp_create_assignment_lex(thd, $1.pos())) + if (sp_create_assignment_lex(thd, $1->pos())) MYSQL_YYABORT; } set_expr_or_default { - Lex_ident_sys tmp(thd, &$1); - - if (unlikely(!tmp.str) || - unlikely(Lex->set_variable(&tmp, $4.expr, $4.expr_str)) || - unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY, - false))) - MYSQL_YYABORT; - } - | ident_cli_directly_assignable '.' ident SET_VAR - { - LEX *lex=Lex; - lex->set_stmt_init(); - if (sp_create_assignment_lex(thd, $1.pos())) - MYSQL_YYABORT; - } - set_expr_or_default - { - LEX *lex= Lex; - DBUG_ASSERT(lex->var_list.is_empty()); - Lex_ident_sys tmp(thd, &$1); - - if (unlikely(!tmp.str) || - unlikely(lex->set_variable(&tmp, &$3, $6.expr, $6.expr_str)) || + if (unlikely(Lex->set_variable($1, $4.expr, $4.expr_str)) || unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY, false))) MYSQL_YYABORT; @@ -19762,6 +19895,40 @@ package_implementation_executable_section: | BEGIN_ORACLE_SYM sp_block_statements_and_exceptions END { $$= $2; } ; +named_expr_list: + remember_name named_expr remember_end + { + if (unlikely(!($$= new (thd->mem_root) List) || + $$->push_back($2, thd->mem_root))) + MYSQL_YYABORT; + } + | named_expr_list ',' remember_name named_expr remember_end + { + if (($$= $1)->push_back($4, thd->mem_root)) + MYSQL_YYABORT; + } + ; + +assoc_name: + TEXT_STRING_sys + | LONG_NUM + | ULONGLONG_NUM + | DECIMAL_NUM + | NUM + ; + +named_expr: + assoc_name ARROW_SYM expr + { + if ($1.str) + { + $3->base_flags|= item_base_t::IS_EXPLICIT_NAME; + $3->set_name(thd, $1); + } + $$= $3; + } + ; + %endif ORACLE @@ -20154,6 +20321,79 @@ opt_sp_decl_handler_list: | sp_decl_handler_list ; +typed_ident: + TYPE_SYM ident_directly_assignable + { + if (unlikely(!Lex->init_spvar_definition(thd, ($$= $2)))) + MYSQL_YYABORT; + } + ; + +assoc_array_table_types: + field_type_all_with_record + { + if (Lex->last_field->set_attributes(thd, $1, + COLUMN_DEFINITION_ROUTINE_LOCAL)) + MYSQL_YYABORT; + $$= static_cast(Lex->last_field); + } + | sp_decl_ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + $$= static_cast(Lex->last_field); + if (unlikely(Lex->sphead->spvar_def_fill_type_reference(thd, $$, + $1, $3))) + MYSQL_YYABORT; + } + | sp_decl_ident '.' ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + $$= static_cast(Lex->last_field); + if (unlikely(Lex->sphead->spvar_def_fill_type_reference(thd, $$, + $1, $3, + $5))) + MYSQL_YYABORT; + } + | ident PERCENT_ORACLE_SYM ROWTYPE_ORACLE_SYM + { + $$= static_cast(Lex->last_field); + if (unlikely(Lex->sphead->spvar_def_fill_rowtype_reference(thd, $$, + $1))) + MYSQL_YYABORT; + } + | sp_decl_ident '.' ident PERCENT_ORACLE_SYM ROWTYPE_ORACLE_SYM + { + $$= static_cast(Lex->last_field); + if (unlikely(Lex->sphead->spvar_def_fill_rowtype_reference(thd, + $$, + $1, $3))) + MYSQL_YYABORT; + } + ; + +assoc_array_index_types: + int_type opt_field_length last_field_options + { + $$.set_handler_length_flags($1, $2, (uint32) $3); + } + | varchar opt_field_length opt_binary_and_compression + { + $$.set(&type_handler_varchar, $2, $3); + } + | VARCHAR2_ORACLE_SYM opt_field_length opt_binary_and_compression + { + $$.set(&type_handler_varchar, $2, $3); + } + ; + +assoc_array_index_type: + INDEX_SYM BY assoc_array_index_types + { + if (Lex->last_field->set_attributes(thd, $3, + COLUMN_DEFINITION_ROUTINE_LOCAL)) + MYSQL_YYABORT; + $$= new (thd->mem_root) Spvar_definition(*Lex->last_field); + } + ; + sp_decl_non_handler: sp_decl_variable_list | ident_directly_assignable CONDITION_SYM FOR_SYM sp_cond @@ -20192,14 +20432,32 @@ sp_decl_non_handler: $$.vars= $$.conds= $$.hndlrs= 0; $$.curs= 1; } - | TYPE_SYM ident_directly_assignable IS RECORD_SYM rec_type_body + | typed_ident IS RECORD_SYM rec_type_body { if (unlikely(Lex->spcont-> - type_defs_declare_record(thd, Lex_ident_column($2), $5))) + type_defs_declare_record(thd, Lex_ident_column($1), $4))) MYSQL_YYABORT; $$.vars= $$.conds= $$.hndlrs= $$.curs= 0; } + | typed_ident IS TABLE_SYM OF_SYM assoc_array_table_types + { + Lex->init_last_field(new (thd->mem_root) Column_definition(), + &empty_clex_str); + } + assoc_array_index_type + { + const auto aa= "associative_array"_Lex_ident_plugin; + const Type_handler *th= + Type_handler::handler_by_name_or_error(thd, aa); + if (unlikely(!th || + Lex->spcont-> + type_defs_declare_composite2(thd, + Lex_ident_column($1), + th, $7, $5))) + MYSQL_YYABORT; + $$.vars= $$.conds= $$.hndlrs= $$.curs= 0; + } ;