From 8338a091dc881b7a095ea57155390b752c40c71b 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. --- include/mysql_com.h | 2 +- .../compat/oracle/r/sp-assoc-array.result | 1436 +++++++++++++++ .../suite/compat/oracle/r/sp-code.result | 43 + .../suite/compat/oracle/t/sp-assoc-array.test | 1614 +++++++++++++++++ mysql-test/suite/compat/oracle/t/sp-code.test | 39 + .../type_uuid/type_uuid_sp_assoc_array.result | 35 + .../type_uuid/type_uuid_sp_assoc_array.test | 38 + sql/field.cc | 467 +++++ sql/field.h | 118 +- sql/item.cc | 353 +++- sql/item.h | 185 +- sql/item_func.cc | 192 ++ sql/item_func.h | 176 ++ sql/item_strfunc.cc | 1 + sql/item_sum.cc | 1 + sql/item_windowfunc.cc | 1 + sql/lex.h | 1 + sql/share/errmsg-utf8.txt | 26 +- sql/sp_head.cc | 124 ++ sql/sp_head.h | 16 + sql/sp_instr.cc | 82 + sql/sp_instr.h | 70 + sql/sp_pcontext.cc | 41 +- sql/sp_pcontext.h | 55 +- sql/sp_rcontext.cc | 134 +- sql/sp_rcontext.h | 8 + sql/sql_class.cc | 81 +- sql/sql_class.h | 36 +- sql/sql_lex.cc | 516 +++++- sql/sql_lex.h | 57 + sql/sql_partition.cc | 1 + sql/sql_select.cc | 1 + sql/sql_type.cc | 109 +- sql/sql_type.h | 68 + sql/sql_yacc.yy | 435 ++++- storage/connect/ha_connect.cc | 1 + 36 files changed, 6459 insertions(+), 104 deletions(-) create mode 100644 mysql-test/suite/compat/oracle/r/sp-assoc-array.result create mode 100644 mysql-test/suite/compat/oracle/t/sp-assoc-array.test 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 diff --git a/include/mysql_com.h b/include/mysql_com.h index b9fae54e7f476..c2a7e28ca520e 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -663,7 +663,7 @@ struct my_rnd_struct; enum Item_result { STRING_RESULT=0, REAL_RESULT, INT_RESULT, ROW_RESULT, DECIMAL_RESULT, - TIME_RESULT + TIME_RESULT, ASSOC_ARRAY_RESULT }; typedef struct st_udf_args diff --git a/mysql-test/suite/compat/oracle/r/sp-assoc-array.result b/mysql-test/suite/compat/oracle/r/sp-assoc-array.result new file mode 100644 index 0000000000000..cde53b9a50437 --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-assoc-array.result @@ -0,0 +1,1436 @@ +# +# MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +# +set sql_mode=oracle; +SET NAMES utf8mb4; +# +# RECORD element type +# +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; +$$ +nick temp.first_name temp.last_name +Monty Michael Widenius +nick temp.first_name temp.last_name +Serg Sergei Golubchik +# +# RECORD element type with initialization +# +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; +$$ +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; +END; +$$ +marks(id) id +62 0 +marks(id) id +78 1 +# +# 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 +# +# 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; +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 +# +# 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 +# +# Key numeric, 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' +# +# Key numeric, 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' +# +# 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' +# +# 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 '(1):= 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('Monty').first_name:= 'Mike'; +SELECT person_by_nickname('Monty').first_name, +person_by_nickname('Monty').last_name; +END; +$$ +person_by_nickname('Monty').first_name person_by_nickname('Monty').last_name +Mike Widenius +# +# 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 '' 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 +# TODO Oracle allows comparison between two associative arrays +# +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 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 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 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: Key specifier used with variable 'id' are only allowed for associative array data types +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; +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 HY000: Unknown data type: 'marks_t' +# +# 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 diff --git a/mysql-test/suite/compat/oracle/r/sp-code.result b/mysql-test/suite/compat/oracle/r/sp-code.result index 0fc980a3b8432..e08e52889a758 100644 --- a/mysql-test/suite/compat/oracle/r/sp-code.result +++ b/mysql-test/suite/compat/oracle/r/sp-code.result @@ -1514,3 +1514,46 @@ SHOW PROCEDURE CODE p1; Pos Instruction 0 stmt 31 "max_error_count:=10" DROP PROCEDURE p1; +# +# 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 (43@1,99@2) +1 set marks@0[1] 62 +2 stmt 0 "SELECT marks(1)" +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 (('Michael','Widenius')@Monty,('Sergei ','Golubchik')@Serg) +1 set nick@1 NULL +2 set nick@1 first(person_by_nickname@0) +3 set person_by_nickname@0[nick@1].first_name 'Michael' +4 set person_by_nickname@0[nick@1] ('Michael','Widenius') +DROP PROCEDURE p1; diff --git a/mysql-test/suite/compat/oracle/t/sp-assoc-array.test b/mysql-test/suite/compat/oracle/t/sp-assoc-array.test new file mode 100644 index 0000000000000..728e68dcc4e42 --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-assoc-array.test @@ -0,0 +1,1614 @@ +--echo # +--echo # MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +--echo # + +set sql_mode=oracle; +SET NAMES utf8mb4; + +--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; +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 # 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; +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 # Key numeric, 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 # Key numeric, 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 # 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 # 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('Monty').first_name:= 'Mike'; + + SELECT person_by_nickname('Monty').first_name, + person_by_nickname('Monty').last_name; +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 +--echo # TODO Oracle allows comparison between two associative arrays +--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 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 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 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_WRONG_TYPE_FOR_ASSOC_ARRAY_KEY +DECLARE + id INTEGER; +BEGIN + 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; + + +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_UNKNOWN_DATA_TYPE +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 ;$$ diff --git a/mysql-test/suite/compat/oracle/t/sp-code.test b/mysql-test/suite/compat/oracle/t/sp-code.test index 1ffd3b948e602..f083ec8cb6248 100644 --- a/mysql-test/suite/compat/oracle/t/sp-code.test +++ b/mysql-test/suite/compat/oracle/t/sp-code.test @@ -1086,3 +1086,42 @@ SHOW PROCEDURE CODE p1; DROP PROCEDURE p1; +--echo # +--echo # MDEV-34319 DECLARE TYPE .. TABLE OF .. INDEX BY in stored routines +--echo # +DELIMITER $$; +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; +$$ +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; 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 0000000000000..e22da37bbff0d --- /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 0000000000000..15d3c877fa17a --- /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/field.cc b/sql/field.cc index 1874cba662a53..42dad1b066343 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -33,6 +33,7 @@ #include "filesort.h" // change_double_for_sort #include "log_event.h" // class Table_map_log_event #include +#include "sp_rcontext.h" // Maximum allowed exponent value for converting string to decimal #define MAX_EXPONENT 1024 @@ -2846,6 +2847,471 @@ void Field_row::sql_type_for_sp_returns(String &res) const } +/**************************************************************************** + 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, Item_field *value) + : key(key), value(value) + { + } + + String* key; + Item_field *value; +}; + + +static int assoc_array_tree_cmp(void *arg, const Assoc_array_data *lhs, + const Assoc_array_data *rhs) +{ + return sortcmp(lhs->key, rhs->key, (CHARSET_INFO*)arg); +} + + +static int assoc_array_tree_del(Assoc_array_data *data, TREE_FREE, void*) +{ + // Explicitly set the key's buffer to NULL to deallocate + // the memory held in it's internal buffer. + data->key->set((const char*)NULL, 0, &my_charset_bin); + delete data; + return 0; +} + + +Field_assoc_array::Field_assoc_array(uchar *ptr_arg, + const LEX_CSTRING *field_name_arg) + :Field_null(ptr_arg, 0, Field::NONE, field_name_arg, &my_charset_bin) +{ + init_alloc_root(PSI_NOT_INSTRUMENTED, &m_mem_root, 512, 0, MYF(0)); + + m_table= (TABLE*) alloc_root(&m_mem_root, sizeof(TABLE)+ sizeof(TABLE_SHARE)); + if (!m_table) + return; + + bzero((void *)m_table, sizeof(TABLE)+ sizeof(TABLE_SHARE)); + m_table->s= (TABLE_SHARE*) (m_table+1); + + m_table->alias.set("", 0, table_alias_charset); + m_table->in_use= get_thd(); + m_table->copy_blobs= TRUE; + m_table->s->table_cache_key= empty_clex_str; + m_table->s->table_name= Lex_ident_table(empty_clex_str); + + init_tree(&m_tree, 0, 0, + sizeof(Assoc_array_data), (qsort_cmp2)assoc_array_tree_cmp, + (tree_element_free)assoc_array_tree_del, NULL, + MYF(MY_THREAD_SPECIFIC | MY_TREE_WITH_DELETE)); +} + + +Field_assoc_array::~Field_assoc_array() +{ + m_table->alias.free(); + delete_tree(&m_tree, 0); + + free_root(&m_mem_root, MYF(0)); +} + + +CHARSET_INFO *Field_assoc_array::key_charset() const +{ + if (!m_def || !m_def->m_key_def) + return &my_charset_bin; + + return m_def->m_key_def->charset; +} + + +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() != ASSOC_ARRAY_RESULT) + { + my_error(ER_OPERAND_COLUMNS, MYF(0), m_table->s->fields); + DBUG_RETURN(true); + } + + src->bring_value(); + + delete_all_elements(); + + Query_arena backup_arena; + Query_arena owner_arena(&m_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP); + thd->set_n_backup_active_arena(&owner_arena, &backup_arena); + + String src_key; + if (!src->get_key(&src_key, true)) + { + do + { + Item_field* element= create_element(thd); + if (!element) + goto error; + + Item **src_elem= src->element_addr_by_key(thd, NULL, &src_key); + if (!src_elem) + goto error; + + if (element->field->sp_prepare_and_store_item(thd, src_elem)) + goto error; + + String *key_copy= copy_and_convert_key(thd, &src_key); + if (!key_copy) + goto error; + + Assoc_array_data *data = new (thd->mem_root) + Assoc_array_data(key_copy, element); + tree_insert(&m_tree, data, 0, (void *)key_charset()); + + set_notnull(); + } while (!src->get_next_key(&src_key, &src_key)); + } + + thd->restore_active_arena(&owner_arena, &backup_arena); + DBUG_RETURN(false); + +error: + thd->restore_active_arena(&owner_arena, &backup_arena); + DBUG_RETURN(true); +} + + +Item_field *Field_assoc_array::element_by_key(THD *thd, String *key) +{ + if (!key) + return NULL; + + Item_field *item= NULL; + + Assoc_array_data key_data(key, NULL); + Assoc_array_data *data= (Assoc_array_data *) + tree_search(&m_tree, &key_data, + (void *)key_charset()); + if (data) + item= data->value; + + Query_arena backup_arena; + Query_arena owner_arena(&m_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP); + thd->set_n_backup_active_arena(&owner_arena, &backup_arena); + if (!item) + { + // Create an element for the key if not found + if (!(item= create_element(thd))) + goto error; + + String *key_copy= copy_and_convert_key(thd, key); + if (!key_copy) + goto error; + + Assoc_array_data *data= new (thd->mem_root) + Assoc_array_data(key_copy, item); + if (!data) + goto error; + + tree_insert(&m_tree, data, 0, (void *)key_charset()); + set_notnull(); + } + + thd->restore_active_arena(&owner_arena, &backup_arena); + return item; + +error: + thd->restore_active_arena(&owner_arena, &backup_arena); + return NULL; +} + + +Item_field *Field_assoc_array::element_by_key(THD *thd, String *key) const +{ + if (!key) + return NULL; + + Item_field *item= NULL; + + String *key_copy= copy_and_convert_key(thd, key); + if (!key_copy) + return NULL; + + Assoc_array_data key_data(key_copy, NULL); + Assoc_array_data *data= (Assoc_array_data *) + tree_search((TREE *)&m_tree, + &key_data, + (void *)key_charset()); + if (data) + item= data->value; + + delete key_copy; + return item; +} + + +String *Field_assoc_array::copy_and_convert_key(THD *thd, const String *key) const +{ + DBUG_ASSERT(key); + + String *key_copy= new (thd->mem_root) String(); + if (!key_copy) + return NULL; + + uint errors; + auto key_def= m_def->m_key_def; + if (key_def->type_handler()->field_type() == MYSQL_TYPE_VARCHAR) + { + if (key_copy->copy(key, key_def->charset, &errors)) + { + delete key_copy; + return NULL; + } + + if (key_copy->length() > key_def->length) + { + delete key_copy; + my_error(ER_TOO_LONG_KEY, MYF(0), key_def->length); + return NULL; + } + } + else + { + if (key_copy->copy(key, key_charset(), &errors)) + { + return NULL; + } + + // Convert the key to a number to perform range check + // Follow Oracle's range for numerical keys + char *endptr; + int error; + long key_long= key_charset()->strntol(key_copy->ptr(), + key_copy->length(), + 10, &endptr, &error); + + if (error || + (endptr != key_copy->ptr() + key_copy->length()) || + key_long < INT32_MIN || key_long > INT32_MAX) + { + my_error(ER_WRONG_VALUE, MYF(0), "ASSOCIATIVE ARRAY KEY", + key_copy->ptr()); + delete key_copy; + return NULL; + } + } + + return key_copy; +} + + +Item_field *Field_assoc_array::create_element(THD *thd) +{ + Item_field *item= NULL; + + auto def= m_def->m_value_def; + const LEX_CSTRING empty_clex_str= {"", 0}; + Field *field= NULL; + if (def->is_column_type_ref()) + { + Column_definition cdef; + if (def->column_type_ref()->resolve_type_ref(thd, &cdef)) + return NULL; + + field= cdef.make_field(m_table->s, thd->mem_root, &empty_clex_str); + } + else + { + field= def->make_field(m_table->s, thd->mem_root, &empty_clex_str); + } + + field->init(m_table); + + Field_row *field_row= dynamic_cast(field); + if (field_row) + { + item= def->make_item_field_row(thd, field_row); + } + else + { + // Assign a buffer to the field + uchar *tmp; + if (!(tmp= (uchar *)thd->alloc(field->pack_length() + 1))) + return NULL; + field->move_field(tmp + 1, field->maybe_null() ? tmp : 0, 1); + + if (field->maybe_null()) + field->set_null(); + + if (field->default_value) + field->set_default(); + + item= new (thd->mem_root) Item_field(thd, field); + } + + return item; +} + + +Item **Field_assoc_array::element_addr_by_key(THD *thd, String *key) +{ + if (!key) + return NULL; + + String *key_copy= copy_and_convert_key(thd, key); + if (!key_copy) + return NULL; + + Assoc_array_data key_data(key_copy, NULL); + Assoc_array_data *data= (Assoc_array_data *) + tree_search(&m_tree, + &key_data, + (void *)key_charset()); + delete key_copy; + if (data) + return (Item **)&data->value; + + return NULL; +} + + +Field * +Field_assoc_array::get_field_by_key_and_name(THD *thd, + const LEX_CSTRING &var_name, + String *key, + const LEX_CSTRING &field_name) + const +{ + Item_field *item= element_by_key(thd, key); + if (!item) + return NULL; + + Field *elem_field= item->field; + Virtual_tmp_table **ptable= elem_field->virtual_tmp_table_addr(); + DBUG_ASSERT(ptable); + DBUG_ASSERT(ptable[0]); + + uint field_index; + if (ptable[0]->sp_find_field_by_name_or_error( + &field_index, + empty_clex_str, + field_name)) + return NULL; + + return ptable[0]->field[field_index]; +} + + +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 + + Assoc_array_data key_data(key, NULL); + (void) tree_delete(&m_tree, &key_data, 0, (void *)key_charset()); + 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) + { + key->copy(*data->key); + return false; + } + + return true; +} + + +bool Field_assoc_array::get_next_key(const String *curr_key, String *next_key) +{ + DBUG_ASSERT(next_key); + + TREE_ELEMENT **last_pos; + TREE_ELEMENT *parents[MAX_TREE_HEIGHT+1]; + + if (!curr_key) + return true; + + Assoc_array_data key_data((String *)curr_key, NULL); + + Assoc_array_data *data= (Assoc_array_data *) + tree_search_key(&m_tree, &key_data, + parents, &last_pos, + HA_READ_AFTER_KEY, (void *)key_charset()); + if (data) + { + next_key->copy(*data->key); + return false; + } + return true; +} + + +bool Field_assoc_array::get_prior_key(const String *curr_key, String *prior_key) +{ + DBUG_ASSERT(prior_key); + + TREE_ELEMENT **last_pos; + TREE_ELEMENT *parents[MAX_TREE_HEIGHT+1]; + + if (!curr_key) + return true; + + Assoc_array_data key_data((String *)curr_key, NULL); + + Assoc_array_data *data= (Assoc_array_data *) + tree_search_key(&m_tree, + &key_data, + parents, + &last_pos, + HA_READ_BEFORE_KEY, + (void *)key_charset()); + if (data) + { + prior_key->copy(*data->key); + return false; + } + return true; +} + + /**************************************************************************** Functions for the Field_decimal class This is an number stored as a pre-space (or pre-zero) string @@ -9932,6 +10398,7 @@ Field_enum::can_optimize_range_or_keypart_ref(const Item_bool_func *cond, Data_type_compatibility::OK : Data_type_compatibility::INCOMPATIBLE_COLLATION); case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } diff --git a/sql/field.h b/sql/field.h index 19080ba8e3a7d..2fa01923b4513 100644 --- a/sql/field.h +++ b/sql/field.h @@ -29,6 +29,7 @@ #include "compat56.h" #include "sql_type.h" /* Type_std_attributes */ #include "field_comp.h" +#include "my_tree.h" class Send_field; class Copy_field; @@ -44,6 +45,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; @@ -2033,6 +2035,32 @@ class Field: public Value_source } virtual bool sp_prepare_and_store_item(THD *thd, Item **value); + 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; } + virtual Field *get_field_by_key_and_name(THD *thd, + const LEX_CSTRING &var_name, + String *key, + const LEX_CSTRING &field_name) const + { + return NULL; + } + friend int cre_myisam(char * name, TABLE *form, uint options, ulonglong auto_increment_value); friend class Copy_field; @@ -5253,6 +5281,73 @@ class Field_row final :public Field_null }; +class Assoc_array_definition :public Sql_alloc +{ +public: + Column_definition *m_key_def; + Spvar_definition *m_value_def; + + Assoc_array_definition() + :m_key_def(NULL), m_value_def(NULL) + { } + + Assoc_array_definition( + Column_definition *key_def, + Spvar_definition *value_def) + :m_key_def(key_def), m_value_def(value_def) + { } +}; + + +class Field_assoc_array final :public Field_null +{ +public: + MEM_ROOT m_mem_root; + TREE m_tree; + + TABLE *m_table; + Assoc_array_definition *m_def; + +public: + Field_assoc_array(uchar *ptr_arg, + const LEX_CSTRING *field_name_arg); + ~Field_assoc_array(); + 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; + } + 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; + + Field *get_field_by_key_and_name(THD *thd, const LEX_CSTRING &var_name, + String *key, + const LEX_CSTRING &field_name) + const override; + +protected: + Item_field *create_element(THD *thd); + String *copy_and_convert_key(THD *thd, const String *key) const; + CHARSET_INFO *key_charset() const; +}; + + extern const LEX_CSTRING null_clex_str; class Column_definition_attributes: public Type_extra_attributes @@ -5662,6 +5757,7 @@ class Row_definition_list: public List - 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. @@ -5681,13 +5777,15 @@ class Spvar_definition: public Column_definition bool m_cursor_rowtype_ref; // for cursor%ROWTYPE uint m_cursor_rowtype_offset; // for cursor%ROWTYPE Row_definition_list *m_row_field_definitions; // for ROW + Assoc_array_definition *m_assoc_array_def; public: Spvar_definition() :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) + m_row_field_definitions(NULL), + m_assoc_array_def(NULL) { } Spvar_definition(THD *thd, Field *field) :Column_definition(thd, field, NULL), @@ -5695,7 +5793,8 @@ class Spvar_definition: public Column_definition m_table_rowtype_ref(NULL), m_cursor_rowtype_ref(false), m_cursor_rowtype_offset(0), - m_row_field_definitions(NULL) + m_row_field_definitions(NULL), + m_assoc_array_def(NULL) { } const Type_handler *type_handler() const { @@ -5772,6 +5871,21 @@ class Spvar_definition: public Column_definition } class Item_field_row *make_item_field_row(THD *thd, Field_row *field); + + uint is_assoc_array() const + { + return m_assoc_array_def != NULL; + } + Assoc_array_definition *assoc_array_definition() const + { + return m_assoc_array_def; + } + void set_assoc_array_definition(Assoc_array_definition *assoc_array_def) + { + DBUG_ASSERT(assoc_array_def); + set_handler(&type_handler_assoc_array); + m_assoc_array_def= assoc_array_def; + } }; diff --git a/sql/item.cc b/sql/item.cc index 390c82a34d268..038b8ecfbd697 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -545,6 +545,7 @@ void Item::print_value(String *str) str->append(*ptr); break; case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); } } @@ -1923,10 +1924,14 @@ CALL p1(); bool Item_splocal::check_cols(uint n) { DBUG_ASSERT(m_thd->spcont); - if (Type_handler_hybrid_field_type::cmp_type() != ROW_RESULT) + + Item_result cmp_type= Type_handler_hybrid_field_type::cmp_type(); + if (cmp_type != ROW_RESULT && + cmp_type != ASSOC_ARRAY_RESULT) return Item::check_cols(n); - if (n != this_item()->cols() || n == 1) + if (n != this_item()->cols() || n == 1 || + cmp_type == ASSOC_ARRAY_RESULT) { my_error(ER_OPERAND_COLUMNS, MYF(0), n); return true; @@ -2040,6 +2045,180 @@ bool Item_splocal_row_field_by_name::set_value(THD *thd, sp_rcontext *ctx, Item } +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->null_value) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, MYF(0), + m_name.str); + return true; + } + + Item *item= get_variable(thd->spcont)->element_by_key(thd, m_key->val_str()); + if (!item) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + m_key->val_str()->ptr()); + return true; + } + + 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_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_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_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) +{ + return get_rcontext(ctx)->set_variable_assoc_array_by_key(thd, m_var_idx, m_key, + it); +} + + +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; + + Item *element_item_base= get_variable(thd->spcont)-> + element_by_key(thd, m_key->val_str()); + if (!element_item_base) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + m_key->val_str()->ptr()); + return true; + } + + Item_field *element_item= element_item_base->field_for_view_update(); + if (!element_item) + return true; + + Virtual_tmp_table **ptable= element_item->field->virtual_tmp_table_addr(); + if (!ptable) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), + m_key->val_str()->ptr(), thd_where(thd)); + return true; + } + + Virtual_tmp_table *vtable= ptable[0]; + DBUG_ASSERT(vtable); + + if (vtable->sp_find_field_by_name_or_error(&m_field_idx, + m_name, m_field_name)) + return true; + + Item *item= element_item->element_index(m_field_idx); + 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()); + + Item *element_item= get_variable(m_thd->spcont)->element_by_key(m_thd, m_key->val_str()); + return element_item->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()); + + Item *element_item= get_variable(m_thd->spcont)->element_by_key(m_thd, m_key->val_str()); + return element_item->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()); + + Item *element_item= get_variable(thd->spcont)->element_by_key(thd, m_key->val_str()); + return element_item->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); +} + + /***************************************************************************** Item_case_expr methods *****************************************************************************/ @@ -4747,6 +4926,7 @@ double Item_param::PValue::val_real(const Type_std_attributes *attr) const */ return TIME_to_double(&time); case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -4768,6 +4948,7 @@ longlong Item_param::PValue::val_int(const Type_std_attributes *attr) const case TIME_RESULT: return (longlong) TIME_to_ulonglong(&time); case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -4792,6 +4973,7 @@ my_decimal *Item_param::PValue::val_decimal(my_decimal *dec, case TIME_RESULT: return TIME_to_my_decimal(&time, dec); case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -4825,6 +5007,7 @@ String *Item_param::PValue::val_str(String *str, return str; } case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -4901,6 +5084,7 @@ const String *Item_param::value_query_val_str(THD *thd, String *str) const return str; } case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -5001,6 +5185,7 @@ Item *Item_param::value_clone_item(THD *thd) const case TIME_RESULT: break; case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } @@ -10377,6 +10562,8 @@ Item_result item_cmp_type(Item_result a,Item_result b) return ROW_RESULT; if (a == TIME_RESULT || b == TIME_RESULT) return TIME_RESULT; + if (a == ASSOC_ARRAY_RESULT || b == ASSOC_ARRAY_RESULT) + return ASSOC_ARRAY_RESULT; if ((a == INT_RESULT || a == DECIMAL_RESULT) && (b == INT_RESULT || b == DECIMAL_RESULT)) return DECIMAL_RESULT; @@ -11393,3 +11580,165 @@ bool ignored_list_includes_table(ignored_tables_list_t list, TABLE_LIST *tbl) } 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('('); + for (uint i= 0; i < arg_count; i++) + { + if (i) + str->append(','); + args[i]->print(str, query_type); + str->append('@'); + str->append(args[i]->name.str, args[i]->name.length); + } + str->append(')'); +} + + +void Item_assoc_array::illegal_method_call(const char *method) +{ + DBUG_ENTER("Item_assoc_array::illegal_method_call"); + DBUG_PRINT("error", ("!!! %s method was called for associative array", + method)); + DBUG_ASSERT(0); + my_error(ER_OPERAND_COLUMNS, MYF(0), 1); + DBUG_VOID_RETURN; +} + + +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; +} + diff --git a/sql/item.h b/sql/item.h index 985fdc3db530b..071c0c06decd5 100644 --- a/sql/item.h +++ b/sql/item.h @@ -883,7 +883,7 @@ class Item :public Value_source, FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM, SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER, PARAM_ITEM, TRIGGER_FIELD_ITEM, - EXPR_CACHE_ITEM}; + EXPR_CACHE_ITEM, ASSOC_ARRAY_ITEM}; enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE }; enum traverse_order { POSTFIX, PREFIX }; @@ -2524,6 +2524,22 @@ class Item :public Value_source, // used in row subselects to get value of elements virtual void bring_value() {} + // For associative arrays + /// Returns the number of columns for the elements of the array + /// returns 0 if the element is of scalar type + virtual uint cols_for_elements() const { return 0; } + 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 NULL; } + virtual Item **element_addr_by_key(THD *thd, Item **addr_arg, String *key) + { + return addr_arg; + } + const Type_handler *type_handler_long_or_longlong() const { return Type_handler::type_handler_long_or_longlong(max_char_length(), @@ -3382,6 +3398,58 @@ class Item_splocal_row_field_by_name :public Item_splocal_row_field }; +class Item_splocal_assoc_array_element :public Item_splocal +{ +protected: + Item *m_key; + bool set_value(THD *thd, sp_rcontext *ctx, Item **it) override; +public: + Item_splocal_assoc_array_element(THD *thd, + const Sp_rcontext_handler *rh, + const LEX_CSTRING *sp_var_name, + Item *m_key, uint sp_var_idx, + const Type_handler *handler, + uint pos_in_q= 0, uint len_in_q= 0) + :Item_splocal(thd, rh, sp_var_name, sp_var_idx, handler, pos_in_q, len_in_q), + m_key(m_key) + { } + 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; } +}; + + +class Item_splocal_assoc_array_element_field : + public Item_splocal_row_field_by_name +{ +public: + Item *m_key; + Item_splocal_assoc_array_element_field(THD *thd, + const Sp_rcontext_handler *rh, + const LEX_CSTRING *sp_var_name, + Item *key, const LEX_CSTRING *sp_field_name, + uint sp_var_idx, + const Type_handler *handler, + uint pos_in_q= 0, uint len_in_q= 0) + :Item_splocal_row_field_by_name(thd, rh, sp_var_name, sp_field_name, + sp_var_idx, handler, pos_in_q, len_in_q), + m_key(key) + { } + 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_splocal inline implementation. *****************************************************************************/ @@ -3961,6 +4029,50 @@ class Item_field_row: public Item_field, }; +/** + Item_field for the associative array data type +*/ +class Item_field_assoc_array: public Item_field +{ +public: + Assoc_array_definition *m_def; + Item_field_assoc_array(THD *thd, Field *field) + :Item_field(thd, field), + m_def(NULL) + { } + 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; } + + bool set_array_def(THD *thd, Assoc_array_definition *def); + + uint cols_for_elements() const override; + + uint rows() const override + { + return field->rows(); + } + bool get_key(String *key, bool is_first) override + { + return field->get_key(key, is_first); + } + bool get_next_key(const String *curr_key, String *next_key) override + { + return field->get_next_key(curr_key, next_key); + } + Item *element_by_key(THD *thd, String *key) override + { + return ((const Field *)field)->element_by_key(thd, key); + } + Item **element_addr_by_key(THD *thd, Item **ref, String *key) override + { + return field->element_addr_by_key(thd, key); + } +}; + + class Item_null :public Item_basic_constant { public: @@ -8219,6 +8331,77 @@ inline void Virtual_column_info::print(String* str) expr->print_for_table_def(str); } +class Item_assoc_array: public Item_fixed_hybrid, + private Item_args +{ +public: + Item_assoc_array(THD *thd) + :Item_fixed_hybrid(thd), Item_args() + { } + + Item_assoc_array(THD *thd, List &list) + :Item_fixed_hybrid(thd), Item_args(thd, list) + { } + + enum Type type() const override { return ASSOC_ARRAY_ITEM; } + const Type_handler *type_handler() const override + { + return &type_handler_assoc_array; + } + + Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src, + const Tmp_field_param *param) override + { + return NULL; + } + + 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; + } + + 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_direct_ref_to_item : public Item_direct_ref { Item *m_item; diff --git a/sql/item_func.cc b/sql/item_func.cc index ff32db8f996b6..f8f1c0b787584 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3649,6 +3649,7 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -3725,6 +3726,7 @@ bool udf_handler::get_arguments() break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -4553,6 +4555,7 @@ longlong Item_func_benchmark::val_int() break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen return 0; } @@ -4795,6 +4798,10 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref) DBUG_ASSERT(0); set_handler(&type_handler_row); break; + case ASSOC_ARRAY_RESULT: + DBUG_ASSERT(0); + set_handler(&type_handler_assoc_array); + break; } if (thd->lex->current_select) { @@ -5015,6 +5022,7 @@ double user_var_entry::val_real(bool *null_value) return my_atof(value); // This is null terminated case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -5043,6 +5051,7 @@ longlong user_var_entry::val_int(bool *null_value) const } case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -5077,6 +5086,7 @@ String *user_var_entry::val_str(bool *null_value, String *str, break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -5105,6 +5115,7 @@ my_decimal *user_var_entry::val_decimal(bool *null_value, my_decimal *val) break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -5163,6 +5174,7 @@ Item_func_set_user_var::check(bool use_result_field) } case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5197,6 +5209,7 @@ void Item_func_set_user_var::save_item_result(Item *item) break; case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5264,6 +5277,7 @@ Item_func_set_user_var::update() } case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5726,6 +5740,7 @@ bool Item_func_get_user_var::fix_length_and_dec(THD *thd) break; case ROW_RESULT: // Keep compiler happy case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -6891,6 +6906,183 @@ bool Item_func_sp::check_vcol_func_processor(void *arg) return mark_unsupported_function(func_name(), "()", arg, VCOL_IMPOSSIBLE); } + +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 *) const override + { + return FALSE; + } + + + static Field *get_field_assoc_array(Item *item) + { + Item_splocal* item_splocal= item->get_item_splocal(); + return item_splocal->this_item()->field_for_view_update()->field; + } + + + virtual String *val_str(Item_handled_func *item, String *tmp) const override + { + auto field_assoc_array= get_field_assoc_array(item->arguments()[0]); + + if (field_assoc_array->get_key(tmp, true)) { + item->null_value= 1; + return NULL; + } + + item->null_value= 0; + 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 field_assoc_array= get_field_assoc_array(item->arguments()[0]); + + if (field_assoc_array->get_key(tmp, false)) { + item->null_value= 1; + 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 field= get_field_assoc_array(item->arguments()[0]); + auto curr_key= item->arguments()[1]->val_str(); + + if (field->get_next_key(curr_key, tmp)) { + item->null_value= 1; + 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 field= get_field_assoc_array(item->arguments()[0]); + auto curr_key= item->arguments()[1]->val_str(); + + if (field->get_prior_key(curr_key, tmp)) { + item->null_value= 1; + return NULL; + } + + return tmp; + } +}; + + +class Func_handler_assoc_array_count: + public Item_handled_func::Handler_ulonglong +{ + Longlong_null to_longlong_null(Item_handled_func *item) const override + { + DBUG_ASSERT(item->fixed()); + + auto field_assoc_array= + Func_handler_assoc_array_first::get_field_assoc_array( + item->arguments()[0]); + return Longlong_null(field_assoc_array->rows()); + } +}; + + +bool Item_func_assoc_array_first::fix_length_and_dec(THD *thd) +{ + static Func_handler_assoc_array_first ha_str_key; + set_func_handler(&ha_str_key); + return m_func_handler->fix_length_and_dec(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); +} + + +longlong Item_func_assoc_array_count::val_int() +{ + DBUG_ASSERT(fixed()); + return args[0]->this_item()->rows(); +} + + +longlong Item_func_assoc_array_exists::val_int() +{ + DBUG_ASSERT(fixed()); + + if (args[1]->null_value) + return 0; + + return args[0]->this_item()->element_by_key(current_thd, + args[1]->val_str()) != NULL; +} + + +longlong Item_func_assoc_array_delete::val_int() +{ + DBUG_ASSERT(fixed()); + + Field *field= args[0]->this_item()->field_for_view_update()->field; + if (arg_count == 1) + return field->delete_all_elements(); + else if (arg_count == 2) + return field->delete_element_by_key(args[1]->val_str()); + + return 0; +} + + /* uuid_short handling. diff --git a/sql/item_func.h b/sql/item_func.h index d2da02fc39e0b..dfee4ab3bc8fc 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -4373,6 +4373,182 @@ class Item_func_setval :public Item_func_nextval { return get_item_copy(thd, this); } }; + +/* Item_funcs for associative array methods */ + +class Item_func_assoc_array_first :public Item_handled_func +{ +public: + Item_func_assoc_array_first(THD *thd, Item *array) + :Item_handled_func(thd, array) {} + 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; + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Item_func_assoc_array_last :public Item_handled_func +{ +public: + Item_func_assoc_array_last(THD *thd, Item *array) + :Item_handled_func(thd, array) {} + 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_func +{ +public: + Item_func_assoc_array_next(THD *thd, Item *array, Item *curr_key) + :Item_handled_func(thd, array, 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_func +{ +public: + Item_func_assoc_array_prior(THD *thd, Item *array, Item *curr_key) + :Item_handled_func(thd, array, 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_func +{ + bool check_arguments() const override + { return arg_count != 1; } +public: + Item_func_assoc_array_count(THD *thd, Item *array) + :Item_long_func(thd, array) {} + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("count") }; + return name; + } + longlong val_int() override; + 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_exists :public Item_long_func +{ + bool check_arguments() const override + { return arg_count != 2; } + +public: + Item_func_assoc_array_exists(THD *thd, Item *array, Item *key) + :Item_long_func(thd, array, key) {} + longlong val_int() override; + 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_long_func +{ + bool check_arguments() const override + { return arg_count < 1 || arg_count > 2; } + +public: + Item_func_assoc_array_delete(THD *thd, Item *array) + :Item_long_func(thd, array) + {} + Item_func_assoc_array_delete(THD *thd, Item *array, Item *key) + :Item_long_func(thd, array, key) + {} + longlong val_int() override; + 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 Interruptible_wait; Item *get_system_var(THD *thd, enum_var_type var_type, diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 0d16245c052aa..d66e04700606e 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1602,6 +1602,7 @@ String *Item_func_sformat::val_str(String *res) break; case TIME_RESULT: // TODO case ROW_RESULT: // TODO + case ASSOC_ARRAY_RESULT: // TODO default: DBUG_ASSERT(0); return NULL; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 3bbcfe50786eb..57626e68852a8 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2870,6 +2870,7 @@ void Item_sum_min_max::reset_field() } case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); } diff --git a/sql/item_windowfunc.cc b/sql/item_windowfunc.cc index f0a5737bdf73a..8e46c8ad6fe73 100644 --- a/sql/item_windowfunc.cc +++ b/sql/item_windowfunc.cc @@ -540,6 +540,7 @@ void Item_sum_hybrid_simple::reset_field() } case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); } } diff --git a/sql/lex.h b/sql/lex.h index d420430545f29..acc1824cfce0b 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)}, { "ACCESSIBLE", SYM(ACCESSIBLE_SYM)}, { "ACCOUNT", SYM(ACCOUNT_SYM)}, { "ACTION", SYM(ACTION)}, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index adb7227ed8282..2ccdf5bc34195 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -9165,12 +9165,8 @@ ER_BINLOG_UNSAFE_INSERT_TWO_KEYS spa "INSERT... ON DUPLICATE KEY UPDATE en una tabla con más de una UNIQUE KEY no es segura" sw "INSERT... ON DUPLICATE KEY UPDATE kwenye jedwali iliyo na zaidi ya UNIQUE KEY moja si salama" -ER_UNUSED_28 - chi "你永远不应该看到它" - eng "You should never see it" - geo "ამას ვერასდროს უნდა ხედავდეთ" - spa "Nunca debería vd de ver esto" - sw "Hupaswi kuiona kamwe" +ER_WRONG_TYPE_FOR_ASSOC_ARRAY_KEY + eng "Key specifier used with variable '%-.64s' are only allowed for associative array data types" ER_VERS_NOT_ALLOWED chi "系统版本的表%`s.%`s不允许" @@ -11272,14 +11268,10 @@ ER_SECURE_TRANSPORT_REQUIRED 08004 # MariaDB extra error numbers starts from 4000 skip-to-error-number 4000 -ER_UNUSED_26 0A000 - eng "This error never happens" - spa "Este error nunca ocurre" - sw "Hitilafu hii haifanyiki kamwe" -ER_UNUSED_27 - eng "This error never happens" - spa "Este error nunca ocurre" - sw "Hitilafu hii haifanyiki kamwe" +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_WITH_COL_WRONG_LIST chi "使用列列表并选择字段列表具有不同的列计数" eng "WITH column list and SELECT field list have different column counts" @@ -11898,10 +11890,8 @@ ER_VERS_DROP_PARTITION_INTERVAL spa "Sólo se pueden eliminar viejas particiones al rotar mediante INTERVAL" sw "Inaweza tu kuacha kizigeu cha zamani zaidi wakati wa kuzungushwa kwa INTERVAL" -ER_UNUSED_25 - eng "You should never see it" - spa "Nunca debería vd de ver esto" - sw "Hupaswi kuiona kamwe" +ER_NEED_NAMED_ASSOCIATION + eng "Initializing %s requires named association" WARN_VERS_PART_NON_HISTORICAL chi "分区%`s包含非历史数据" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 584badad5f193..23315ea5b85a5 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -166,6 +166,51 @@ bool Item_splocal_row_field::append_for_log(THD *thd, String *str) } +bool Item_splocal_assoc_array_element::append_for_log(THD *thd, String *str) +{ + if (fix_fields_if_needed(thd, NULL)) + return true; + + if (limit_clause_param) + return str->append_ulonglong(val_uint()); + + if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || + str->append(&m_name) || + str->append('[') || + ((m_key->val_str() && m_key->val_str()->ptr()) ? + str->append(*m_key->val_str()) : + str->append(NULL_clex_str)) || + str->append(']') || + str->append(STRING_WITH_LEN("',"))) + return true; + return append_value_for_log(thd, str) || str->append(')'); +} + + +bool Item_splocal_assoc_array_element_field::append_for_log(THD *thd, + String *str) +{ + if (fix_fields_if_needed(thd, NULL)) + return true; + + if (limit_clause_param) + return str->append_ulonglong(val_uint()); + + if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || + str->append(&m_name) || + str->append('[') || + ((m_key->val_str() && m_key->val_str()->ptr()) ? + str->append(*m_key->val_str()) : + str->append(NULL_clex_str)) || + str->append(']') || + str->append('.') || + str->append(&m_field_name) || + str->append(STRING_WITH_LEN("',"))) + return true; + return append_value_for_log(thd, str) || str->append(')'); +} + + /** Returns a combination of: - sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might @@ -3811,6 +3856,56 @@ sp_head::set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, } +/** + Similar to set_local_variable(), but for ASSOC ARRAY variable fields. +*/ + +bool +sp_head::set_local_variable_assoc_array(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, + sp_variable *spv, Item* key, + Item *val, LEX *lex, + const LEX_CSTRING &value_query) +{ + if (!(val= adjust_assignment_source(thd, val, NULL))) + return true; + + sp_instr_set_assoc_array_by_key *sp_set= new (thd->mem_root) + sp_instr_set_assoc_array_by_key( + instructions(), + spcont, rh, + spv->offset, + key, val, + lex, true, + value_query); + return sp_set == NULL || add_instr(sp_set); +} + + +bool +sp_head::set_local_variable_assoc_array_field(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, + sp_variable *spv, Item* key, + const LEX_CSTRING *field_name, + Item *val, LEX *lex, + const LEX_CSTRING &value_query) +{ + if (!(val= adjust_assignment_source(thd, val, NULL))) + return true; + + sp_instr_set_assoc_array_field_by_key *sp_set= + new (thd->mem_root) sp_instr_set_assoc_array_field_by_key(instructions(), + spcont, rh, + spv->name, + spv->offset, + key, *field_name, + val, + lex, true, + value_query); + return sp_set == NULL || add_instr(sp_set); +} + + bool sp_head::add_open_cursor(THD *thd, sp_pcontext *spcont, uint offset, sp_pcontext *param_spcont, List *parameters) @@ -3965,6 +4060,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 11f1dae5926c8..54322c2853ca4 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -439,6 +439,17 @@ class sp_head :private Query_arena, const LEX_CSTRING *field_name, Item *val, LEX *lex, const LEX_CSTRING &value_query); + bool set_local_variable_assoc_array(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, + sp_variable *spv, Item* key, + Item *val, LEX *lex, + const LEX_CSTRING &value_query); + bool set_local_variable_assoc_array_field(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, + sp_variable *spv, Item* key, + const LEX_CSTRING *field_name, + Item *val, LEX *lex, + const LEX_CSTRING &value_query); bool check_package_routine_end_name(const LEX_CSTRING &end_name) const; bool check_standalone_routine_end_name(const sp_name *end_name) const; bool check_group_aggregate_instructions_function() const; @@ -822,6 +833,11 @@ class sp_head :private Query_arena, 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 2002274107553..23d907c0a4b18 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -1223,6 +1223,88 @@ sp_instr_set_row_field_by_name::print(String *str) } +/* + sp_instr_set_assoc_array_by_key class functions +*/ + +int +sp_instr_set_assoc_array_by_key::exec_core(THD *thd, uint *nextp) +{ + int res= get_rcontext(thd)->set_variable_assoc_array_by_key(thd, m_offset, + m_key, + &m_value); + *nextp= m_ip + 1; + return res; +} + + +void +sp_instr_set_assoc_array_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(var->field_def.is_assoc_array()); + + 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(' '); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); +} + + +/* + sp_instr_set_assoc_array_field_by_key class functions +*/ + +int +sp_instr_set_assoc_array_field_by_key::exec_core(THD *thd, uint *nextp) +{ + int res= get_rcontext(thd)->set_variable_assoc_array_field_by_key(thd, + m_var_name, + m_offset, + m_key, + m_field_name, + &m_value); + *nextp= m_ip + 1; + return res; +} + + +void +sp_instr_set_assoc_array_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(var->field_def.is_assoc_array()); + + 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)); +} + + + /* sp_instr_set_trigger_field class functions */ diff --git a/sql/sp_instr.h b/sql/sp_instr.h index 87937d08d2015..8a1003560aa6f 100644 --- a/sql/sp_instr.h +++ b/sql/sp_instr.h @@ -738,6 +738,76 @@ class sp_instr_set_row_field_by_name : public sp_instr_set }; // class sp_instr_set_field_by_name : public sp_instr_set +/* + This class handles assignments of an associative array + TYPE t IS TABLE OF rec_t INDEX BY VARCHAR2(20); + arr t; + arr('key'):= rec_t(10, 20); +*/ + +class sp_instr_set_assoc_array_by_key : public sp_instr_set +{ + sp_instr_set_assoc_array_by_key(const sp_instr_set_assoc_array_by_key &); + void operator=(sp_instr_set_assoc_array_by_key &); + Item* m_key; + +public: + sp_instr_set_assoc_array_by_key(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, + uint offset, Item* key, + 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_key(key) + {} + + virtual ~sp_instr_set_assoc_array_by_key() = default; + + int exec_core(THD *thd, uint *nextp) override; + + void print(String *str) override; +}; // class sp_instr_set_assoc_array_by_key : public sp_instr_set + + +/* + This class handles assignments of an associative array + TYPE t IS TABLE OF rec_t INDEX BY VARCHAR2(20); + arr t; + arr('key'):= rec_t(10, 20); + arr('key').field:= 30; +*/ +class sp_instr_set_assoc_array_field_by_key : public sp_instr_set +{ + sp_instr_set_assoc_array_field_by_key(const sp_instr_set_assoc_array_field_by_key &); + void operator=(sp_instr_set_assoc_array_field_by_key &); + const LEX_CSTRING m_var_name; + Item* m_key; + const LEX_CSTRING m_field_name; + +public: + sp_instr_set_assoc_array_field_by_key(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, + const LEX_CSTRING &var_name, + uint offset, Item* key, + 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_var_name(var_name), + m_key(key), + m_field_name(field_name) + {} + + virtual ~sp_instr_set_assoc_array_field_by_key() = default; + + int exec_core(THD *thd, uint *nextp) override; + + void print(String *str) override; +}; // class sp_instr_set_assoc_array_field_by_key : public sp_instr_set + + /** Set NEW/OLD row field value instruction. Used in triggers. */ diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc index a1d6a1bc9d2cf..2df01aba77052 100644 --- a/sql/sp_pcontext.cc +++ b/sql/sp_pcontext.cc @@ -95,7 +95,8 @@ sp_pcontext::sp_pcontext() m_vars(PSI_INSTRUMENT_MEM), m_case_expr_ids(PSI_INSTRUMENT_MEM), m_conditions(PSI_INSTRUMENT_MEM), m_cursors(PSI_INSTRUMENT_MEM), m_handlers(PSI_INSTRUMENT_MEM), m_records(PSI_INSTRUMENT_MEM), - m_children(PSI_INSTRUMENT_MEM), m_scope(REGULAR_SCOPE) + m_assoc_arrays(PSI_INSTRUMENT_MEM), m_children(PSI_INSTRUMENT_MEM), + m_scope(REGULAR_SCOPE) { init(0, 0, 0); } @@ -108,7 +109,8 @@ sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope) m_vars(PSI_INSTRUMENT_MEM), m_case_expr_ids(PSI_INSTRUMENT_MEM), m_conditions(PSI_INSTRUMENT_MEM), m_cursors(PSI_INSTRUMENT_MEM), m_handlers(PSI_INSTRUMENT_MEM), m_records(PSI_INSTRUMENT_MEM), - m_children(PSI_INSTRUMENT_MEM), m_scope(scope) + m_assoc_arrays(PSI_INSTRUMENT_MEM), m_children(PSI_INSTRUMENT_MEM), + m_scope(scope) { init(prev->m_var_offset + prev->m_max_var_index, prev->current_cursor_count(), @@ -443,6 +445,41 @@ sp_record *sp_pcontext::find_record(const LEX_CSTRING *name, } +bool sp_pcontext::add_assoc_array(THD *thd, + const Lex_ident_column &name, + Column_definition *key, + Spvar_definition *value) +{ + sp_assoc_array *p= new (thd->mem_root) sp_assoc_array(name, key, value); + + if (p == NULL) + return true; + + return m_assoc_arrays.append(p); +} + + +sp_assoc_array *sp_pcontext::find_assoc_array(const LEX_CSTRING *name, + bool current_scope_only) const +{ + size_t i= m_assoc_arrays.elements(); + + while (i--) + { + sp_assoc_array *p= m_assoc_arrays.at(i); + + if (p->eq_name(name)) + { + return p; + } + } + + return (!current_scope_only && m_parent) ? + m_parent->find_assoc_array(name, false) : + NULL; +} + + 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 c88e96688cba5..908320fe9819c 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -344,6 +344,31 @@ class sp_record : public Sql_alloc } }; +/// This class represents 'DECLARE TYPE .. TABLE OF' statement. + +class sp_assoc_array : public Sql_alloc +{ +public: + /// Name of the associative aray + Lex_ident_column name; + Column_definition *key_def; + Spvar_definition *value_def; + +public: + sp_assoc_array(const Lex_ident_column &name_arg, + Column_definition *key_def_arg, + Spvar_definition *value_def_arg) + :Sql_alloc(), + name(name_arg), + key_def(key_def_arg), + value_def(value_def_arg) + { } + bool eq_name(const LEX_CSTRING *str) const + { + return name.streq(*str); + } +}; + /////////////////////////////////////////////////////////////////////////// @@ -736,7 +761,7 @@ class sp_pcontext : public Sql_alloc const Lex_ident_column &name, Row_definition_list *field) { - if (find_record(&name, true)) + if (unlikely(find_record(&name, true) || find_assoc_array(&name, true))) { my_error(ER_SP_DUP_DECL, MYF(0), name.str); return true; @@ -744,6 +769,31 @@ class sp_pcontext : public Sql_alloc return add_record(thd, name, field); } + ///////////////////////////////////////////////////////////////////////// + // Associative array. + ///////////////////////////////////////////////////////////////////////// + + bool add_assoc_array(THD *thd, + const Lex_ident_column &name, + Column_definition *key, + Spvar_definition *value); + + sp_assoc_array *find_assoc_array(const LEX_CSTRING *name, + bool current_scope_only) const; + + bool declare_assoc_array(THD *thd, + const Lex_ident_column &name, + Column_definition *key, + Spvar_definition *value) + { + if (unlikely(find_record(&name, true) || find_assoc_array(&name, true))) + { + my_error(ER_SP_DUP_DECL, MYF(0), name.str); + return true; + } + return add_assoc_array(thd, name, key, value); + } + private: /// Constructor for a tree node. /// @param prev the parent parsing context @@ -811,6 +861,9 @@ class sp_pcontext : public Sql_alloc /// Stack of records. Dynamic_array m_records; + /// Stack of associative array types. + Dynamic_array m_assoc_arrays; + /* In the below example the label <> has two meanings: - GOTO lab : must go before the beginning of the loop diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 03d581ae0db53..0a6445a54368e 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -385,16 +385,53 @@ bool sp_rcontext::init_var_items(THD *thd, for (uint idx= 0; idx < num_vars; ++idx, def= it++) { Field *field= m_var_table->field[idx]; - Field_row *field_row= dynamic_cast(field); - if (!(m_var_items[idx]= field_row ? - def->make_item_field_row(thd, field_row) : - new (thd->mem_root) Item_field(thd, field))) - return true; + + if (def->is_assoc_array()) + { + Item_field_assoc_array *item= new (thd->mem_root) + Item_field_assoc_array(thd, field); + if (!(m_var_items[idx]= item) || + item->set_array_def(thd, def->assoc_array_definition())) + return true; + } + else + { + Field_row *field_row= dynamic_cast(field); + if (!(m_var_items[idx]= field_row ? + def->make_item_field_row(thd, field_row) : + new (thd->mem_root) Item_field(thd, field))) + return true; + } } return false; } +bool Item_field_assoc_array::set_array_def(THD *thd, + Assoc_array_definition *def) +{ + DBUG_ASSERT(field); + + m_def= def; + Field_assoc_array *field_assoc_array= dynamic_cast(field); + if (!field_assoc_array) + return true; + + field_assoc_array->m_def= def; + return false; +} + + +uint Item_field_assoc_array::cols_for_elements() const +{ + if (m_def->m_value_def->is_row()) + { + return m_def->m_value_def->row_field_definitions()->elements; + } + return 0; +} + + bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) { DBUG_ASSERT(m_return_value_fld); @@ -647,6 +684,93 @@ Virtual_tmp_table *sp_rcontext::virtual_tmp_table_for_row(uint var_idx) } +int sp_rcontext::set_variable_assoc_array_by_key(THD *thd, + uint var_idx, + Item* key, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_assoc_array_by_key"); + DBUG_ASSERT(value); + DBUG_ASSERT(key); + + DBUG_ASSERT(get_variable(var_idx)->type() == Item::FIELD_ITEM); + DBUG_ASSERT(get_variable(var_idx)->cmp_type() == ASSOC_ARRAY_RESULT); + + if (key->fix_fields_if_needed(thd, &key)) + DBUG_RETURN(1); + + if (key->null_value) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, + MYF(0), + get_variable(var_idx)->name.str); + DBUG_RETURN(1); + } + + auto field_assoc_array= m_var_table->field[var_idx]; + Item *item= field_assoc_array->element_by_key(thd, key->val_str()); + if (!item) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + key->val_str()->ptr()); + DBUG_RETURN(1); + } + + Field *field= item->field_for_view_update()->field; + DBUG_ASSERT(field); + + DBUG_RETURN(thd->sp_eval_expr(field, value)); +} + + +int +sp_rcontext::set_variable_assoc_array_field_by_key(THD *thd, + const LEX_CSTRING &var_name, + uint var_idx, + Item *key, + const LEX_CSTRING &field_name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_assoc_array_field_by_key"); + DBUG_ASSERT(value); + DBUG_ASSERT(key); + + DBUG_ASSERT(get_variable(var_idx)->type() == Item::FIELD_ITEM); + DBUG_ASSERT(get_variable(var_idx)->cmp_type() == ASSOC_ARRAY_RESULT); + + if (key->fix_fields_if_needed(thd, &key)) + DBUG_RETURN(1); + + if (key->null_value) + { + my_error(ER_NULL_FOR_ASSOC_ARRAY_INDEX, + MYF(0), + get_variable(var_idx)->name.str); + DBUG_RETURN(1); + } + + auto field_assoc_array= m_var_table->field[var_idx]; + Item *item= ((const Field *)field_assoc_array)-> + element_by_key(thd, + key->val_str()); + if (!item) + { + my_error(ER_ASSOC_ARRAY_ELEM_NOT_FOUND, MYF(0), + key->val_str()->ptr()); + DBUG_RETURN(1); + } + + Field *field= field_assoc_array->get_field_by_key_and_name(thd, + var_name, + key->val_str(), + field_name); + if (!field) + DBUG_RETURN(1); + + DBUG_RETURN(thd->sp_eval_expr(field, value)); +} + + 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 e2e273e9489bc..528d1db8f0ada 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -184,6 +184,14 @@ class sp_rcontext : public Sql_alloc Item **value); int set_variable_row(THD *thd, uint var_idx, List &items); + int set_variable_assoc_array_by_key(THD *thd, uint var_idx, Item* key, + Item **value); + int set_variable_assoc_array_field_by_key(THD *thd, + const LEX_CSTRING &var_name, + uint var_idx, + Item *key, + const LEX_CSTRING &field_name, + Item **value); int set_parameter(THD *thd, uint var_idx, Item **value) { DBUG_ASSERT(var_idx < argument_count()); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 5b39e5931c44d..b0b6e20b9cc77 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3876,6 +3876,7 @@ void select_max_min_finder_subselect::set_op(const Type_handler *th) op= &select_max_min_finder_subselect::cmp_str; break; case ROW_RESULT: + case ASSOC_ARRAY_RESULT: // This case should never be chosen DBUG_ASSERT(0); op= 0; @@ -4038,15 +4039,27 @@ int select_dumpvar::prepare(List &list, SELECT_LEX_UNIT *u) m_var_sp_row= NULL; if (var_list.elements == 1 && - (mvsp= var_list.head()->get_my_var_sp()) && - mvsp->type_handler() == &type_handler_row) + (mvsp= var_list.head()->get_my_var_sp())) { - // SELECT INTO row_type_sp_variable - if (mvsp->get_rcontext(thd->spcont)->get_variable(mvsp->offset)->cols() != - list.elements) - goto error; - m_var_sp_row= mvsp; - return 0; + Item *item= mvsp->get_rcontext(thd->spcont)->get_variable(mvsp->offset); + if (mvsp->type_handler() == &type_handler_row) + { + // SELECT INTO row_type_sp_variable + if (item->cols() != list.elements) + goto error; + m_var_sp_row= mvsp; + return 0; + } + else if (mvsp->type_handler() == &type_handler_assoc_array && + item->cols_for_elements() != 0) + { + // SELECT INTO assoc_array_sp_variable + if (item->cols_for_elements() != list.elements) + goto error; + + m_var_sp_assoc_array= mvsp; + return 0; + } } // SELECT INTO variable list @@ -4447,6 +4460,13 @@ bool my_var_sp_row_field::set(THD *thd, Item *item) } +bool my_var_sp_assoc_array_element::set(THD *thd, Item *item) +{ + return get_rcontext(thd->spcont)-> + set_variable_assoc_array_by_key(thd, offset, m_key, &item); +} + + bool select_dumpvar::send_data_to_var_list(List &items) { DBUG_ENTER("select_dumpvar::send_data_to_var_list"); @@ -4472,10 +4492,19 @@ int select_dumpvar::send_data(List &items) my_message(ER_TOO_MANY_ROWS, ER_THD(thd, ER_TOO_MANY_ROWS), MYF(0)); DBUG_RETURN(1); } - if (m_var_sp_row ? - m_var_sp_row->get_rcontext(thd->spcont)-> - set_variable_row(thd, m_var_sp_row->offset, items) : - send_data_to_var_list(items)) + if (m_var_sp_row) + { + if (m_var_sp_row->get_rcontext(thd->spcont)-> + set_variable_row(thd, m_var_sp_row->offset, items)) + DBUG_RETURN(1); + } + else if (m_var_sp_assoc_array) + { + Item_row *item_row= new (thd->mem_root) Item_row(thd, items); + if (var_list.begin()->set(thd, item_row)) + DBUG_RETURN(1); + } + else if (send_data_to_var_list(items)) DBUG_RETURN(1); DBUG_RETURN(thd->is_error()); @@ -8750,6 +8779,34 @@ bool Qualified_column_ident::append_to(THD *thd, String *str) const } +Qualified_ident::Qualified_ident(THD *thd, Lex_ident_cli_st *a) + :ident(*a) +{ + parts[0].length= parts[1].length= 0; + parts[0].str= parts[1].str= NULL; +} + + +Qualified_ident::Qualified_ident(THD *thd, Lex_ident_cli_st *a, + Lex_ident_sys_st *b) + :ident(*a) +{ + parts[0]= *b; + parts[1].length= 0; + parts[1].str= NULL; +} + + +Qualified_ident::Qualified_ident(THD *thd, Lex_ident_cli_st *a, + Lex_ident_sys_st *b, + Lex_ident_sys_st *c) + :ident(*a) +{ + parts[0]= *b; + parts[1]= *c; +} + + #endif /* !defined(MYSQL_CLIENT) */ diff --git a/sql/sql_class.h b/sql/sql_class.h index 208e4ba10e6d8..a22c2e5ef085e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -7536,6 +7536,24 @@ class Qualified_column_ident: public Table_ident }; +class Qualified_ident: public Sql_alloc +{ +public: + Lex_ident_cli_st ident; + Lex_ident_sys_st parts[2]; +public: + Qualified_ident(THD *thd, Lex_ident_cli_st *a); + Qualified_ident(THD *thd, Lex_ident_cli_st *a, Lex_ident_sys_st *b); + Qualified_ident(THD *thd, Lex_ident_cli_st *a, Lex_ident_sys_st *b, + Lex_ident_sys_st *c); + + const char *pos() const + { + return ident.pos(); + } +}; + + // this is needed for user_vars hash class user_var_entry: public Type_handler_hybrid_field_type { @@ -7710,6 +7728,20 @@ class my_var_sp_row_field: public my_var_sp bool set(THD *thd, Item *val) override; }; +class my_var_sp_assoc_array_element: public my_var_sp +{ + Item *m_key; +public: + my_var_sp_assoc_array_element(const Sp_rcontext_handler *rcontext_handler, + const LEX_CSTRING *varname, Item *key, + uint var_idx, sp_head *s) + :my_var_sp(rcontext_handler, varname, var_idx, + &type_handler_assoc_array, s), + m_key(key) + { } + bool set(THD *thd, Item *val) override; +}; + class my_var_user: public my_var { public: my_var_user(const LEX_CSTRING *j) @@ -7721,11 +7753,13 @@ class my_var_user: public my_var { class select_dumpvar :public select_result_interceptor { ha_rows row_count; my_var_sp *m_var_sp_row; // Not NULL if SELECT INTO row_type_sp_variable + my_var_sp *m_var_sp_assoc_array; bool send_data_to_var_list(List &items); public: List var_list; select_dumpvar(THD *thd_arg) - :select_result_interceptor(thd_arg), row_count(0), m_var_sp_row(NULL) + :select_result_interceptor(thd_arg), row_count(0), m_var_sp_row(NULL), + m_var_sp_assoc_array(NULL) { var_list.empty(); } ~select_dumpvar() = default; int prepare(List &list, SELECT_LEX_UNIT *u) override; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 5bf41c2b86381..aa04913091264 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -6765,6 +6765,17 @@ bool LEX::sp_variable_declarations_finalize(THD *thd, int nvars, dflt_value_item, expr_str); } } + else if (cdef->type_handler() == &type_handler_assoc_array) + { + if (sp_assoc_array *spaa= + (sp_assoc_array *)cdef->get_attr_const_void_ptr(0)) { + return sp_variable_declarations_assoc_array_finalize(thd, nvars, + spaa->key_def, + spaa->value_def, + dflt_value_item, + expr_str); + } + } Column_definition tmp(*cdef); if (sphead->fill_spvar_definition(thd, &tmp)) @@ -6802,6 +6813,282 @@ bool LEX::sp_variable_declarations_rec_finalize(THD *thd, int nvars, dflt_value_item, expr_str); } + +static int cstring_tree_cmp(void *arg, const char *lhs, + const char *rhs) +{ + return strcmp(lhs, rhs); +} + + +bool LEX::sp_check_assoc_array_args(const LEX_CSTRING& type_name, + List &list) +{ + /* + Use a TREE to ensure that all arguments have unique names. + */ + TREE tree; + init_tree(&tree, 0, 0, + sizeof(const char*), (qsort_cmp2)cstring_tree_cmp, + NULL, NULL, + MYF(MY_THREAD_SPECIFIC)); + + List_iterator it(list); + for (Item *item= it++; item; item= it++) + { + if (unlikely(!item->is_explicit_name())) + { + my_error(ER_NEED_NAMED_ASSOCIATION, MYF(0), type_name.str); + delete_tree(&tree, 0); + return true; + } + + const char *name= item->name.str; + if (unlikely(tree_search(&tree, (void *)name, nullptr))) + { + my_error(ER_DUP_UNKNOWN_IN_INDEX, MYF(0), name); + delete_tree(&tree, 0); + return true; + } + + tree_insert(&tree, (void *)name, 0, nullptr); + } + + delete_tree(&tree, 0); + return false; +} + + +bool LEX::sp_set_assoc_array(THD *thd, const Qualified_ident *ident, + Item *item, const LEX_CSTRING &expr_str) +{ + sp_pcontext *ctx; + const Sp_rcontext_handler *rh; + Item *key= assoc_key; + + Lex_ident_sys name(thd, &ident->ident); + if (unlikely(!name.str)) + return true; + + sp_variable *spv= find_variable(&name, &ctx, &rh); + if (!spv->field_def.is_assoc_array()) + { + my_error(ER_WRONG_TYPE_FOR_ASSOC_ARRAY_KEY, MYF(0), name.str); + return true; + } + + return spv ? sphead->set_local_variable_assoc_array(thd, ctx, + rh, spv, key, + item, this, + expr_str) : + true; +} + + +bool LEX::sp_set_assoc_array_field(THD *thd, const Qualified_ident *ident, + const Lex_ident_sys_st *field_name, + Item *item, const LEX_CSTRING &expr_str) +{ + DBUG_ASSERT(field_name); + + Item *key= assoc_key; + + sp_pcontext *ctx; + const Sp_rcontext_handler *rh; + Lex_ident_sys name(thd, &ident->ident); + if (unlikely(!name.str)) + return true; + + sp_variable *spv= find_variable(&name, &ctx, &rh); + + if (!spv->field_def.is_assoc_array()) + { + my_error(ER_WRONG_TYPE_FOR_ASSOC_ARRAY_KEY, MYF(0), name.str); + return true; + } + + return spv ? sphead->set_local_variable_assoc_array_field(thd, ctx, + rh, spv, key, + field_name, + item, this, + expr_str) : + true; +} + + +bool LEX::sp_set_assoc_array_copy_key(LEX *sub_lex) +{ + if (value_list.elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "ASSOC_ARRAY KEY", + ErrConvDQName(sphead).ptr(), + 1, value_list.elements); + return true; + } + + sub_lex->assoc_key= &*value_list.begin(); + return false; +} + + +Item *LEX::sp_get_assoc_array_key(THD *thd, Item_splocal* array, + List *args, bool is_first) +{ + DBUG_ASSERT(array); + + if (args) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), is_first ? "FIRST" : "LAST", + ErrConvDQName(sphead).ptr(), + 0, args->elements); + return NULL; + } + + return is_first ? + (Item *)new (thd->mem_root) Item_func_assoc_array_first(thd, array) : + (Item *)new (thd->mem_root) Item_func_assoc_array_last(thd, array); +} + + +Item *LEX::sp_get_assoc_array_next_or_prior(THD *thd, + Item_splocal* array, + List *args, bool is_next) +{ + DBUG_ASSERT(array); + + if (!args || args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), is_next ? "NEXT" : "PRIOR", + ErrConvDQName(sphead).ptr(), + 1, args ? args->elements : 0); + return NULL; + } + + Item_args args_item(thd, *args); + return is_next ? (Item *) + new (thd->mem_root) + Item_func_assoc_array_next(thd, array, + args_item.arguments()[0]) : + (Item *) + new (thd->mem_root) + Item_func_assoc_array_prior(thd, array, + args_item.arguments()[0]); +} + + +Item *LEX::sp_get_assoc_array_count(THD *thd, Item_splocal* array, + List *args) +{ + DBUG_ASSERT(array); + + if (args) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "COUNT", + ErrConvDQName(sphead).ptr(), + 0, args->elements); + return NULL; + } + + return new (thd->mem_root) Item_func_assoc_array_count(thd, array); +} + + +Item *LEX::sp_get_assoc_array_exists(THD *thd, + Item_splocal* array, + List *args) +{ + DBUG_ASSERT(array); + + if (!args || args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "EXISTS", + ErrConvDQName(sphead).ptr(), + 1, args ? args->elements : 0); + return NULL; + } + + Item_args args_item(thd, *args); + return new (thd->mem_root) + Item_func_assoc_array_exists(thd, + array, + args_item.arguments()[0]); +} + + +Item *LEX::sp_get_assoc_array_delete(THD *thd, + Item_splocal* array, + List *args) +{ + DBUG_ASSERT(array); + + if (args) + { + if (args->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "DELETE", + ErrConvDQName(sphead).ptr(), + 1, args->elements); + return NULL; + } + + Item_args args_item(thd, *args); + return new (thd->mem_root) + Item_func_assoc_array_delete(thd, array, + args_item.arguments()[0]); + } + else + return new (thd->mem_root) Item_func_assoc_array_delete(thd, array); +} + + +bool LEX::sp_variable_declarations_assoc_array_finalize(THD *thd, int nvars, + Column_definition *key_def, + Spvar_definition *value_def, + Item *def, + const LEX_CSTRING &expr_str) +{ + DBUG_ASSERT(key_def); + DBUG_ASSERT(value_def); + + // 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 (sp_record *sprec= + (sp_record *)value_def->get_attr_const_void_ptr(0)) + { + if (sphead->row_fill_field_definitions(thd, sprec->field)) + return true; + + value_def->set_row_field_definitions(sprec->field); + } + } + + if (sphead->fill_spvar_definition(thd, value_def)) + return true; + + Assoc_array_definition* aa_def = + new (thd->mem_root) Assoc_array_definition(key_def, value_def); + + for (uint i= 0 ; i < (uint) nvars ; i++) + { + sp_variable *spvar= spcont->get_last_context_variable((uint) nvars - 1 - i); + spvar->field_def.set_assoc_array_definition(aa_def); + if (sphead->fill_spvar_definition(thd, &spvar->field_def, &spvar->name)) + return true; + } + + if (sp_variable_declarations_set_default(thd, nvars, def, + expr_str)) + return true; + + spcont->declare_var_boundary(0); + return sphead->restore_lex(thd); +} + bool LEX::sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *dflt_value_item, @@ -8444,6 +8731,87 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, } +Item_splocal *LEX::create_item_spvar_assoc_array_element(THD *thd, + const Lex_ident_sys_st *ca, + List *item_list) +{ + sp_variable *spv; + Lex_ident_sys a(thd, ca); + + if (!item_list || item_list->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "ASSOC_ARRAY_ELEMENT", + ErrConvDQName(sphead).ptr(), + 1, !item_list ? 0 : item_list->elements); + return NULL; + } + + const Sp_rcontext_handler *rh; + if (unlikely(!(spv= find_variable(&a, &rh)))) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), a.str); + return NULL; + } + + Item_args args(thd, *item_list); + Item *key= args.arguments()[0]; + + Item_splocal *item= new (thd->mem_root) + Item_splocal_assoc_array_element(thd, rh, ca, + key, spv->offset, + &type_handler_null); +#ifdef DBUG_ASSERT_EXISTS + if (item) + { + item->m_sp= sphead; + } +#endif + + return item; +} + + +Item_splocal *LEX::create_item_spvar_assoc_array_element_field(THD *thd, + const Lex_ident_sys_st *ca, + List *item_list, + const Lex_ident_sys_st *cb) +{ + sp_variable *spv; + Lex_ident_sys a(thd, ca); + + if (item_list->elements != 1) + { + my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "ASSOC_ARRAY_ELEMENT", + ErrConvDQName(sphead).ptr(), + 1, item_list->elements); + return NULL; + } + + const Sp_rcontext_handler *rh; + if (unlikely(!(spv= find_variable(&a, &rh)))) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), a.str); + return NULL; + } + + Item_args args(thd, *item_list); + Item *key= args.arguments()[0]; + + Item_splocal *item= new (thd->mem_root) + Item_splocal_assoc_array_element_field(thd, rh, ca, key, + cb, spv->offset, + &type_handler_null); +#ifdef DBUG_ASSERT_EXISTS + if (item) + { + item->m_sp= sphead; + } +#endif + + return item; +} + + my_var *LEX::create_outvar(THD *thd, const LEX_CSTRING *name) { const Sp_rcontext_handler *rh; @@ -8478,6 +8846,32 @@ my_var *LEX::create_outvar(THD *thd, NULL /* EXPLAIN */; } +my_var *LEX::create_outvar(THD *thd, + const LEX_CSTRING *name, + Item *key) +{ + DBUG_ASSERT(key); + + 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; + } + + if (unlikely(!t->field_def.is_assoc_array())) + { + my_error(ER_WRONG_TYPE_FOR_ASSOC_ARRAY_KEY, MYF(0), name->str); + return NULL; + } + + return result ? + new (thd->mem_root) my_var_sp_assoc_array_element(rh, name, key, t->offset, + sphead) : + NULL /* EXPLAIN */; +} + Item *LEX::create_item_func_nextval(THD *thd, Table_ident *table_ident) { @@ -8544,6 +8938,59 @@ Item *LEX::create_item_func_setval(THD *thd, Table_ident *table_ident, } +Item *LEX::sp_get_assoc_array_method(THD *thd, + const Lex_ident_cli_st *ca, + const Lex_ident_cli_st *cb, + List *args) +{ + DBUG_ASSERT(ca); + DBUG_ASSERT(cb); + + Item_splocal *array= create_item_for_sp_var(ca, NULL); + if (unlikely(array == NULL)) + return NULL; + + return sp_get_assoc_array_method(thd, array, cb, args); +} + + +Item *LEX::sp_get_assoc_array_method(THD *thd, + Item_splocal* array, + const Lex_ident_cli_st *method_name, + List *args) +{ + DBUG_ASSERT(method_name); + + Lex_ident_sys b(thd, method_name); + if (b.length == 5) + { + if (Lex_ident_column(b).streq("COUNT"_Lex_ident_column)) + return sp_get_assoc_array_count(thd, array, args); + else if (Lex_ident_column(b).streq("FIRST"_Lex_ident_column)) + return sp_get_assoc_array_key(thd, array, args, true); + else if (Lex_ident_column(b).streq("PRIOR"_Lex_ident_column)) + return sp_get_assoc_array_next_or_prior(thd, array, args, false); + } + else if (b.length == 4) + { + if (Lex_ident_column(b).streq("LAST"_Lex_ident_column)) + return sp_get_assoc_array_key(thd, array, args, false); + else if (Lex_ident_column(b).streq("NEXT"_Lex_ident_column)) + return sp_get_assoc_array_next_or_prior(thd, array, args, true); + } + else if (b.length == 6) + { + if (Lex_ident_column(b).streq("EXISTS"_Lex_ident_column)) + return sp_get_assoc_array_exists(thd, array, args); + else if (Lex_ident_column(b).streq("DELETE"_Lex_ident_column)) + return sp_get_assoc_array_delete(thd, array, args); + } + + my_error(ER_BAD_FIELD_ERROR, MYF(0), method_name->str); + return NULL; +} + + Item *LEX::create_item_ident(THD *thd, const Lex_ident_cli_st *ca, const Lex_ident_cli_st *cb) @@ -8558,11 +9005,15 @@ 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); + else if ((thd->variables.sql_mode & MODE_ORACLE) && spv->field_def.is_assoc_array()) + return sp_get_assoc_array_method(thd, ca, cb, NULL); + } if ((thd->variables.sql_mode & MODE_ORACLE) && b.length == 7) { @@ -8819,6 +9270,26 @@ 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) +{ + Lex_ident_sys name(thd, &ident->ident); + if (unlikely(!name.str)) + return true; + + if (unlikely(ident->parts[1].length)) + { + thd->parse_error(ER_SYNTAX_ERROR, ident->ident.pos()); + return true; + } + + if (ident->parts[0].length) + return set_variable(&name, &ident->parts[0], item, expr_str); + + return set_variable(&name, item, expr_str); +} + + bool LEX::set_default_system_variable(enum_var_type var_type, const Lex_ident_sys_st *name, Item *val) @@ -9520,7 +9991,6 @@ bool LEX::call_statement_start(THD *thd, sp_name *name) Database_qualified_name pkgname; const Sp_handler *sph= &sp_handler_procedure; sql_command= SQLCOM_CALL; - value_list.empty(); if (unlikely(sph->sp_resolve_package_routine(thd, thd->lex->sphead, name, &sph, &pkgname))) return true; @@ -9582,6 +10052,25 @@ bool LEX::call_statement_start(THD *thd, } +bool LEX::call_statement_start(THD *thd, const Qualified_ident *ident) +{ + Lex_ident_sys name(thd, &ident->ident); + if (unlikely(!name.str)) + return true; + + if (ident->parts[1].length) + { + return call_statement_start(thd, &name, &ident->parts[0], &ident->parts[1]); + } + else if (ident->parts[0].length) + { + return call_statement_start(thd, &name, &ident->parts[0]); + } + + return call_statement_start(thd, &name); +} + + sp_package *LEX::get_sp_package() const { return sphead ? sphead->get_package() : NULL; @@ -12105,6 +12594,23 @@ Spvar_definition *LEX::row_field_name(THD *thd, const Lex_ident_sys_st &name) } +Column_definition *LEX::assoc_array_def_init(THD *thd, const Lex_ident_sys_st &name) +{ + Spvar_definition *res; + if (unlikely(check_string_char_length(&name, 0, NAME_CHAR_LEN, + system_charset_info, 1))) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), name.str); + return NULL; + } + if (unlikely(!(res= new (thd->mem_root) Spvar_definition()))) + return NULL; + + init_last_field(res, &name); + return res; +} + + Item * Lex_cast_type_st::create_typecast_item_or_error(THD *thd, Item *item) const { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 47dfdc048ad7f..1b9d489d52971 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3205,6 +3205,14 @@ struct LEX: public Query_tables_list List var_list; List stmt_var_list; //SET_STATEMENT values List old_var_list; // SET STATEMENT old values + + /* + Associative array key used in assignement statement + i.e. assoc_array(key):= value or + assoc_array(key).field:= value + */ + Item *assoc_key= nullptr; + Qualified_ident *assoc_ident= nullptr; private: Query_arena_memroot *arena_for_set_stmt; MEM_ROOT *mem_root_for_set_stmt; @@ -3853,6 +3861,7 @@ struct LEX: public Query_tables_list 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); sp_variable *find_variable(const LEX_CSTRING *name, sp_pcontext **ctx, const Sp_rcontext_handler **rh) const; @@ -3867,6 +3876,8 @@ struct LEX: public Query_tables_list 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, @@ -3878,6 +3889,38 @@ struct LEX: public Query_tables_list Row_definition_list *src_row, Item *def, const LEX_CSTRING &expr_str); + bool sp_check_assoc_array_args(const LEX_CSTRING& type_name, + List &list); + bool sp_set_assoc_array(THD *thd, const Qualified_ident *ident, Item *item, + const LEX_CSTRING &expr_str); + bool sp_set_assoc_array_field(THD *thd, const Qualified_ident *ident, + const Lex_ident_sys_st *field_name, + Item *item, const LEX_CSTRING &expr_str); + bool sp_set_assoc_array_copy_key(LEX *sub_lex); + + Item *sp_get_assoc_array_method(THD *thd, + const Lex_ident_cli_st *ca, + const Lex_ident_cli_st *cb, + List *args); + Item *sp_get_assoc_array_method(THD *thd, + Item_splocal* array, + const Lex_ident_cli_st *method_name, + List *args); + Item *sp_get_assoc_array_key(THD *thd, Item_splocal* array, + List *args, bool is_first); + Item *sp_get_assoc_array_next_or_prior(THD *thd, Item_splocal* array, + List *args, bool is_next); + Item *sp_get_assoc_array_count(THD *thd, Item_splocal* array, + List *args); + Item *sp_get_assoc_array_exists(THD *thd, Item_splocal* array, + List *args); + Item *sp_get_assoc_array_delete(THD *thd, Item_splocal* array, + List *args); + bool sp_variable_declarations_assoc_array_finalize(THD *thd, int nvars, + Column_definition *key_def, + Spvar_definition *value_def, + Item *def, + const LEX_CSTRING &expr_str); bool sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *def, @@ -4060,6 +4103,13 @@ struct LEX: public Query_tables_list return a.is_null() ? NULL : create_item_ident(thd, &a, &b, &c); } + Item_splocal *create_item_spvar_assoc_array_element(THD *thd, + const Lex_ident_sys_st *ca, + List *args); + Item_splocal *create_item_spvar_assoc_array_element_field(THD *thd, + const Lex_ident_sys_st *ca, + List *item_list, + const Lex_ident_sys_st *cb); /* Create an item for "NEXT VALUE FOR sequence_name" */ @@ -4170,6 +4220,10 @@ struct LEX: public Query_tables_list const LEX_CSTRING *var_name, const LEX_CSTRING *field_name); + my_var *create_outvar(THD *thd, + const LEX_CSTRING *name, + Item *key); + 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, @@ -4826,6 +4880,8 @@ struct LEX: public Query_tables_list 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); + Column_definition *assoc_array_def_init(THD *thd, + const Lex_ident_sys_st &name); bool set_field_type_udt(Lex_field_type_st *type, const LEX_CSTRING &name, @@ -5105,6 +5161,7 @@ class sp_lex_set_var: public sp_lex_local var_list.empty(); autocommit= 0; option_type= oldlex->option_type; // Inherit from the outer lex + assoc_ident= oldlex->assoc_ident; } }; diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index fdd35d7cef5d3..a811936c44a75 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -2815,6 +2815,7 @@ static inline int part_val_int(Item *item_expr, longlong *result) case REAL_RESULT: case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); break; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c881957c118f5..0001696236256 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -21213,6 +21213,7 @@ Field *Item_sum::create_tmp_field(MEM_ROOT *root, bool group, TABLE *table) new_field= tmp_table_field_from_field_type(root, table); break; case ROW_RESULT: + case ASSOC_ARRAY_RESULT: // This case should never be chosen DBUG_ASSERT(0); new_field= 0; diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 2c0d2b4bf250b..59f2a2fd20bc6 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -122,6 +122,7 @@ bool DTCollation::merge_collation(Sql_used *used, Named_type_handler type_handler_row("row"); +Named_type_handler type_handler_assoc_array("associative array"); Named_type_handler type_handler_null("null"); @@ -259,6 +260,50 @@ const Type_collection *Type_handler_row::type_collection() const } +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 + { + DBUG_ASSERT(a == &type_handler_assoc_array); + DBUG_ASSERT(b == &type_handler_assoc_array); + return &type_handler_assoc_array; + } + 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_data::init() { return type_collection_geometry.init(this); @@ -690,6 +735,7 @@ Interval_DDhhmmssff::Interval_DDhhmmssff(THD *thd, Status *st, { switch (item->cmp_type()) { case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); time_type= MYSQL_TIMESTAMP_NONE; break; @@ -779,6 +825,7 @@ uint Interval_DDhhmmssff::fsp(THD *thd, Item *item) case DECIMAL_RESULT: return MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS); case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); return 0; case STRING_RESULT: @@ -1769,6 +1816,13 @@ const Type_handler *Type_handler_row::type_handler_for_comparison() const return &type_handler_row; } + +const Type_handler *Type_handler_assoc_array::type_handler_for_comparison() const +{ + return &type_handler_assoc_array; +} + + /***************************************************************************/ const Type_handler * @@ -1894,7 +1948,8 @@ aggregate_for_result(const LEX_CSTRING &funcname, Item **items, uint nitems, { bool bit_and_non_bit_mixture_found= false; uint32 max_display_length; - if (!nitems || items[0]->result_type() == ROW_RESULT) + if (!nitems || items[0]->result_type() == ROW_RESULT || + items[0]->result_type() == ASSOC_ARRAY_RESULT) { DBUG_ASSERT(0); set_handler(&type_handler_null); @@ -1970,6 +2025,8 @@ Type_collection_std::aggregate_for_comparison(const Type_handler *ha, return &type_handler_slonglong; if (a == ROW_RESULT || b == ROW_RESULT) return &type_handler_row; + if (a == ASSOC_ARRAY_RESULT || b == ASSOC_ARRAY_RESULT) + return &type_handler_assoc_array; if (a == TIME_RESULT || b == TIME_RESULT) { if ((a == TIME_RESULT) + (b == TIME_RESULT) == 1) @@ -2062,6 +2119,8 @@ Type_collection_std::aggregate_for_min_max(const Type_handler *ha, Item_result b= hb->cmp_type(); DBUG_ASSERT(a != ROW_RESULT); // Disallowed by check_cols() in fix_fields() DBUG_ASSERT(b != ROW_RESULT); // Disallowed by check_cols() in fix_fields() + DBUG_ASSERT(a != ASSOC_ARRAY_RESULT); + DBUG_ASSERT(b != ASSOC_ARRAY_RESULT); if (a == STRING_RESULT && b == STRING_RESULT) return Type_collection_std::aggregate_for_result(ha, hb); @@ -2313,6 +2372,7 @@ Type_handler::handler_by_log_event_data_type(THD *thd, case STRING_RESULT: case ROW_RESULT: case TIME_RESULT: + case ASSOC_ARRAY_RESULT: break; case REAL_RESULT: return &type_handler_double; @@ -6388,6 +6448,40 @@ String *Type_handler_row:: } +String *Type_handler_assoc_array:: + print_item_value(THD *thd, Item *item, String *str) const +{ + CHARSET_INFO *cs= thd->variables.character_set_client; + StringBuffer val(cs); + String key; + str->append(STRING_WITH_LEN("ASSOC_ARRAY(")); + + uint i= 0; + if (!item->get_key(&key, true)) + { + do + { + if (i > 0) + str->append(','); + + Item *elem= item->element_by_key(thd, &key); + String *tmp= elem->type_handler()->print_item_value(thd, elem, &val); + if (tmp) + str->append(*tmp); + else + str->append(NULL_clex_str); + + i++; + } while (!item->get_next_key(&key, &key)); + } + 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. @@ -8318,6 +8412,19 @@ Field *Type_handler_row:: } +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); +} + + Field *Type_handler_olddecimal:: make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root, const LEX_CSTRING *name, diff --git a/sql/sql_type.h b/sql/sql_type.h index 829bf4f224591..224a157edc649 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -5027,6 +5027,73 @@ class Type_handler_row: public Type_handler }; +/* + Special handler associative arrays +*/ +class Type_handler_assoc_array: public Type_handler_row +{ +public: + const Type_collection *type_collection() const override; + protocol_send_type_t protocol_send_type() const override + { + MY_ASSERT_UNREACHABLE(); + return PROTOCOL_SEND_STRING; + } + Item_result result_type() const override + { + return ASSOC_ARRAY_RESULT; + } + Item_result cmp_type() const override + { + return ASSOC_ARRAY_RESULT; + } + const Type_handler *type_handler_for_comparison() 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; + } + 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; +}; + + /* A common parent class for numeric data type handlers */ @@ -7859,6 +7926,7 @@ class Named_type_handler : public TypeHandler }; extern Named_type_handler type_handler_row; +extern Named_type_handler type_handler_assoc_array; extern Named_type_handler type_handler_null; extern Named_type_handler type_handler_float; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6a231c32c5998..5db5233b8f9e2 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -244,6 +244,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() /* pointers */ Lex_ident_sys *ident_sys_ptr; Create_field *create_field; + Column_definition *column_definition; Spvar_definition *spvar_definition; Row_definition_list *spvar_definition_list; const Type_handler *type_handler; @@ -276,6 +277,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() TABLE_LIST *table_list; Table_ident *table; Qualified_column_ident *qualified_column_ident; + Qualified_ident *qualified_ident; char *simple_string; const char *const_simple_string; chooser_compare_func_creator boolfunc2creator; @@ -441,6 +443,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 */ /* @@ -1342,6 +1345,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_func @@ -1405,6 +1413,11 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type optionally_qualified_column_ident +%ifdef ORACLE +%type + optionally_qualified_directly_assignable +%endif + %type remember_name remember_end remember_tok_start @@ -1429,7 +1442,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 @@ -1438,6 +1451,14 @@ 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_result_field + %type binary opt_binary @@ -1582,6 +1603,11 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); execute_using execute_params +%ifdef ORACLE +%type named_expr named_expr_conflict +%type named_expr_list +%endif + %type sp_cursor_stmt_lex sp_cursor_stmt @@ -1982,6 +2008,7 @@ rule: %type reserved_keyword_udt_param_type %else %type set_assign +%type set_assign_assoc_key %type sp_opt_inout %type sp_tail_standalone %type sp_labelable_stmt @@ -3536,7 +3563,7 @@ 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); @@ -6450,6 +6477,7 @@ field_type_all: } ; +%ifdef ORACLE field_type_all_with_record: field_type_all_builtin { @@ -6459,17 +6487,53 @@ field_type_all_with_record: { sp_record *sprec = NULL; if (Lex->spcont) - sprec = Lex->spcont->find_record(&$1, false); + { + sprec= Lex->spcont->find_record(&$1, false); + if (sprec) { + $$.set(&type_handler_row, NULL); + Lex->last_field->set_attr_const_void_ptr(0, sprec); + } + } if (sprec == NULL) { 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 + { + sp_record *sprec= NULL; + sp_assoc_array *spassoc= NULL; + if (Lex->spcont) { - $$.set(&type_handler_row, NULL); - Lex->last_field->set_attr_const_void_ptr(0, sprec); + sprec= Lex->spcont->find_record(&$1, false); + if (sprec) { + $$.set(&type_handler_row, NULL); + Lex->last_field->set_attr_const_void_ptr(0, sprec); + } + else + { + spassoc= Lex->spcont->find_assoc_array(&$1, false); + if (spassoc) { + $$.set(&type_handler_assoc_array, NULL); + Lex->last_field->set_attr_const_void_ptr(0, spassoc); + } + } + } + + if (sprec == NULL && spassoc == NULL) + { + if (Lex->set_field_type_udt(&$$, $1, $2)) + MYSQL_YYABORT; } } ; @@ -10906,12 +10970,16 @@ function_call_generic: $$= udf; #endif } - opt_udf_expr_list ')' + opt_udf_expr_list ')' opt_result_field { const Type_handler *h; Create_func *builder; Item *item= NULL; sp_record* rec= NULL; + sp_assoc_array* assoc= NULL; + sp_variable *spv= NULL; + + bool allow_field_accessor= false; if (unlikely(Lex_ident_routine::check_name_with_error($1))) MYSQL_YYABORT; @@ -10942,6 +11010,29 @@ function_call_generic: { item= new (thd->mem_root) Item_row(thd, *$4); } + else if (Lex->spcont && + (assoc = Lex->spcont->find_assoc_array(&$1, false))) + { + if (unlikely($4 && Lex->sp_check_assoc_array_args($1, *$4))) + MYSQL_YYABORT; + + if (unlikely($4 == NULL)) + item= new (thd->mem_root) Item_assoc_array(thd); + else + item= new (thd->mem_root) Item_assoc_array(thd, *$4); + } + else if (Lex->spcont && + (spv= Lex->spcont->find_variable(&$1, false)) && + spv->field_def.is_assoc_array()) + { + if ($6) + item= Lex->create_item_spvar_assoc_array_element_field(thd, &$1, $4, $6); + else + item= Lex->create_item_spvar_assoc_array_element(thd, &$1, $4); + + // Only allow 'result accessors' for associative arrays + allow_field_accessor= true; + } else { #ifdef HAVE_DLOPEN @@ -10966,6 +11057,12 @@ function_call_generic: } } + if ($6 && !allow_field_accessor) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), $6->str, $1); + MYSQL_YYABORT; + } + if (unlikely(! ($$= item))) MYSQL_YYABORT; } @@ -10989,8 +11086,24 @@ function_call_generic: } | ident_cli '.' ident_cli '(' opt_expr_list ')' { - if (unlikely(!($$= Lex->make_item_func_call_generic(thd, &$1, &$3, $5)))) - MYSQL_YYABORT; + sp_variable *spv; + if (Lex->spcont && (spv= Lex->spcont->find_variable(&$1, false)) && + spv->field_def.is_assoc_array()) + { + if (unlikely(!($$= Lex->sp_get_assoc_array_method(thd, + &$1, + &$3, + $5)))) + MYSQL_YYABORT; + } + else + { + if (unlikely(!($$= Lex->make_item_func_call_generic(thd, + &$1, + &$3, + $5)))) + MYSQL_YYABORT; + } } | ident_cli '.' ident_cli '.' ident_cli '(' opt_expr_list ')' { @@ -11036,6 +11149,13 @@ function_call_generic: */ ; +opt_result_field: + /* empty */ + { $$= NULL; } + | '.' ident_sys_alloc + { $$= $2; } + ; + fulltext_options: opt_natural_language_mode opt_query_expansion { $$= $1 | $2; } @@ -11056,6 +11176,9 @@ opt_query_expansion: opt_udf_expr_list: /* empty */ { $$= NULL; } | udf_expr_list { $$= $1; } +%ifdef ORACLE + | named_expr_list { $$= $1; } +%endif ; udf_expr_list: @@ -13290,6 +13413,11 @@ select_outvar: if (unlikely(!($$= Lex->create_outvar(thd, &$1, &$3)) && Lex->result)) MYSQL_YYABORT; } + | ident '(' expr ')' + { + if (unlikely(!($$= Lex->create_outvar(thd, &$1, $3)) && Lex->result)) + MYSQL_YYABORT; + } ; into: @@ -19202,48 +19330,120 @@ sp_unlabeled_block_not_atomic: statement: verb_clause | set_assign + | set_assign_assoc_key ; -sp_statement: - statement - | ident_cli_directly_assignable +opt_sp_cparam_list_direct: + /* Empty */ + | '(' opt_sp_cparams ')' + ; + +direct_call_or_assoc_init: + optionally_qualified_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; + sp_variable *spv; + Lex_ident_sys a(thd, &$1->ident); + if (Lex->spcont && + (spv= Lex->spcont->find_variable(&a, false))) + { + if (likely(spv->field_def.is_assoc_array())) + { + Lex->assoc_ident= $1; + + thd->where= THD_WHERE::USE_WHERE_STRING; + thd->where_str= "ASSOC ARRAY KEY"; + } + else + { + thd->parse_error(); + MYSQL_YYABORT; + } + } + else + { + Lex->assoc_ident= NULL; + + // Direct procedure call (without the CALL keyword) + if (unlikely(Lex->call_statement_start(thd, $1))) + MYSQL_YYABORT; + + thd->where= THD_WHERE::USE_WHERE_STRING; + thd->where_str= "CALL"; + } } - opt_sp_cparam_list + ; + +direct_call_statement: + direct_call_or_assoc_init opt_sp_cparam_list_direct { - if (Lex->check_cte_dependencies_and_resolve_references()) - MYSQL_YYABORT; + if (!Lex->assoc_ident) + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } } - | ident_cli_directly_assignable '.' ident + ; + +set_assign_assoc_key: + direct_call_or_assoc_init '(' opt_sp_cparams ')' SET_VAR { - Lex_ident_sys tmp(thd, &$1); - if (unlikely(!tmp.str) || - unlikely(Lex->call_statement_start(thd, &tmp, &$3))) + if (unlikely(Lex->assoc_ident == NULL)) + { + thd->parse_error(); + MYSQL_YYABORT; + } + + LEX *lex= Lex; + lex->set_stmt_init(); + if (sp_create_assignment_lex(thd, Lex->assoc_ident->pos())) + MYSQL_YYABORT; + if (lex->sp_set_assoc_array_copy_key(Lex)) MYSQL_YYABORT; } - opt_sp_cparam_list + set_expr_or_default { - if (Lex->check_cte_dependencies_and_resolve_references()) + if (unlikely(Lex->sp_set_assoc_array(thd, + Lex->assoc_ident, + $7.expr, + $7.expr_str)) || + unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY, + false))) MYSQL_YYABORT; } - | ident_cli_directly_assignable '.' ident '.' ident + | direct_call_or_assoc_init '(' opt_sp_cparams ')' '.' + ident SET_VAR { - Lex_ident_sys tmp(thd, &$1); - if (unlikely(Lex->call_statement_start(thd, &tmp, &$3, &$5))) + if (unlikely(Lex->assoc_ident == NULL)) + { + thd->parse_error(); + MYSQL_YYABORT; + } + + LEX *lex= Lex; + lex->set_stmt_init(); + if (sp_create_assignment_lex(thd, Lex->assoc_ident->pos())) + MYSQL_YYABORT; + if (lex->sp_set_assoc_array_copy_key(Lex)) MYSQL_YYABORT; } - opt_sp_cparam_list + set_expr_or_default { - if (Lex->check_cte_dependencies_and_resolve_references()) + if (unlikely(Lex->sp_set_assoc_array_field(thd, + Lex->assoc_ident, + &$6, + $9.expr, + $9.expr_str)) || + unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY, + false))) MYSQL_YYABORT; } ; +sp_statement: + statement + | direct_call_statement + ; + sp_if_then_statements: sp_proc_stmts1_implicit_block { } ; @@ -19485,39 +19685,39 @@ ident_cli_directly_assignable: ; -set_assign: - ident_cli_directly_assignable SET_VAR +optionally_qualified_directly_assignable: + ident_cli_directly_assignable { - LEX *lex=Lex; - lex->set_stmt_init(); - if (sp_create_assignment_lex(thd, $1.pos())) + if (unlikely(!($$= new (thd->mem_root) + Qualified_ident(thd, &$1)))) MYSQL_YYABORT; } - set_expr_or_default + | ident_cli_directly_assignable '.' ident { - 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))) + if (unlikely(!($$= new (thd->mem_root) + Qualified_ident(thd, &$1, &$3)))) MYSQL_YYABORT; } - | ident_cli_directly_assignable '.' ident SET_VAR + | ident_cli_directly_assignable '.' ident '.' ident + { + if (unlikely(!($$= new (thd->mem_root) + Qualified_ident(thd, &$1, &$3, &$5)))) + MYSQL_YYABORT; + } + ; + + +set_assign: + 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 *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; @@ -19682,6 +19882,48 @@ package_implementation_executable_section: | BEGIN_ORACLE_SYM sp_block_statements_and_exceptions END { $$= $2; } ; +named_expr_list: + named_expr_conflict + { + $$= new (thd->mem_root) List; + if (unlikely($$ == NULL)) + MYSQL_YYABORT; + $$->push_back($1, thd->mem_root); + } + | named_expr_list ',' named_expr_conflict + { + $1->push_back($3, thd->mem_root); + $$= $1; + } + ; + +assoc_name: + TEXT_STRING_sys ARROW_SYM + | LONG_NUM ARROW_SYM + | ULONGLONG_NUM ARROW_SYM + | DECIMAL_NUM ARROW_SYM + | NUM ARROW_SYM + ; + +named_expr: + assoc_name expr + { + if ($1.str) + { + $2->base_flags|= item_base_t::IS_EXPLICIT_NAME; + $2->set_name(thd, $1); + } + $$= $2; + } + ; + +named_expr_conflict: + remember_name named_expr remember_end + { + $$= $2; + } + ; + %endif ORACLE @@ -20074,6 +20316,79 @@ opt_sp_decl_handler_list: | sp_decl_handler_list ; +typed_ident: + TYPE_SYM ident_directly_assignable + { + Lex->name= $2; + + if (unlikely(!Lex->assoc_array_def_init(thd, $2))) + MYSQL_YYABORT; + } + ; + +assoc_array_table_types: + field_type_all_with_record + { + Lex->last_field->set_attributes(thd, $1, COLUMN_DEFINITION_ROUTINE_LOCAL); + $$= (Spvar_definition *)Lex->last_field; + } + | sp_decl_ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + if (unlikely(Lex->sphead-> + spvar_def_fill_type_reference(thd, + ($$= (Spvar_definition *)Lex->last_field), $1, + $3))) + MYSQL_YYABORT; + } + | sp_decl_ident '.' ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + if (unlikely(Lex->sphead-> + spvar_def_fill_type_reference(thd, + ($$= (Spvar_definition *)Lex->last_field), $1, + $3, $5))) + MYSQL_YYABORT; + } + | ident PERCENT_ORACLE_SYM ROWTYPE_ORACLE_SYM + { + if (unlikely(Lex->sphead-> + spvar_def_fill_rowtype_reference(thd, + ($$= (Spvar_definition *)Lex->last_field), + $1))) + MYSQL_YYABORT; + } + | sp_decl_ident '.' ident PERCENT_ORACLE_SYM ROWTYPE_ORACLE_SYM + { + if (unlikely(Lex->sphead-> + spvar_def_fill_rowtype_reference(thd, + ($$= (Spvar_definition *)Lex->last_field), + $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 + { + Lex->last_field->set_attributes(thd, $3, COLUMN_DEFINITION_ROUTINE_LOCAL); + $$= Lex->last_field; + } + ; + sp_decl_non_handler: sp_decl_variable_list | ident_directly_assignable CONDITION_SYM FOR_SYM sp_cond @@ -20112,14 +20427,28 @@ 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-> - declare_record(thd, Lex_ident_column($2), $5))) + declare_record(thd, Lex_ident_column(Lex->name), $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 + { + if (unlikely(Lex->spcont-> + declare_assoc_array(thd, + Lex_ident_column(Lex->name), + $7, $5))) + MYSQL_YYABORT; + $$.vars= $$.conds= $$.hndlrs= $$.curs= 0; + } ; diff --git a/storage/connect/ha_connect.cc b/storage/connect/ha_connect.cc index 3757d0d1c033d..4cea4cef6a92d 100644 --- a/storage/connect/ha_connect.cc +++ b/storage/connect/ha_connect.cc @@ -2839,6 +2839,7 @@ PFIL ha_connect::CondFilter(PGLOBAL g, Item *cond) *((double*)pp->Value)= pval->val_real(); break; case ROW_RESULT: + case ASSOC_ARRAY_RESULT: DBUG_ASSERT(0); return NULL; }