From 97439b3d569ea717fffabfe0f1c95737b48970e6 Mon Sep 17 00:00:00 2001 From: Iqbal Hassan Date: Thu, 12 Sep 2024 23:25:40 +0800 Subject: [PATCH] MDEV-34317: Implement RECORD type Implement `DECLARE TYPE type_name IS RECORD (..)` with scalar members in stored routines and anonymous blocks --- .../suite/compat/oracle/r/sp-record.result | 641 ++++++++++++++++ .../suite/compat/oracle/t/sp-record.test | 703 ++++++++++++++++++ sql/lex.h | 1 + sql/share/errmsg-utf8.txt | 5 +- sql/sp_head.cc | 32 + sql/sp_head.h | 8 + sql/sp_pcontext.cc | 42 +- sql/sp_pcontext.h | 51 ++ sql/sp_rcontext.cc | 7 +- sql/sql_lex.cc | 39 + sql/sql_lex.h | 4 + sql/sql_yacc.yy | 110 ++- 12 files changed, 1632 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/compat/oracle/r/sp-record.result create mode 100644 mysql-test/suite/compat/oracle/t/sp-record.test diff --git a/mysql-test/suite/compat/oracle/r/sp-record.result b/mysql-test/suite/compat/oracle/r/sp-record.result new file mode 100644 index 0000000000000..ea985817f74f2 --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-record.result @@ -0,0 +1,641 @@ +# +# MDEV-34317 DECLARE TYPE type_name IS RECORD (..) with scalar members in stored routines +# +set sql_mode=oracle; +# +# Basic RECORD +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); +str VARCHAR(1024); +BEGIN +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +END; +$$ +str +dept_id: 11; dept_name: a; mgr_id: 201; loc_id: 1700 +# +# RECORD fields with anchored type +# +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id t1.dept_id%TYPE, +dept_name t1.dept_name%TYPE, +mgr_id t1.mgr_id%TYPE, +loc_id t1.loc_id%TYPE +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); +str VARCHAR(1024); +BEGIN +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +END; +$$ +str +dept_id: 11; dept_name: a; mgr_id: 201; loc_id: 1700 +DROP TABLE t1; +# +# RECORD using SUBTYPE +# This is not supported yet +# +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +DECLARE +SUBTYPE DeptRecTyp IS t1%ROWTYPE; +dept_rec DeptRecTyp; +str VARCHAR(1024); +BEGIN +dept_rec.dept_id := 11; +dept_rec.dept_name:= 'a'; +dept_rec.mgr_id := 201; +dept_rec.loc_id := 1700; +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +END; +$$ +ERROR HY000: Unknown data type: 'DeptRecTyp' +DROP TABLE t1; +# +# RECORD with NOT NULL or default clauses +# This is not supported yet +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) NOT NULL := 10, +dept_name VARCHAR2(30) NOT NULL := 'Administration', +mgr_id NUMBER(6) := 200, +loc_id NUMBER(4) := 1700 +); +dept_rec DeptRecTyp; +str VARCHAR(1024); +BEGIN +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || chr(13) || +'mgr_id: ' || dept_rec.mgr_id || chr(13) || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +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 'NOT NULL := 10, +dept_name VARCHAR2(30) NOT NULL := 'Administration', +mgr_id ...' at line 3 +# +# Nested RECORDs +# This is not supported yet +# +DECLARE +TYPE name_rec IS RECORD ( +first VARCHAR(64), +last VARCHAR(64) +); +TYPE contact IS RECORD ( +name name_rec, -- a nested record +phone VARCHAR(32) +); +person contact; +str VARCHAR(1024); +BEGIN +person.name.first := 'John'; +person.name.last := 'Brown'; +person.phone := '1-654-222-1234'; +str:= +person.name.first || ' ' || +person.name.last || ', ' || +person.phone; +SELECT str; +END; +$$ +ERROR HY000: Unknown data type: 'name_rec' +# +# RECORD duplicate type declaration +# +DECLARE +TYPE RcType IS RECORD ( +a NUMBER(4) +); +TYPE RcType IS RECORD ( +b NUMBER(4) +); +BEGIN +END; +$$ +ERROR HY000: Duplicate declaration: 'RcType' +# +# RECORD with no field +# +DECLARE +TYPE RcType IS RECORD ( +); +rec RcType; +str VARCHAR(1024); +BEGIN +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 '); +rec RcType; +str VARCHAR(1024); +BEGIN +END' at line 3 +# +# RECORD field test +# +DECLARE +TYPE RcType IS RECORD ( +int11 INT, +dec103 DECIMAL(10,3), +flt0 FLOAT, +dbl0 DOUBLE, +enum0 ENUM('a','b'), +bit3 BIT(3), +varchar10 VARCHAR(10), +text1 TEXT, +tinytext1 TINYTEXT, +mediumtext1 MEDIUMTEXT CHARACTER SET utf8, +longtext1 LONGTEXT, +time3 TIME(3), +datetime4 DATETIME(4), +timestamp5 TIMESTAMP(5), +date0 DATE +); +rec1,rec2 RcType:= RcType(4, 11.2, 6.7, 111.43234663, 'a', b'101', +'bbbb', 'TEXT', 'TINY', 'MEDIUM', 'LONG', +'10:14:22','2001-07-22 10:14:22', '2001-07-22 12:12:12', '2001-07-22'); +rec3 RcType; +BEGIN +SELECT rec1.int11, rec2.timestamp5, rec3.date0; +END; +$$ +rec1.int11 rec2.timestamp5 rec3.date0 +4 2001-07-22 12:12:12.00000 NULL +# +# RECORD with field manipulations +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec DeptRecTyp; +test INT:=4; +BEGIN +dept_rec.dept_id:= 2; +test:= test + dept_rec.dept_id; +dept_rec.dept_id:= 5; +SELECT dept_rec.dept_id, test; +END; +$$ +dept_rec.dept_id test +5 6 +# +# RECORD comparison +# RECORD type comparison is not supported +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec1 DeptRecTyp; +dept_rec2 DeptRecTyp; +BEGIN +SELECT dept_rec1 = dept_rec2; +END; +$$ +ERROR 21000: Operand should contain 1 column(s) +# +# RECORD assignment with the same type +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec1 DeptRecTyp:= DeptRecTyp(11); +dept_rec2 DeptRecTyp:= DeptRecTyp(12); +BEGIN +dept_rec1:= dept_rec2; +SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +dept_rec1.dept_id dept_rec2.dept_id +12 12 +# +# RECORD assignment with different types, but the same fields +# This fails in ORACLE TODO +# +DECLARE +TYPE DeptRecTyp1 IS RECORD ( +dept_id NUMBER(4) +); +TYPE DeptRecTyp2 IS RECORD ( +dept_id NUMBER(4) +); +dept_rec1 DeptRecTyp1:= DeptRecTyp1(11); +dept_rec2 DeptRecTyp2:= DeptRecTyp2(12); +BEGIN +dept_rec1:= dept_rec2; +SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +dept_rec1.dept_id dept_rec2.dept_id +12 12 +# +# RECORD assignment with different types +# This fails in ORACLE with +# PLS-00382: expression is of wrong type +# In MariaDB we need to set sql_mode='strict_all_tables' +# to get a similar effect. Otherwise we only get a warning. +# +DECLARE +TYPE DeptRecTyp1 IS RECORD ( +dept_id NUMBER(4) +); +TYPE DeptRecTyp2 IS RECORD ( +dept_id VARCHAR2(30) +); +dept_rec1 DeptRecTyp1:= DeptRecTyp1(11); +dept_rec2 DeptRecTyp2:= DeptRecTyp2('a'); +BEGIN +dept_rec1:= dept_rec2; +SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +dept_rec1.dept_id dept_rec2.dept_id +0 a +Warnings: +Warning 1366 Incorrect decimal value: 'a' for column ``.``.`dept_id` at row 0 +# +# RECORD assignment with anchored ROWTYPE from table +# +CREATE TABLE t1 +( +dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec1 DeptRecTyp:= DeptRecTyp(11); +dept_rec2 t1%ROWTYPE; +BEGIN +SELECT dept_id INTO dept_rec2 FROM t1 WHERE ROWNUM < 2; +dept_rec1:= dept_rec2; +SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +dept_rec1.dept_id dept_rec2.dept_id +12 12 +DROP TABLE t1; +# +# RECORD assignment with anchored ROWTYPE from cursor (1) +# +CREATE TABLE t1 +( +dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +CURSOR c1 IS SELECT * FROM t1; +dept_rec1 DeptRecTyp:= DeptRecTyp(11); +BEGIN +DECLARE +dept_rec2 c1%ROWTYPE; +BEGIN +SELECT dept_id INTO dept_rec2 FROM t1 WHERE ROWNUM < 2; +dept_rec1:= dept_rec2; +SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +END; +$$ +dept_rec1.dept_id dept_rec2.dept_id +12 12 +DROP TABLE t1; +# +# RECORD assignment with anchored ROWTYPE from cursor (2) +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4), +b VARCHAR(10) +); +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 DeptRecTyp:=DeptRecTyp(11,'a'); +rec2 cur1%ROWTYPE; +BEGIN +rec2.a:= 10; +rec2.b:= 'bbb'; +rec1:= rec2; +SELECT rec1.dept_id, rec2.a; +END; +END; +$$ +CALL p1(); +rec1.dept_id rec2.a +10 10 +DROP TABLE t1; +DROP PROCEDURE p1; +# +# RECORD within stored procedures +# +CREATE OR REPLACE PROCEDURE p1() AS +TYPE DeptRecTyp IS RECORD(dept_id NUMBER(4)); +dept_rec DeptRecTyp; +BEGIN +END; +$$ +DROP PROCEDURE p1; +# +# SELECT INTO RECORD (??) +# +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +INSERT INTO t1 VALUES(12, 'b',202,2000); +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id t1.dept_id%TYPE, +dept_name t1.dept_name%TYPE, +mgr_id t1.mgr_id%TYPE, +loc_id t1.loc_id%TYPE +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); +str VARCHAR(1024); +BEGIN +SELECT * INTO dept_rec FROM t1 WHERE ROWNUM <= 1; +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +END; +$$ +str +dept_id: 12; dept_name: b; mgr_id: 202; loc_id: 2000 +DROP TABLE t1; +# +# INSERT RECORDs INTO table +# Not supported (?) +# This is supported in Oracle with `VALUES row_constructor_list` +CREATE TABLE t1 +( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id t1.dept_id%TYPE, +dept_name t1.dept_name%TYPE, +mgr_id t1.mgr_id%TYPE, +loc_id t1.loc_id%TYPE +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); +str VARCHAR(1024); +BEGIN +INSERT INTO t1 VALUES dept_rec; +SELECT * FROM t1; +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 'dept_rec; +SELECT * FROM t1; +END' at line 11 +DROP TABLE t1; +# +# UPDATE table using RECORD +# This is supported in Oracle +CREATE TABLE t1 +( +dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); +DECLARE +TYPE DeptRecTyp IS RECORD(dept_id NUMBER(4)); +dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN +UPDATE t1 SET ROW = dept_rec WHERE dept_id = 12; +END; +$$ +ERROR 42S22: Unknown column 'ROW' in 'field list' +DROP TABLE t1; +# +# Wrong parameter count to RECORD's constructor (actual < expected) +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201); +str VARCHAR(1024); +BEGIN +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +select str; +END; +$$ +ERROR 21000: Operand should contain 4 column(s) +# +# Wrong parameter count to RECORD's constructor (actual > expected) +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4), +dept_name VARCHAR2(30), +mgr_id NUMBER(6), +loc_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700,2000); +str VARCHAR(1024); +BEGIN +str:= +'dept_id: ' || dept_rec.dept_id || '; ' || +'dept_name: ' || dept_rec.dept_name || '; ' || +'mgr_id: ' || dept_rec.mgr_id || '; ' || +'loc_id: ' || dept_rec.loc_id; +SELECT str; +END; +$$ +ERROR 21000: Operand should contain 4 column(s) +# +# Case insensitivity test for RECORD variable +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11); +str VARCHAR(1024); +BEGIN +SELECT DEPT_rec.DEPT_id; +END; +$$ +DEPT_rec.DEPT_id +11 +# +# Quoted indentifier test for RECORD variable +# +DECLARE +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +"dept/rec" DeptRecTyp:= DeptRecTyp(11); +str VARCHAR(1024); +BEGIN +SELECT "dept/rec".dept_id; +END; +$$ +"dept/rec".dept_id +11 +# +# RECORD type used in a stored PROCEDURE +# +CREATE PROCEDURE p1(v NUMBER) AS +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN +SELECT dept_rec.dept_id + v; +END; +$$ +CALL p1(4); +dept_rec.dept_id + v +15 +DROP PROCEDURE p1; +# +# RECORD type used in a stored FUNCTION +# +CREATE FUNCTION f1(v NUMBER) +RETURN NUMBER IS +TYPE DeptRecTyp IS RECORD ( +dept_id NUMBER(4) +); +dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN +RETURN dept_rec.dept_id + v; +END; +$$ +SELECT f1(4); +f1(4) +15 +DROP FUNCTION f1; +# +# RECORD keyword as identifier (sp var name) +# +DECLARE +RECORD NUMBER(4); +BEGIN +SELECT RECORD; +END; +$$ +RECORD +NULL +# +# RECORD keyword as identifier (function name) +# +CREATE FUNCTION RECORD(a IN NUMBER) +RETURN NUMBER +IS b NUMBER(11,2); +BEGIN +b:= a; +RETURN(b); +END; +$$ +Warnings: +Note 1585 This function 'RECORD' has the same name as a native function +DROP FUNCTION RECORD; +SET sql_mode=default; +# +# Basic RECORD, anonymous block sql_mode=default; +# +BEGIN NOT ATOMIC +DECLARE TYPE DeptRecTyp IS RECORD ( +dept_id INT +); +DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); +SELECT dept_rec.dept_id; +END; +$$ +ERROR HY000: Unknown data type: 'DeptRecTyp' +# +# Basic RECORD, stored procedure sql_mode=default; +# +CREATE OR REPLACE PROCEDURE p1() +BEGIN +DECLARE TYPE DeptRecTyp IS RECORD ( +dept_id INT +); +DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); +SELECT dept_rec.dept_id; +END; +$$ +ERROR HY000: Unknown data type: 'DeptRecTyp' +CALL p1(); +ERROR 42000: PROCEDURE test.p1 does not exist +DROP PROCEDURE p1; +ERROR 42000: PROCEDURE test.p1 does not exist +# +# Basic RECORD, stored function sql_mode=default; +# +CREATE OR REPLACE FUNCTION f1() RETURNS INT +BEGIN +DECLARE TYPE DeptRecTyp IS RECORD ( +dept_id INT +); +DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); +RETURN dept_rec.dept_id; +END; +$$ +ERROR HY000: Unknown data type: 'DeptRecTyp' +SELECT f1(); +ERROR 42000: FUNCTION test.f1 does not exist +DROP FUNCTION f1; +ERROR 42000: FUNCTION test.f1 does not exist diff --git a/mysql-test/suite/compat/oracle/t/sp-record.test b/mysql-test/suite/compat/oracle/t/sp-record.test new file mode 100644 index 0000000000000..e2adcd7513bb2 --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-record.test @@ -0,0 +1,703 @@ +--echo # +--echo # MDEV-34317 DECLARE TYPE type_name IS RECORD (..) with scalar members in stored routines +--echo # + +set sql_mode=oracle; + +--echo # +--echo # Basic RECORD +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); + str VARCHAR(1024); +BEGIN + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD fields with anchored type +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) +); + +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id t1.dept_id%TYPE, + dept_name t1.dept_name%TYPE, + mgr_id t1.mgr_id%TYPE, + loc_id t1.loc_id%TYPE + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); + str VARCHAR(1024); +BEGIN + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # RECORD using SUBTYPE +--echo # This is not supported yet +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) +); +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +DECLARE + SUBTYPE DeptRecTyp IS t1%ROWTYPE; + dept_rec DeptRecTyp; + str VARCHAR(1024); +BEGIN + dept_rec.dept_id := 11; + dept_rec.dept_name:= 'a'; + dept_rec.mgr_id := 201; + dept_rec.loc_id := 1700; + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # RECORD with NOT NULL or default clauses +--echo # This is not supported yet +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) NOT NULL := 10, + dept_name VARCHAR2(30) NOT NULL := 'Administration', + mgr_id NUMBER(6) := 200, + loc_id NUMBER(4) := 1700 + ); + dept_rec DeptRecTyp; + str VARCHAR(1024); +BEGIN + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || chr(13) || + 'mgr_id: ' || dept_rec.mgr_id || chr(13) || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Nested RECORDs +--echo # This is not supported yet +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +DECLARE + TYPE name_rec IS RECORD ( + first VARCHAR(64), + last VARCHAR(64) + ); + + TYPE contact IS RECORD ( + name name_rec, -- a nested record + phone VARCHAR(32) + ); + person contact; + str VARCHAR(1024); +BEGIN + person.name.first := 'John'; + person.name.last := 'Brown'; + person.phone := '1-654-222-1234'; + + str:= + person.name.first || ' ' || + person.name.last || ', ' || + person.phone; + + SELECT str; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD duplicate type declaration +--echo # +DELIMITER $$; +--error ER_SP_DUP_DECL +DECLARE + TYPE RcType IS RECORD ( + a NUMBER(4) + ); + TYPE RcType IS RECORD ( + b NUMBER(4) + ); +BEGIN +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD with no field +--echo # +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE RcType IS RECORD ( + ); + rec RcType; + str VARCHAR(1024); +BEGIN +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD field test +--echo # +DELIMITER $$; +DECLARE + TYPE RcType IS RECORD ( + int11 INT, + dec103 DECIMAL(10,3), + flt0 FLOAT, + dbl0 DOUBLE, + enum0 ENUM('a','b'), + bit3 BIT(3), + + varchar10 VARCHAR(10), + text1 TEXT, + tinytext1 TINYTEXT, + mediumtext1 MEDIUMTEXT CHARACTER SET utf8, + longtext1 LONGTEXT, + + time3 TIME(3), + datetime4 DATETIME(4), + timestamp5 TIMESTAMP(5), + date0 DATE + ); + rec1,rec2 RcType:= RcType(4, 11.2, 6.7, 111.43234663, 'a', b'101', + 'bbbb', 'TEXT', 'TINY', 'MEDIUM', 'LONG', + '10:14:22','2001-07-22 10:14:22', '2001-07-22 12:12:12', '2001-07-22'); + rec3 RcType; +BEGIN + SELECT rec1.int11, rec2.timestamp5, rec3.date0; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD with field manipulations +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec DeptRecTyp; + test INT:=4; +BEGIN + dept_rec.dept_id:= 2; + test:= test + dept_rec.dept_id; + dept_rec.dept_id:= 5; + SELECT dept_rec.dept_id, test; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD comparison +--echo # RECORD type comparison is not supported +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec1 DeptRecTyp; + dept_rec2 DeptRecTyp; +BEGIN + SELECT dept_rec1 = dept_rec2; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD assignment with the same type +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec1 DeptRecTyp:= DeptRecTyp(11); + dept_rec2 DeptRecTyp:= DeptRecTyp(12); +BEGIN + dept_rec1:= dept_rec2; + SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD assignment with different types, but the same fields +--echo # This fails in ORACLE TODO +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp1 IS RECORD ( + dept_id NUMBER(4) + ); + TYPE DeptRecTyp2 IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec1 DeptRecTyp1:= DeptRecTyp1(11); + dept_rec2 DeptRecTyp2:= DeptRecTyp2(12); +BEGIN + dept_rec1:= dept_rec2; + SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD assignment with different types +--echo # This fails in ORACLE with +--echo # PLS-00382: expression is of wrong type +--echo # In MariaDB we need to set sql_mode='strict_all_tables' +--echo # to get a similar effect. Otherwise we only get a warning. +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp1 IS RECORD ( + dept_id NUMBER(4) + ); + TYPE DeptRecTyp2 IS RECORD ( + dept_id VARCHAR2(30) + ); + dept_rec1 DeptRecTyp1:= DeptRecTyp1(11); + dept_rec2 DeptRecTyp2:= DeptRecTyp2('a'); +BEGIN + dept_rec1:= dept_rec2; + SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD assignment with anchored ROWTYPE from table +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec1 DeptRecTyp:= DeptRecTyp(11); + dept_rec2 t1%ROWTYPE; +BEGIN + SELECT dept_id INTO dept_rec2 FROM t1 WHERE ROWNUM < 2; + dept_rec1:= dept_rec2; + SELECT dept_rec1.dept_id, dept_rec2.dept_id; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # RECORD assignment with anchored ROWTYPE from cursor (1) +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + CURSOR c1 IS SELECT * FROM t1; + dept_rec1 DeptRecTyp:= DeptRecTyp(11); +BEGIN + DECLARE + dept_rec2 c1%ROWTYPE; + BEGIN + SELECT dept_id INTO dept_rec2 FROM t1 WHERE ROWNUM < 2; + dept_rec1:= dept_rec2; + SELECT dept_rec1.dept_id, dept_rec2.dept_id; + END; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # RECORD assignment with anchored ROWTYPE from cursor (2) +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4), + b VARCHAR(10) + ); + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 DeptRecTyp:=DeptRecTyp(11,'a'); + rec2 cur1%ROWTYPE; + BEGIN + rec2.a:= 10; + rec2.b:= 'bbb'; + + rec1:= rec2; + SELECT rec1.dept_id, rec2.a; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + +--echo # +--echo # RECORD within stored procedures +--echo # +DELIMITER $$; +CREATE OR REPLACE PROCEDURE p1() AS + TYPE DeptRecTyp IS RECORD(dept_id NUMBER(4)); + dept_rec DeptRecTyp; +BEGIN +END; +$$ +DELIMITER ;$$ +DROP PROCEDURE p1; + +--echo # +--echo # SELECT INTO RECORD (??) +--echo # +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) +); +INSERT INTO t1 VALUES(12, 'b',202,2000); + +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id t1.dept_id%TYPE, + dept_name t1.dept_name%TYPE, + mgr_id t1.mgr_id%TYPE, + loc_id t1.loc_id%TYPE + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); + str VARCHAR(1024); +BEGIN + SELECT * INTO dept_rec FROM t1 WHERE ROWNUM <= 1; + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # INSERT RECORDs INTO table +--echo # Not supported (?) +--echo # This is supported in Oracle with `VALUES row_constructor_list` +CREATE TABLE t1 +( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) +); + +DELIMITER $$; +--error ER_PARSE_ERROR +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id t1.dept_id%TYPE, + dept_name t1.dept_name%TYPE, + mgr_id t1.mgr_id%TYPE, + loc_id t1.loc_id%TYPE + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700); + str VARCHAR(1024); +BEGIN + INSERT INTO t1 VALUES dept_rec; + SELECT * FROM t1; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # UPDATE table using RECORD +--echo # This is supported in Oracle +CREATE TABLE t1 +( + dept_id NUMBER(4) +); +INSERT INTO t1 VALUES(12); + +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +DECLARE + TYPE DeptRecTyp IS RECORD(dept_id NUMBER(4)); + dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN + UPDATE t1 SET ROW = dept_rec WHERE dept_id = 12; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + +--echo # +--echo # Wrong parameter count to RECORD's constructor (actual < expected) +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201); + str VARCHAR(1024); +BEGIN + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + select str; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Wrong parameter count to RECORD's constructor (actual > expected) +--echo # +DELIMITER $$; +--error ER_OPERAND_COLUMNS +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4), + dept_name VARCHAR2(30), + mgr_id NUMBER(6), + loc_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11,'a',201,1700,2000); + str VARCHAR(1024); +BEGIN + str:= + 'dept_id: ' || dept_rec.dept_id || '; ' || + 'dept_name: ' || dept_rec.dept_name || '; ' || + 'mgr_id: ' || dept_rec.mgr_id || '; ' || + 'loc_id: ' || dept_rec.loc_id; + SELECT str; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Case insensitivity test for RECORD variable +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11); + str VARCHAR(1024); +BEGIN + SELECT DEPT_rec.DEPT_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Quoted indentifier test for RECORD variable +--echo # +DELIMITER $$; +DECLARE + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + "dept/rec" DeptRecTyp:= DeptRecTyp(11); + str VARCHAR(1024); +BEGIN + SELECT "dept/rec".dept_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD type used in a stored PROCEDURE +--echo # +DELIMITER $$; +CREATE PROCEDURE p1(v NUMBER) AS + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN + SELECT dept_rec.dept_id + v; +END; +$$ +DELIMITER ;$$ +CALL p1(4); +DROP PROCEDURE p1; + +--echo # +--echo # RECORD type used in a stored FUNCTION +--echo # +DELIMITER $$; +CREATE FUNCTION f1(v NUMBER) +RETURN NUMBER IS + TYPE DeptRecTyp IS RECORD ( + dept_id NUMBER(4) + ); + dept_rec DeptRecTyp:= DeptRecTyp(11); +BEGIN + RETURN dept_rec.dept_id + v; +END; +$$ +DELIMITER ;$$ +SELECT f1(4); +DROP FUNCTION f1; + +--echo # +--echo # RECORD keyword as identifier (sp var name) +--echo # +DELIMITER $$; +DECLARE + RECORD NUMBER(4); +BEGIN + SELECT RECORD; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # RECORD keyword as identifier (function name) +--echo # +DELIMITER $$; +CREATE FUNCTION RECORD(a IN NUMBER) +RETURN NUMBER +IS b NUMBER(11,2); +BEGIN + b:= a; + RETURN(b); +END; +$$ +DELIMITER ;$$ +DROP FUNCTION RECORD; + + +SET sql_mode=default; + +--echo # +--echo # Basic RECORD, anonymous block sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +BEGIN NOT ATOMIC + DECLARE TYPE DeptRecTyp IS RECORD ( + dept_id INT + ); + DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); + SELECT dept_rec.dept_id; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Basic RECORD, stored procedure sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +CREATE OR REPLACE PROCEDURE p1() +BEGIN + DECLARE TYPE DeptRecTyp IS RECORD ( + dept_id INT + ); + DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); + SELECT dept_rec.dept_id; +END; +$$ +DELIMITER ;$$ +--error ER_SP_DOES_NOT_EXIST +CALL p1(); +--error ER_SP_DOES_NOT_EXIST +DROP PROCEDURE p1; + +--echo # +--echo # Basic RECORD, stored function sql_mode=default; +--echo # +DELIMITER $$; +--error ER_UNKNOWN_DATA_TYPE +CREATE OR REPLACE FUNCTION f1() RETURNS INT +BEGIN + DECLARE TYPE DeptRecTyp IS RECORD ( + dept_id INT + ); + DECLARE dept_rec DeptRecTyp DEFAULT DeptRecTyp(11); + RETURN dept_rec.dept_id; +END; +$$ +DELIMITER ;$$ +--error ER_SP_DOES_NOT_EXIST +SELECT f1(); +--error ER_SP_DOES_NOT_EXIST +DROP FUNCTION f1; diff --git a/sql/lex.h b/sql/lex.h index 3cf4db19f0772..6950a467b091f 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -516,6 +516,7 @@ SYMBOL symbols[] = { { "READS", SYM(READS_SYM)}, { "REAL", SYM(REAL)}, { "REBUILD", SYM(REBUILD_SYM)}, + { "RECORD", SYM(RECORD_SYM)}, { "RECOVER", SYM(RECOVER_SYM)}, { "RECURSIVE", SYM(RECURSIVE_SYM)}, { "REDO_BUFFER_SIZE", SYM(REDO_BUFFER_SIZE_SYM)}, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index b937b0614ccec..ce23a46ba3193 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12148,9 +12148,8 @@ ER_UNKNOWN_OPERATOR eng "Operator does not exist: '%-.128s'" spa "El operador no existe: '%-.128s'" sw "Opereta haipo: '% -.128s'" -ER_UNUSED_29 - eng "You should never see it" - sw "Hupaswi kuiona kamwe" +ER_SP_DUP_DECL + eng "Duplicate declaration: '%-.64s'" ER_PART_STARTS_BEYOND_INTERVAL eng "%`s: STARTS is later than query time, first history partition may exceed INTERVAL value" spa "%`s: STARTS es posterior al momento de consulta (query), la primera particiĆ³n de historia puede exceder el valor INTERVAL" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 546f7b8b7bb66..792d710cbaaeb 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3928,6 +3928,38 @@ bool sp_head::spvar_fill_type_reference(THD *thd, } +bool sp_head::spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &table, + const LEX_CSTRING &column) +{ + Qualified_column_ident *ref; + if (!(ref= new (thd->mem_root) Qualified_column_ident(&table, &column))) + return true; + + def->set_column_type_ref(ref); + m_flags|= sp_head::HAS_COLUMN_TYPE_REFS; + + return false; +} + + +bool sp_head::spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &db, + const LEX_CSTRING &table, + const LEX_CSTRING &column) +{ + Qualified_column_ident *ref; + if (!(ref= new (thd->mem_root) Qualified_column_ident(thd, &db, &table, + &column))) + return true; + + def->set_column_type_ref(ref); + m_flags|= sp_head::HAS_COLUMN_TYPE_REFS; + + 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 c91bedc358191..1143ca4d95798 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -809,6 +809,14 @@ class sp_head :private Query_arena, const LEX_CSTRING &db, const LEX_CSTRING &table); + bool spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &table, + const LEX_CSTRING &column); + bool spvar_def_fill_type_reference(THD *thd, Spvar_definition *def, + const LEX_CSTRING &db, + const LEX_CSTRING &table, + const LEX_CSTRING &column); + void set_c_chistics(const st_sp_chistics &chistics); void set_info(longlong created, longlong modified, const st_sp_chistics &chistics, sql_mode_t sql_mode); diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc index 9f21d34eb88f1..eb987dc8c8351 100644 --- a/sql/sp_pcontext.cc +++ b/sql/sp_pcontext.cc @@ -98,8 +98,8 @@ sp_pcontext::sp_pcontext() m_parent(NULL), m_pboundary(0), 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_children(PSI_INSTRUMENT_MEM), - m_scope(REGULAR_SCOPE) + m_handlers(PSI_INSTRUMENT_MEM), m_records(PSI_INSTRUMENT_MEM), + m_children(PSI_INSTRUMENT_MEM), m_scope(REGULAR_SCOPE) { init(0, 0, 0); } @@ -111,8 +111,8 @@ sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope) m_parent(prev), m_pboundary(0), 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_children(PSI_INSTRUMENT_MEM), - m_scope(scope) + m_handlers(PSI_INSTRUMENT_MEM), m_records(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(), @@ -413,6 +413,40 @@ sp_condition_value *sp_pcontext::find_condition(const LEX_CSTRING *name, NULL; } + +bool sp_pcontext::add_record(THD *thd, const Lex_ident_column &name, + Row_definition_list *field) +{ + sp_record *p= new (thd->mem_root) sp_record(name, field); + + if (p == NULL) + return true; + + return m_records.append(p); +} + + +sp_record *sp_pcontext::find_record(const LEX_CSTRING *name, + bool current_scope_only) const +{ + size_t i= m_records.elements(); + + while (i--) + { + sp_record *p= m_records.at(i); + + if (p->eq_name(name)) + { + return p; + } + } + + return (!current_scope_only && m_parent) ? + m_parent->find_record(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 972fc6aedc1f2..5418091b30e31 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -324,6 +324,31 @@ class sp_handler : public Sql_alloc { } }; + +/////////////////////////////////////////////////////////////////////////// + +/// This class represents 'DECLARE RECORD' statement. + +class sp_record : public Sql_alloc +{ +public: + /// Name of the record. + Lex_ident_column name; + Row_definition_list *field; + +public: + sp_record(const Lex_ident_column &name_arg, Row_definition_list *prmfield) + :Sql_alloc(), + name(name_arg), + field(prmfield) + { } + bool eq_name(const LEX_CSTRING *str) const + { + return name.streq(*str); + } +}; + + /////////////////////////////////////////////////////////////////////////// /// The class represents parse-time context, which keeps track of declared @@ -700,6 +725,29 @@ class sp_pcontext : public Sql_alloc return m_for_loop; } + ///////////////////////////////////////////////////////////////////////// + // Record. + ///////////////////////////////////////////////////////////////////////// + + bool add_record(THD *thd, + const Lex_ident_column &name, + Row_definition_list *field); + + sp_record *find_record(const LEX_CSTRING *name, + bool current_scope_only) const; + + bool declare_record(THD *thd, + const Lex_ident_column &name, + Row_definition_list *field) + { + if (find_record(&name, true)) + { + my_error(ER_SP_DUP_DECL, MYF(0), name.str); + return true; + } + return add_record(thd, name, field); + } + private: /// Constructor for a tree node. /// @param prev the parent parsing context @@ -764,6 +812,9 @@ class sp_pcontext : public Sql_alloc /// Stack of SQL-handlers. Dynamic_array m_handlers; + /// Stack of records. + Dynamic_array m_records; + /* 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 fc8a34854fc55..d63791ffdd6fe 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -346,7 +346,12 @@ bool Row_definition_list::resolve_type_refs(THD *thd) Spvar_definition *def; while ((def= it++)) { - if (def->is_column_type_ref() && + if (def->is_row()) + { + if (def->row_field_definitions()->resolve_type_refs(thd)) + return true; + } + else if (def->is_column_type_ref() && def->column_type_ref()->resolve_type_ref(thd, def)) return true; } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 01b675f868e95..2ebad5de91133 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -6678,6 +6678,17 @@ bool LEX::sp_variable_declarations_finalize(THD *thd, int nvars, const LEX_CSTRING &expr_str) { DBUG_ASSERT(cdef); + + if (cdef->type_handler() == &type_handler_row) + { + if (sp_record *sprec= + (sp_record *)cdef->get_attr_const_void_ptr(0)) { + return sp_variable_declarations_rec_finalize(thd, nvars, + sprec->field, + dflt_value_item, expr_str); + } + } + Column_definition tmp(*cdef); if (sphead->fill_spvar_definition(thd, &tmp)) return true; @@ -6686,6 +6697,34 @@ bool LEX::sp_variable_declarations_finalize(THD *thd, int nvars, } +bool LEX::sp_variable_declarations_rec_finalize(THD *thd, int nvars, + Row_definition_list *src_row, + Item *dflt_value_item, + const LEX_CSTRING &expr_str) +{ + DBUG_ASSERT(src_row); + + // Create a copy of the row definition list to fill + // definitions + Row_definition_list *row= new (thd->mem_root) Row_definition_list(); + if (unlikely(row == NULL)) + return true; + + // Create a deep copy of the elements + List_iterator it(*src_row); + for (Spvar_definition *def= it++; def; def= it++) + { + Spvar_definition *new_def= new (thd->mem_root) Spvar_definition(*def); + if (unlikely(new_def == NULL)) + return true; + + row->push_back(new_def, thd->mem_root); + } + + return sp_variable_declarations_row_finalize(thd, nvars, row, + dflt_value_item, expr_str); +} + bool LEX::sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *dflt_value_item, diff --git a/sql/sql_lex.h b/sql/sql_lex.h index ea504d821048e..838f9d97cb00d 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3859,6 +3859,10 @@ struct LEX: public Query_tables_list const LEX_CSTRING &expr_str); bool sp_variable_declarations_set_default(THD *thd, int nvars, Item *def, const LEX_CSTRING &expr_str); + bool sp_variable_declarations_rec_finalize(THD *thd, int nvars, + Row_definition_list *src_row, + Item *def, + const LEX_CSTRING &expr_str); bool sp_variable_declarations_row_finalize(THD *thd, int nvars, Row_definition_list *row, Item *def, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 1dfed65c776f5..428f5a51b9b33 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -286,6 +286,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() class sp_head *sphead; class sp_name *spname; class sp_variable *spvar; + class sp_record *sprec; class With_element_head *with_element_head; class With_clause *with_clause; class Virtual_column_info *virtual_column; @@ -730,6 +731,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token OTHERS_MARIADB_SYM // SQL-2011-N, PLSQL-R %token PACKAGE_MARIADB_SYM // Oracle-R %token RAISE_MARIADB_SYM // PLSQL-R +%token RECORD_SYM %token ROWTYPE_MARIADB_SYM // PLSQL-R %token ROWNUM_SYM /* Oracle-R */ @@ -1418,7 +1420,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type json_on_response -%type field_type field_type_all +%type field_type field_type_all field_type_all_without_udt + field_type_with_record field_type_all_with_record qualified_field_type field_type_numeric field_type_string @@ -1898,6 +1901,11 @@ rule: %type row_field_name row_field_definition %type row_field_definition_list row_type_body +%ifdef ORACLE +%type rec_field_definition +%type rec_field_definition_anchored +%type rec_field_definition_list rec_type_body +%endif %type opt_window_clause window_def_list window_def window_spec %type window_name @@ -3361,6 +3369,51 @@ row_type_body: '(' row_field_definition_list ')' { $$= $2; } ; +%ifdef ORACLE +rec_field_definition: + row_field_name field_type + { + Lex->last_field->set_attributes(thd, $2, + COLUMN_DEFINITION_ROUTINE_LOCAL); + } + | rec_field_definition_anchored + ; + +rec_field_definition_anchored: + row_field_name sp_decl_ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + if (unlikely(Lex->sphead->spvar_def_fill_type_reference(thd, + $1, $2, + $4))) + MYSQL_YYABORT; + } + | row_field_name sp_decl_ident '.' ident '.' ident PERCENT_ORACLE_SYM TYPE_SYM + { + if (unlikely(Lex->sphead->spvar_def_fill_type_reference(thd, + $1, $2, + $4, $6))) + MYSQL_YYABORT; + } + ; + +rec_field_definition_list: + rec_field_definition + { + if (!($$= Row_definition_list::make(thd->mem_root, $1))) + MYSQL_YYABORT; + } + | rec_field_definition_list ',' rec_field_definition + { + if (($$= $1)->append_uniq(thd->mem_root, $3)) + MYSQL_YYABORT; + } + ; + +rec_type_body: + '(' rec_field_definition_list ')' { $$= $2; } + ; +%endif + sp_decl_idents_init_vars: sp_decl_idents { @@ -3370,7 +3423,7 @@ sp_decl_idents_init_vars: sp_decl_variable_list: sp_decl_idents_init_vars - field_type + field_type_with_record { Lex->last_field->set_attributes(thd, $2, COLUMN_DEFINITION_ROUTINE_LOCAL); @@ -6240,6 +6293,12 @@ field_type: field_type_all } ; +field_type_with_record: field_type_all_with_record + { + Lex->map_data_type(Lex_ident_sys(), &($$= $1)); + } + ; + qualified_field_type: field_type_all { @@ -6258,12 +6317,16 @@ udt_name: | non_reserved_keyword_udt { $$= $1; } ; -field_type_all: +field_type_all_without_udt: field_type_numeric | field_type_temporal | field_type_string | field_type_lob | field_type_misc + ; + +field_type_all: + field_type_all_without_udt | udt_name float_options srid_option { if (Lex->set_field_type_udt(&$$, $1, $2)) @@ -6271,6 +6334,27 @@ field_type_all: } ; +field_type_all_with_record: + field_type_all_without_udt + | udt_name float_options srid_option + { + sp_record *sprec = NULL; + if (Lex->spcont) + sprec = Lex->spcont->find_record(&$1, false); + + if (sprec == NULL) + { + if (Lex->set_field_type_udt(&$$, $1, $2)) + MYSQL_YYABORT; + } + else + { + $$.set(&type_handler_row, NULL); + Lex->last_field->set_attr_const_void_ptr(0, sprec); + } + } + ; + field_type_numeric: int_type opt_field_length last_field_options { @@ -10668,6 +10752,7 @@ function_call_generic: const Type_handler *h; Create_func *builder; Item *item= NULL; + sp_record* rec= NULL; if (unlikely(Lex_ident_routine::check_name_with_error($1))) MYSQL_YYABORT; @@ -10693,6 +10778,11 @@ function_call_generic: { // Found a constructor with a proper argument count } + else if (Lex->spcont && + (rec = Lex->spcont->find_record(&$1, false))) + { + item= new (thd->mem_root) Item_row(thd, *$4); + } else { #ifdef HAVE_DLOPEN @@ -15874,6 +15964,9 @@ keyword_ident: | WINDOW_SYM | EXCEPTION_ORACLE_SYM | IGNORED_SYM +%ifdef ORACLE + | TYPE_SYM +%endif ; keyword_sysvar_name: @@ -16389,7 +16482,9 @@ keyword_func_sp_var_and_label: | TRANSACTIONAL_SYM | THREADS_SYM | TRIGGERS_SYM +%ifdef MARIADB | TYPE_SYM +%endif | UDF_RETURNS_SYM | UNCOMMITTED_SYM | UNDEFINED_SYM @@ -16437,6 +16532,7 @@ keyword_sp_var_and_label: | MONTH_SYM | NEXTVAL_SYM | OVERLAPS_SYM + | RECORD_SYM %ifdef MARIADB | ROWNUM_SYM %endif @@ -19742,6 +19838,14 @@ sp_decl_non_handler: $$.vars= $$.conds= $$.hndlrs= 0; $$.curs= 1; } + | TYPE_SYM ident_directly_assignable IS RECORD_SYM rec_type_body + { + if (unlikely(Lex->spcont-> + declare_record(thd, Lex_ident_column($2), $5))) + MYSQL_YYABORT; + + $$.vars= $$.conds= $$.hndlrs= $$.curs= 0; + } ;