diff options
-rw-r--r-- | mysql-test/suite/compat/oracle/r/sp-code.result | 34 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/r/sp-cursor-rowtype.result | 978 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/t/sp-code.test | 25 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/t/sp-cursor-rowtype.test | 1057 | ||||
-rw-r--r-- | sql/field.h | 25 | ||||
-rw-r--r-- | sql/sp_head.cc | 105 | ||||
-rw-r--r-- | sql/sp_head.h | 65 | ||||
-rw-r--r-- | sql/sp_pcontext.cc | 5 | ||||
-rw-r--r-- | sql/sp_pcontext.h | 10 | ||||
-rw-r--r-- | sql/sp_rcontext.cc | 35 | ||||
-rw-r--r-- | sql/sp_rcontext.h | 10 | ||||
-rw-r--r-- | sql/sql_cursor.cc | 4 | ||||
-rw-r--r-- | sql/sql_cursor.h | 5 | ||||
-rw-r--r-- | sql/sql_lex.cc | 67 | ||||
-rw-r--r-- | sql/sql_lex.h | 9 | ||||
-rw-r--r-- | sql/sql_select.cc | 2 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 32 | ||||
-rw-r--r-- | sql/sql_yacc_ora.yy | 37 | ||||
-rw-r--r-- | sql/table.cc | 22 | ||||
-rw-r--r-- | sql/table.h | 1 |
20 files changed, 2445 insertions, 83 deletions
diff --git a/mysql-test/suite/compat/oracle/r/sp-code.result b/mysql-test/suite/compat/oracle/r/sp-code.result index 9fc8574cbc8..5f21da6eb16 100644 --- a/mysql-test/suite/compat/oracle/r/sp-code.result +++ b/mysql-test/suite/compat/oracle/r/sp-code.result @@ -1007,3 +1007,37 @@ Pos Instruction 5 set rec1.c@0["c"] rec1.d@0["d"] DROP PROCEDURE p1; DROP TABLE t1; +# +# MDEV-12011 sql_mode=ORACLE: cursor%ROWTYPE in variable declarations +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1,rec2 cur1%ROWTYPE; +rec3 cur2%ROWTYPE; +BEGIN +rec1.a:= 10; +rec1.b:= 'bbb'; +END; +END; +$$ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 cpush cur1@0 +1 cpush cur2@1 +2 cursor_copy_struct rec1@0 +3 set rec1@0 NULL +4 cursor_copy_struct rec2@1 +5 set rec2@1 NULL +6 cursor_copy_struct rec3@2 +7 set rec3@2 NULL +8 set rec1.a@0["a"] 10 +9 set rec1.b@0["b"] 'bbb' +10 jump 11 +11 cpop 2 +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/mysql-test/suite/compat/oracle/r/sp-cursor-rowtype.result b/mysql-test/suite/compat/oracle/r/sp-cursor-rowtype.result new file mode 100644 index 00000000000..683b02c3b4f --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-cursor-rowtype.result @@ -0,0 +1,978 @@ +SET sql_mode=ORACLE; +# +# MDEV-12011 sql_mode=ORACLE: cursor%ROWTYPE in variable declarations +# +# +# A complete working example +# +CREATE TABLE t1 (a INT, b VARCHAR(32)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'b10'); +INSERT INTO t1 VALUES (20,'b20'); +INSERT INTO t1 VALUES (30,'b30'); +CREATE PROCEDURE p1 AS +CURSOR c IS SELECT a,b FROM t1; +BEGIN +DECLARE +rec c%ROWTYPE; +BEGIN +OPEN c; +LOOP +FETCH c INTO rec; +EXIT WHEN c%NOTFOUND; +SELECT 'rec=(' || rec.a ||','|| rec.b||')' AS c FROM dual; +INSERT INTO t2 VALUES (rec.a, rec.b); +END LOOP; +CLOSE c; +END; +END; +$$ +CALL p1(); +c +rec=(10,b10) +c +rec=(20,b20) +c +rec=(30,b30) +SELECT * FROM t2; +a b +10 b10 +20 b20 +30 b30 +DROP PROCEDURE p1; +DROP TABLE t2; +DROP TABLE t1; +# +# cursor%ROWTYPE referring to a table in a non-existing database +# +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM tes2.t1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +NULL; +END; +END; +$$ +CALL p1(); +ERROR 42S02: Table 'tes2.t1' doesn't exist +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CALL p1(); +ERROR 42S02: Table 'tes2.t1' doesn't exist +DROP TABLE t1; +DROP PROCEDURE p1; +# +# cursor%ROWTYPE referring to a table in the current database +# +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; +SHOW CREATE TABLE t2; +DROP TABLE t2; +END; +END; +$$ +CALL p1(); +ERROR 42S02: Table 'test.t1' doesn't exist +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CALL p1(); +Table Create Table +t2 CREATE TABLE "t2" ( + "rec.a" bigint(11) DEFAULT NULL, + "rec.b" varchar(10) DEFAULT NULL, + "rec.c" double DEFAULT NULL, + "rec.d" decimal(10,0) DEFAULT NULL +) +DROP TABLE t1; +CALL p1(); +ERROR 42S02: Table 'test.t1' doesn't exist +DROP PROCEDURE p1; +# +# cursor%ROWTYPE referring to a table in an explicitly specified database +# +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM test.t1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; +SHOW CREATE TABLE t2; +DROP TABLE t2; +END; +END; +$$ +CALL p1(); +ERROR 42S02: Table 'test.t1' doesn't exist +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CALL p1(); +Table Create Table +t2 CREATE TABLE "t2" ( + "rec.a" bigint(11) DEFAULT NULL, + "rec.b" varchar(10) DEFAULT NULL, + "rec.c" double DEFAULT NULL, + "rec.d" decimal(10,0) DEFAULT NULL +) +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Cursor%ROWTYPE referring to a view in the current database +# +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM v1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; +SHOW CREATE TABLE t2; +DROP TABLE t2; +END; +END; +$$ +CALL p1(); +ERROR 42S02: Table 'test.v1' doesn't exist +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CREATE VIEW v1 AS SELECT * FROM t1; +CALL p1(); +Table Create Table +t2 CREATE TABLE "t2" ( + "rec.a" bigint(11) DEFAULT NULL, + "rec.b" varchar(10) DEFAULT NULL, + "rec.c" double DEFAULT NULL, + "rec.d" decimal(10,0) DEFAULT NULL +) +DROP VIEW v1; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# cursor%ROWTYPE referring to a view in an explicitly specified database +# +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM test.v1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; +SHOW CREATE TABLE t2; +DROP TABLE t2; +END; +END; +$$ +CALL p1(); +ERROR 42S02: Table 'test.v1' doesn't exist +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CREATE VIEW v1 AS SELECT * FROM t1; +CALL p1(); +Table Create Table +t2 CREATE TABLE "t2" ( + "rec.a" bigint(11) DEFAULT NULL, + "rec.b" varchar(10) DEFAULT NULL, + "rec.c" double DEFAULT NULL, + "rec.d" decimal(10,0) DEFAULT NULL +) +DROP VIEW v1; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Checking that all cursor%ROWTYPE fields are NULL by default +# +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +SELECT rec1.a, rec1.b, rec1.c, rec1.d; +END; +END; +$$ +CALL p1(); +rec1.a rec1.b rec1.c rec1.d +NULL NULL NULL NULL +DROP TABLE t1; +DROP PROCEDURE p1; +# +# A cursor%ROWTYPE variable with a ROW expression as a default +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE := ROW(10,'bbb'); +BEGIN +SELECT rec1.a, rec1.b; +END; +END; +$$ +CALL p1(); +rec1.a rec1.b +10 bbb +DROP TABLE t1; +DROP PROCEDURE p1; +# +# A cursor%ROWTYPE variable with an incompatible ROW expression as a default +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE := ROW(10,'bbb','ccc'); +BEGIN +SELECT rec1.a, rec1.b; +END; +END; +$$ +CALL p1(); +ERROR 21000: Operand should contain 2 column(s) +DROP TABLE t1; +DROP PROCEDURE p1; +# +# A cursor%ROWTYPE variable with a ROW variable as a default +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 ROW(a INT, b VARCHAR(10)):= ROW(10,'bbb'); +rec2 cur%ROWTYPE := rec1; +BEGIN +SELECT rec2.a, rec2.b; +END; +END; +$$ +CALL p1(); +rec2.a rec2.b +10 bbb +DROP TABLE t1; +DROP PROCEDURE p1; +# +# A ROW variable using a cursor%ROWTYPE variable as a default +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE := ROW(10,'bbb'); +rec2 ROW(a INT, b VARCHAR(10)):= rec1; +BEGIN +SELECT rec2.a, rec2.b; +END; +END; +$$ +CALL p1(); +rec2.a rec2.b +10 bbb +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Assigning cursor%ROWTYPE variables with a different column count +# +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE); +CREATE TABLE t2 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +rec2 cur2%ROWTYPE; +BEGIN +rec2:=rec1; +END; +END; +$$ +CALL p1(); +ERROR 21000: Operand should contain 2 column(s) +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +rec2 cur2%ROWTYPE; +BEGIN +rec1:=rec2; +END; +END; +$$ +CALL p1(); +ERROR 21000: Operand should contain 3 column(s) +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Assigning compatible cursor%ROWTYPE variables (equal number of fields) +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (x INT, y VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +rec2 cur2%ROWTYPE; +BEGIN +rec1.a:= 10; +rec1.b:= 'bbb'; +rec2:=rec1; +SELECT rec2.x, rec2.y; +END; +END; +$$ +CALL p1(); +rec2.x rec2.y +10 bbb +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Assigning between incompatible cursor%ROWTYPE and explicit ROW variables +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +rec2 ROW(x INT,y INT,z INT); +BEGIN +rec2.x:= 10; +rec2.y:= 20; +rec2.z:= 30; +rec1:= rec2; +END; +END; +$$ +CALL p1(); +ERROR 21000: Operand should contain 2 column(s) +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Assigning between compatible cursor%ROWTYPE and explicit ROW variables +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +rec2 ROW(x INT,y INT); +BEGIN +rec2.x:= 10; +rec2.y:= 20; +rec1:= rec2; +SELECT rec1.a, rec1.b; +rec1.a:= 11; +rec1.b:= 21; +rec2:= rec1; +SELECT rec2.x, rec2.y; +END; +END; +$$ +CALL p1(); +rec1.a rec1.b +10 20 +rec2.x rec2.y +11 21 +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Assigning cursor%ROWTYPE from a ROW expression +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +rec1:= ROW(10,20); +SELECT rec1.a, rec1.b; +END; +END; +$$ +CALL p1(); +rec1.a rec1.b +10 20 +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Fetching a cursor into a cursor%ROWTYPE variable with a wrong field count +# +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec2 cur2%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec2; +CLOSE cur1; +END; +END; +$$ +CALL p1(); +ERROR HY000: Incorrect number of FETCH variables +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Fetching a cursor into a cursor%ROWTYPE variable +# +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +INSERT INTO t1 VALUES (20,'bb2',222.222e2, 12.32); +INSERT INTO t1 VALUES (30,'bb3',333.333e2, 12.33); +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +OPEN cur; +LOOP +FETCH cur INTO rec; +EXIT WHEN cur%NOTFOUND; +SELECT rec.a, rec.b, rec.c, rec.d; +INSERT INTO t2 VALUES (rec.a, rec.b, rec.c, rec.d); +END LOOP; +CLOSE cur; +END; +END; +$$ +CALL p1(); +rec.a rec.b rec.c rec.d +10 bb1 11111.1 12.31 +rec.a rec.b rec.c rec.d +20 bb2 22222.2 12.32 +rec.a rec.b rec.c rec.d +30 bb3 33333.3 12.33 +SELECT * FROM t2; +a b c d +10 bb1 11111.1 12.31 +20 bb2 22222.2 12.32 +30 bb3 33333.3 12.33 +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Fetching a cursor into a cursor%ROWTYPE variable, cur%ROWTYPE declared inside the LOOP +# +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +INSERT INTO t1 VALUES (20,'bb2',222.222e2, 12.32); +INSERT INTO t1 VALUES (30,'bb3',333.333e2, 12.33); +CREATE PROCEDURE p1() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +OPEN cur; +LOOP +DECLARE +rec cur%ROWTYPE; +BEGIN +FETCH cur INTO rec; +EXIT WHEN cur%NOTFOUND; +SELECT rec.a, rec.b, rec.c, rec.d; +INSERT INTO t2 VALUES (rec.a, rec.b, rec.c, rec.d); +END; +END LOOP; +CLOSE cur; +END; +$$ +CALL p1(); +rec.a rec.b rec.c rec.d +10 bb1 11111.1 12.31 +rec.a rec.b rec.c rec.d +20 bb2 22222.2 12.32 +rec.a rec.b rec.c rec.d +30 bb3 33333.3 12.33 +SELECT * FROM t2; +a b c d +10 bb1 11111.1 12.31 +20 bb2 22222.2 12.32 +30 bb3 33333.3 12.33 +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Fetching a cursor into a cursor%ROWTYPE variable with different column names +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (x INT, y VARCHAR(10)); +INSERT INTO t1 VALUES (10,'bbb'); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec2 cur2%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec2; +SELECT rec2.x, rec2.y; +CLOSE cur1; +END; +END; +$$ +CALL p1(); +rec2.x rec2.y +10 bbb +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Fetching a cursor into a cursor%ROWTYPE variable, with truncation +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (a INT, b INT); +INSERT INTO t1 VALUES (10,'11x'); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +CURSOR cur2 IS SELECT * FROM t2; +BEGIN +DECLARE +rec2 cur2%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec2; +SELECT rec2.a, rec2.b; +CLOSE cur1; +END; +END; +$$ +CALL p1(); +rec2.a rec2.b +10 11 +Warnings: +Warning 1265 Data truncated for column 'b' at row 1 +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; +# +# cursor%ROWTYPE variables are not allowed in LIMIT +# +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (1,2); +CREATE PROCEDURE p1() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE:=(1,2); +BEGIN +SELECT * FROM t1 LIMIT rec1.a; +END; +END; +$$ +ERROR HY000: A variable of a non-integer based type in LIMIT clause +DROP TABLE t1; +# +# cursor%ROWTYPE variable fields as OUT parameters +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1(a OUT INT,b OUT VARCHAR(10)) +AS +BEGIN +a:=10; +b:='bb'; +END; +$$ +CREATE PROCEDURE p2() +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +CALL p1(rec1.a, rec1.b); +SELECT rec1.a, rec1.b; +END; +END; +$$ +CALL p2(); +rec1.a rec1.b +10 bb +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Passing the entire cursor%ROWTYPE variable +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1(a ROW(a INT, b VARCHAR(10))) +AS +BEGIN +SELECT a.a, a.b; +END; +$$ +CREATE PROCEDURE p2() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur%ROWTYPE:= ROW(10,'bb'); +BEGIN +CALL p1(rec1); +END; +END; +$$ +CALL p2(); +a.a a.b +10 bb +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Passing the entire cursor%ROWTYPE variable as an OUT parameter +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1(a OUT ROW(a INT, b VARCHAR(10))) +AS +BEGIN +a:= ROW(10,'bb'); +END; +$$ +CREATE PROCEDURE p2() +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec1 cur%ROWTYPE; +BEGIN +CALL p1(rec1); +SELECT rec1.a, rec1.b; +END; +END; +$$ +CALL p2(); +rec1.a rec1.b +10 bb +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Assigning a cursor%ROWTYPE field to an OUT parameter +# +CREATE PROCEDURE p1 (res IN OUT INTEGER) +AS +a INT:=10; +CURSOR cur1 IS SELECT a FROM DUAL; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec1; +CLOSE cur1; +res:=rec1.a; +END; +END; +$$ +CALL p1(@res); +SELECT @res; +@res +10 +SET @res=NULL; +DROP PROCEDURE p1; +# +# Testing Item_splocal_row_field_by_name::print +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1 +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur1%ROWTYPE:=ROW(10,'bb'); +BEGIN +EXPLAIN EXTENDED SELECT rec.a, rec.b; +END; +END; +$$ +CALL p1(); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select rec.a@0["a"] AS "rec.a",rec.b@0["b"] AS "rec.b" +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Run time error in the cursor statement +# +CREATE PROCEDURE p1 +AS +CURSOR cur1 IS SELECT +10 AS a, +CONCAT(_latin1'a' COLLATE latin1_bin, +_latin1'a' COLLATE latin1_swedish_ci) AS b; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec1; +CLOSE cur1; +SELECT a,b; +END; +END; +$$ +CALL p1(); +ERROR HY000: Illegal mix of collations (latin1_bin,EXPLICIT) and (latin1_swedish_ci,EXPLICIT) for operation '||' +DROP PROCEDURE p1; +# +# Non-existing field +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1 +AS +CURSOR cur1 IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur1%ROWTYPE; +BEGIN +SELECT rec.c; +END; +END; +$$ +CALL p1(); +ERROR HY000: Row variable 'rec' does not have a field 'c' +ALTER TABLE t1 ADD c INT; +ALTER PROCEDURE p1 COMMENT 'test'; +CALL p1(); +rec.c +NULL +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Testing that field names are case insensitive +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE PROCEDURE p1 +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE:=ROW(10,'bb'); +BEGIN +SELECT rec.A, rec.B; +END; +END; +$$ +CALL p1(); +rec.A rec.B +10 bb +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Testing that cursor%ROWTYPE uses temporary tables vs shadowed real tables +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TEMPORARY TABLE t1 (x INT, y VARCHAR(10)); +CREATE PROCEDURE p1 +AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE:=ROW(10,'bb'); +BEGIN +SELECT rec.A, rec.B; +END; +END; +$$ +CALL p1(); +ERROR HY000: Row variable 'rec' does not have a field 'A' +DROP TEMPORARY TABLE t1; +ALTER PROCEDURE p1 COMMENT 'test'; +CALL p1(); +rec.A rec.B +10 bb +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Testing that the structure of cursor%ROWTYPE variables is determined at the CURSOR instantiation time +# +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1 AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DROP TABLE t1; +CREATE TABLE t1 (a INT, b VARCHAR(32), c INT); +DECLARE +rec cur%ROWTYPE; -- This has a column "c" + BEGIN +rec.c:=10; +END; +END; +$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1 AS +CURSOR cur IS SELECT * FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE; -- This does not have a column "c" + BEGIN +DROP TABLE t1; +CREATE TABLE t1 (a INT, b VARCHAR(32), c INT); +rec.c:=10; +END; +END; +$$ +CALL p1(); +ERROR HY000: Row variable 'rec' does not have a field 'c' +DROP TABLE t1; +DROP PROCEDURE p1; +# +# Duplicate field nams in a cursor referenced by %ROWTYPE +# +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +CREATE PROCEDURE p1 AS +CURSOR cur IS SELECT * FROM t1, t2; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +SELECT rec.a; +rec.a:=10; +END; +END; +$$ +CALL p1(); +ERROR 42S21: Duplicate column name 'a' +DROP PROCEDURE p1; +DROP TABLE t2; +DROP TABLE t1; +# +# Tricky field names a cursor referenced by %ROWTYPE +# +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10)); +INSERT INTO t1 VALUES ('a'); +CREATE PROCEDURE p1 AS +CURSOR cur IS SELECT a, CONCAT(a,'a'), CONCAT(a,'ö') FROM t1; +BEGIN +DECLARE +rec cur%ROWTYPE; +BEGIN +OPEN cur; +FETCH cur INTO rec; +CLOSE cur; +SELECT rec.a, rec."CONCAT(a,'a')", rec."CONCAT(a,'ö')"; +END; +END; +$$ +CALL p1(); +rec.a rec."CONCAT(a,'a')" rec."CONCAT(a,'ö')" +a aa aö +DROP PROCEDURE p1; +DROP TABLE t1; +SET NAMES latin1; +# +# Using definitions recursively (cursor%ROWTYPE variables in another cursor SELECT) +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b1'),(20,'b2'),(30,'b3'); +CREATE PROCEDURE p1 AS +CURSOR cur1 IS SELECT a,b FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE:=ROW(0,'b0'); +CURSOR cur2 IS SELECT rec1.a AS a, rec1.b AS b FROM t1; +BEGIN +DECLARE +rec2 cur2%ROWTYPE; +BEGIN +OPEN cur2; +LOOP +FETCH cur2 INTO rec2; +EXIT WHEN cur2%NOTFOUND; +SELECT rec2.a, rec2.b; +END LOOP; +CLOSE cur2; +END; +END; +END; +$$ +CALL p1(); +rec2.a rec2.b +0 b0 +rec2.a rec2.b +0 b0 +rec2.a rec2.b +0 b0 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Testing queries with auto-generated Items. +# An instance of Item_func_conv_charset is created during the below SELECT query. +# We check here that during an implicit cursor OPEN +# done in sp_instr_cursor_copy_struct::exec_core() +# all temporary Items are created on a proper memory root and are safely destroyed. +# +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1, b VARCHAR(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES (0xFF, 'a'); +CREATE PROCEDURE p1 +AS +CURSOR cur1 IS SELECT CONCAT(a,b) AS c FROM t1; +BEGIN +DECLARE +rec1 cur1%ROWTYPE; +BEGIN +OPEN cur1; +FETCH cur1 INTO rec1; +CLOSE cur1; +SELECT HEX(rec1.c); +END; +END; +$$ +CALL p1(); +HEX(rec1.c) +C3BF61 +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/mysql-test/suite/compat/oracle/t/sp-code.test b/mysql-test/suite/compat/oracle/t/sp-code.test index 53ffeb7bc01..bc766e868fd 100644 --- a/mysql-test/suite/compat/oracle/t/sp-code.test +++ b/mysql-test/suite/compat/oracle/t/sp-code.test @@ -764,3 +764,28 @@ DELIMITER ;$$ SHOW PROCEDURE CODE p1; DROP PROCEDURE p1; DROP TABLE t1; + + +--echo # +--echo # MDEV-12011 sql_mode=ORACLE: cursor%ROWTYPE in variable declarations +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1,rec2 cur1%ROWTYPE; + rec3 cur2%ROWTYPE; + BEGIN + rec1.a:= 10; + rec1.b:= 'bbb'; + END; +END; +$$ +DELIMITER ;$$ +SHOW PROCEDURE CODE p1; +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/mysql-test/suite/compat/oracle/t/sp-cursor-rowtype.test b/mysql-test/suite/compat/oracle/t/sp-cursor-rowtype.test new file mode 100644 index 00000000000..b873ba8c11b --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-cursor-rowtype.test @@ -0,0 +1,1057 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-12011 sql_mode=ORACLE: cursor%ROWTYPE in variable declarations +--echo # + +--echo # +--echo # A complete working example +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(32)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'b10'); +INSERT INTO t1 VALUES (20,'b20'); +INSERT INTO t1 VALUES (30,'b30'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR c IS SELECT a,b FROM t1; +BEGIN + DECLARE + rec c%ROWTYPE; + BEGIN + OPEN c; + LOOP + FETCH c INTO rec; + EXIT WHEN c%NOTFOUND; + SELECT 'rec=(' || rec.a ||','|| rec.b||')' AS c FROM dual; + INSERT INTO t2 VALUES (rec.a, rec.b); + END LOOP; + CLOSE c; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t2; +DROP PROCEDURE p1; +DROP TABLE t2; +DROP TABLE t1; + + +--echo # +--echo # cursor%ROWTYPE referring to a table in a non-existing database +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM tes2.t1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + NULL; + END; +END; +$$ +DELIMITER ;$$ +--error ER_NO_SUCH_TABLE +CALL p1(); +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +--error ER_NO_SUCH_TABLE +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # cursor%ROWTYPE referring to a table in the current database +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; + SHOW CREATE TABLE t2; + DROP TABLE t2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_NO_SUCH_TABLE +CALL p1(); +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CALL p1(); +DROP TABLE t1; +--error ER_NO_SUCH_TABLE +CALL p1(); +DROP PROCEDURE p1; + + +--echo # +--echo # cursor%ROWTYPE referring to a table in an explicitly specified database +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM test.t1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; + SHOW CREATE TABLE t2; + DROP TABLE t2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_NO_SUCH_TABLE +CALL p1(); +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Cursor%ROWTYPE referring to a view in the current database +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM v1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; + SHOW CREATE TABLE t2; + DROP TABLE t2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_NO_SUCH_TABLE +CALL p1(); +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CREATE VIEW v1 AS SELECT * FROM t1; +CALL p1(); +DROP VIEW v1; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # cursor%ROWTYPE referring to a view in an explicitly specified database +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM test.v1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + CREATE TABLE t2 AS SELECT rec.a, rec.b, rec.c, rec.d; + SHOW CREATE TABLE t2; + DROP TABLE t2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_NO_SUCH_TABLE +CALL p1(); +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10)); +CREATE VIEW v1 AS SELECT * FROM t1; +CALL p1(); +DROP VIEW v1; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Checking that all cursor%ROWTYPE fields are NULL by default +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + SELECT rec1.a, rec1.b, rec1.c, rec1.d; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # A cursor%ROWTYPE variable with a ROW expression as a default +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE := ROW(10,'bbb'); + BEGIN + SELECT rec1.a, rec1.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # A cursor%ROWTYPE variable with an incompatible ROW expression as a default +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE := ROW(10,'bbb','ccc'); + BEGIN + SELECT rec1.a, rec1.b; + END; +END; +$$ +DELIMITER ;$$ +--error ER_OPERAND_COLUMNS +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # A cursor%ROWTYPE variable with a ROW variable as a default +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 ROW(a INT, b VARCHAR(10)):= ROW(10,'bbb'); + rec2 cur%ROWTYPE := rec1; + BEGIN + SELECT rec2.a, rec2.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # A ROW variable using a cursor%ROWTYPE variable as a default +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE := ROW(10,'bbb'); + rec2 ROW(a INT, b VARCHAR(10)):= rec1; + BEGIN + SELECT rec2.a, rec2.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Assigning cursor%ROWTYPE variables with a different column count +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE); +CREATE TABLE t2 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + rec2 cur2%ROWTYPE; + BEGIN + rec2:=rec1; + END; +END; +$$ +DELIMITER ;$$ +--error ER_OPERAND_COLUMNS +CALL p1(); +DROP PROCEDURE p1; +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + rec2 cur2%ROWTYPE; + BEGIN + rec1:=rec2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_OPERAND_COLUMNS +CALL p1(); +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Assigning compatible cursor%ROWTYPE variables (equal number of fields) +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (x INT, y VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + rec2 cur2%ROWTYPE; + BEGIN + rec1.a:= 10; + rec1.b:= 'bbb'; + rec2:=rec1; + SELECT rec2.x, rec2.y; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Assigning between incompatible cursor%ROWTYPE and explicit ROW variables +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + rec2 ROW(x INT,y INT,z INT); + BEGIN + rec2.x:= 10; + rec2.y:= 20; + rec2.z:= 30; + rec1:= rec2; + END; +END; +$$ +DELIMITER ;$$ +--error ER_OPERAND_COLUMNS +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Assigning between compatible cursor%ROWTYPE and explicit ROW variables +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + rec2 ROW(x INT,y INT); + BEGIN + rec2.x:= 10; + rec2.y:= 20; + rec1:= rec2; + SELECT rec1.a, rec1.b; + rec1.a:= 11; + rec1.b:= 21; + rec2:= rec1; + SELECT rec2.x, rec2.y; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Assigning cursor%ROWTYPE from a ROW expression +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + rec1:= ROW(10,20); + SELECT rec1.a, rec1.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching a cursor into a cursor%ROWTYPE variable with a wrong field count +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec2 cur2%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec2; + CLOSE cur1; + END; +END; +$$ +DELIMITER ;$$ +--error ER_SP_WRONG_NO_OF_FETCH_ARGS +CALL p1(); +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching a cursor into a cursor%ROWTYPE variable +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +INSERT INTO t1 VALUES (20,'bb2',222.222e2, 12.32); +INSERT INTO t1 VALUES (30,'bb3',333.333e2, 12.33); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + OPEN cur; + LOOP + FETCH cur INTO rec; + EXIT WHEN cur%NOTFOUND; + SELECT rec.a, rec.b, rec.c, rec.d; + INSERT INTO t2 VALUES (rec.a, rec.b, rec.c, rec.d); + END LOOP; + CLOSE cur; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t2; +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching a cursor into a cursor%ROWTYPE variable, cur%ROWTYPE declared inside the LOOP +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10), c DOUBLE, d DECIMAL(10,2)); +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (10,'bb1',111.111e2, 12.31); +INSERT INTO t1 VALUES (20,'bb2',222.222e2, 12.32); +INSERT INTO t1 VALUES (30,'bb3',333.333e2, 12.33); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + OPEN cur; + LOOP + DECLARE + rec cur%ROWTYPE; + BEGIN + FETCH cur INTO rec; + EXIT WHEN cur%NOTFOUND; + SELECT rec.a, rec.b, rec.c, rec.d; + INSERT INTO t2 VALUES (rec.a, rec.b, rec.c, rec.d); + END; + END LOOP; + CLOSE cur; +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t2; +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching a cursor into a cursor%ROWTYPE variable with different column names +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (x INT, y VARCHAR(10)); +INSERT INTO t1 VALUES (10,'bbb'); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec2 cur2%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec2; + SELECT rec2.x, rec2.y; + CLOSE cur1; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching a cursor into a cursor%ROWTYPE variable, with truncation +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TABLE t2 (a INT, b INT); +INSERT INTO t1 VALUES (10,'11x'); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; + CURSOR cur2 IS SELECT * FROM t2; +BEGIN + DECLARE + rec2 cur2%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec2; + SELECT rec2.a, rec2.b; + CLOSE cur1; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t2; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # cursor%ROWTYPE variables are not allowed in LIMIT +--echo # +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (1,2); +DELIMITER $$; +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +CREATE PROCEDURE p1() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE:=(1,2); + BEGIN + SELECT * FROM t1 LIMIT rec1.a; + END; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # cursor%ROWTYPE variable fields as OUT parameters +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1(a OUT INT,b OUT VARCHAR(10)) +AS +BEGIN + a:=10; + b:='bb'; +END; +$$ +CREATE PROCEDURE p2() +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + CALL p1(rec1.a, rec1.b); + SELECT rec1.a, rec1.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p2(); +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Passing the entire cursor%ROWTYPE variable +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1(a ROW(a INT, b VARCHAR(10))) +AS +BEGIN + SELECT a.a, a.b; +END; +$$ +CREATE PROCEDURE p2() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur%ROWTYPE:= ROW(10,'bb'); + BEGIN + CALL p1(rec1); + END; +END; +$$ +DELIMITER ;$$ +CALL p2(); +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Passing the entire cursor%ROWTYPE variable as an OUT parameter +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1(a OUT ROW(a INT, b VARCHAR(10))) +AS +BEGIN + a:= ROW(10,'bb'); +END; +$$ +CREATE PROCEDURE p2() +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec1 cur%ROWTYPE; + BEGIN + CALL p1(rec1); + SELECT rec1.a, rec1.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p2(); +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Assigning a cursor%ROWTYPE field to an OUT parameter +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 (res IN OUT INTEGER) +AS + a INT:=10; + CURSOR cur1 IS SELECT a FROM DUAL; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec1; + CLOSE cur1; + res:=rec1.a; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(@res); +SELECT @res; +SET @res=NULL; +DROP PROCEDURE p1; + + +--echo # +--echo # Testing Item_splocal_row_field_by_name::print +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur1%ROWTYPE:=ROW(10,'bb'); + BEGIN + EXPLAIN EXTENDED SELECT rec.a, rec.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Run time error in the cursor statement +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur1 IS SELECT + 10 AS a, + CONCAT(_latin1'a' COLLATE latin1_bin, + _latin1'a' COLLATE latin1_swedish_ci) AS b; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec1; + CLOSE cur1; + SELECT a,b; + END; +END; +$$ +DELIMITER ;$$ +--error ER_CANT_AGGREGATE_2COLLATIONS +CALL p1(); +DROP PROCEDURE p1; + + + +--echo # +--echo # Non-existing field +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur1 IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur1%ROWTYPE; + BEGIN + SELECT rec.c; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1(); +ALTER TABLE t1 ADD c INT; +# +# The below ALTER is needed as a workaround to call sp_cache_invalidate() +# Please remove it after fixing MDEV-12166 +# +ALTER PROCEDURE p1 COMMENT 'test'; +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Testing that field names are case insensitive +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE:=ROW(10,'bb'); + BEGIN + SELECT rec.A, rec.B; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Testing that cursor%ROWTYPE uses temporary tables vs shadowed real tables +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +CREATE TEMPORARY TABLE t1 (x INT, y VARCHAR(10)); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE:=ROW(10,'bb'); + BEGIN + SELECT rec.A, rec.B; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1(); +DROP TEMPORARY TABLE t1; +# +# The below ALTER is needed as a workaround to call sp_cache_invalidate() +# Please remove it after fixing MDEV-12166 +# +ALTER PROCEDURE p1 COMMENT 'test'; +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Testing that the structure of cursor%ROWTYPE variables is determined at the CURSOR instantiation time +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DROP TABLE t1; + CREATE TABLE t1 (a INT, b VARCHAR(32), c INT); + DECLARE + rec cur%ROWTYPE; -- This has a column "c" + BEGIN + rec.c:=10; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR cur IS SELECT * FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE; -- This does not have a column "c" + BEGIN + DROP TABLE t1; + CREATE TABLE t1 (a INT, b VARCHAR(32), c INT); + rec.c:=10; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1(); +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Duplicate field nams in a cursor referenced by %ROWTYPE +--echo # + +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR cur IS SELECT * FROM t1, t2; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + SELECT rec.a; + rec.a:=10; + END; +END; +$$ +DELIMITER ;$$ +--error ER_DUP_FIELDNAME +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t2; +DROP TABLE t1; + + +--echo # +--echo # Tricky field names a cursor referenced by %ROWTYPE +--echo # + +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10)); +INSERT INTO t1 VALUES ('a'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR cur IS SELECT a, CONCAT(a,'a'), CONCAT(a,'ö') FROM t1; +BEGIN + DECLARE + rec cur%ROWTYPE; + BEGIN + OPEN cur; + FETCH cur INTO rec; + CLOSE cur; + SELECT rec.a, rec."CONCAT(a,'a')", rec."CONCAT(a,'ö')"; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; +SET NAMES latin1; + + +--echo # +--echo # Using definitions recursively (cursor%ROWTYPE variables in another cursor SELECT) +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b1'),(20,'b2'),(30,'b3'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + CURSOR cur1 IS SELECT a,b FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE:=ROW(0,'b0'); + CURSOR cur2 IS SELECT rec1.a AS a, rec1.b AS b FROM t1; + BEGIN + DECLARE + rec2 cur2%ROWTYPE; + BEGIN + OPEN cur2; + LOOP + FETCH cur2 INTO rec2; + EXIT WHEN cur2%NOTFOUND; + SELECT rec2.a, rec2.b; + END LOOP; + CLOSE cur2; + END; + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Testing queries with auto-generated Items. +--echo # An instance of Item_func_conv_charset is created during the below SELECT query. +--echo # We check here that during an implicit cursor OPEN +--echo # done in sp_instr_cursor_copy_struct::exec_core() +--echo # all temporary Items are created on a proper memory root and are safely destroyed. +--echo # + +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1, b VARCHAR(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES (0xFF, 'a'); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur1 IS SELECT CONCAT(a,b) AS c FROM t1; +BEGIN + DECLARE + rec1 cur1%ROWTYPE; + BEGIN + OPEN cur1; + FETCH cur1 INTO rec1; + CLOSE cur1; + SELECT HEX(rec1.c); + END; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/sql/field.h b/sql/field.h index e110e1f8146..3b96dd546cb 100644 --- a/sql/field.h +++ b/sql/field.h @@ -3987,6 +3987,16 @@ public: }; +class Cursor_rowtype: public Sql_alloc +{ +public: + uint m_cursor; + Cursor_rowtype(uint cursor) + :m_cursor(cursor) + { } +}; + + /** This class is used during a stored routine or a trigger execution, at sp_rcontext::create() time. @@ -4010,20 +4020,25 @@ class Spvar_definition: public Column_definition { class Qualified_column_ident *m_column_type_ref; // for %TYPE class Table_ident *m_table_rowtype_ref; // for table%ROWTYPE + class Cursor_rowtype *m_cursor_rowtype_ref; // for cursor%ROWTYPE Row_definition_list *m_row_field_definitions; // for ROW public: Spvar_definition() :m_column_type_ref(NULL), m_table_rowtype_ref(NULL), - m_row_field_definitions(NULL) { } + m_cursor_rowtype_ref(NULL), + m_row_field_definitions(NULL) + { } Spvar_definition(THD *thd, Field *field) :Column_definition(thd, field, NULL), m_column_type_ref(NULL), m_table_rowtype_ref(NULL), + m_cursor_rowtype_ref(NULL), m_row_field_definitions(NULL) { } bool is_column_type_ref() const { return m_column_type_ref != 0; } bool is_table_rowtype_ref() const { return m_table_rowtype_ref != 0; } + bool is_cursor_rowtype_ref() const { return m_cursor_rowtype_ref != NULL; } class Qualified_column_ident *column_type_ref() const { return m_column_type_ref; @@ -4041,6 +4056,14 @@ public: { m_table_rowtype_ref= ref; } + class Cursor_rowtype *cursor_rowtype_ref() const + { + return m_cursor_rowtype_ref; + } + void set_cursor_rowtype_ref(class Cursor_rowtype *ref) + { + m_cursor_rowtype_ref= ref; + } /* Find a ROW field by name. diff --git a/sql/sp_head.cc b/sql/sp_head.cc index a40ad52676c..2b5b1db5ddf 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3152,6 +3152,26 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, } +int sp_lex_keeper::cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, + bool open_tables, + sp_instr *instr) +{ + Query_arena *old_arena= thd->stmt_arena; + /* + Get the Query_arena from the cursor statement LEX, which contains + the free_list of the query, so new items (if any) are stored in + the right free_list, and we can cleanup after each cursor operation, + e.g. open or cursor_copy_struct (for cursor%ROWTYPE variables). + */ + thd->stmt_arena= m_lex->query_arena(); + int res= reset_lex_and_exec_core(thd, nextp, open_tables, instr); + if (thd->stmt_arena->free_list) + cleanup_items(thd->stmt_arena->free_list); + thd->stmt_arena= old_arena; + return res; +} + + /* sp_instr class functions */ @@ -3455,7 +3475,8 @@ sp_instr_set_row_field_by_name::print(String *str) int rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3 + 2; sp_variable *var= m_ctx->find_variable(m_offset); DBUG_ASSERT(var); - DBUG_ASSERT(var->field_def.is_table_rowtype_ref()); + DBUG_ASSERT(var->field_def.is_table_rowtype_ref() || + var->field_def.is_cursor_rowtype_ref()); rsrv+= var->name.length + 2 * m_field_name.length; if (str->reserve(rsrv)) @@ -3915,7 +3936,7 @@ sp_instr_cpush::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_cpush::execute"); - int ret= thd->spcont->push_cursor(thd, &m_lex_keeper, this); + int ret= thd->spcont->push_cursor(thd, &m_lex_keeper); *nextp= m_ip+1; @@ -3995,19 +4016,7 @@ sp_instr_copen::execute(THD *thd, uint *nextp) else { sp_lex_keeper *lex_keeper= c->get_lex_keeper(); - Query_arena *old_arena= thd->stmt_arena; - - /* - Get the Query_arena from the cpush instruction, which contains - the free_list of the query, so new items (if any) are stored in - the right free_list, and we can cleanup after each open. - */ - thd->stmt_arena= c->get_instr(); - res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this); - /* Cleanup the query's items */ - if (thd->stmt_arena->free_list) - cleanup_items(thd->stmt_arena->free_list); - thd->stmt_arena= old_arena; + res= lex_keeper->cursor_reset_lex_and_exec_core(thd, nextp, FALSE, this); /* TODO: Assert here that we either have an error or a cursor */ } DBUG_RETURN(res); @@ -4140,6 +4149,72 @@ sp_instr_cfetch::print(String *str) /* + sp_instr_cursor_copy_struct class functions +*/ + +int +sp_instr_cursor_copy_struct::exec_core(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cusrot_copy_struct::exec_core"); + int ret= 0; + Item_field_row *row= (Item_field_row*) thd->spcont->get_item(m_var); + DBUG_ASSERT(row->type_handler() == &type_handler_row); + + /* + Copy structure only once. If the cursor%ROWTYPE variable is declared + inside a LOOP block, it gets its structure on the first loop interation + and remembers the structure for all consequent loop iterations. + It we recreated the structure on every iteration, we would get + potential memory leaks, and it would be less efficient. + */ + if (!row->arguments()) + { + sp_cursor tmp(thd, &m_lex_keeper); + if (!(ret= tmp.open_view_structure_only(thd))) + { + Row_definition_list defs; + if (!(ret= tmp.export_structure(thd, &defs))) + { + /* + Create row elements on the caller arena. + It's the same arena that was used during sp_rcontext::create(). + This puts cursor%ROWTYPE elements on the same mem_root + where explicit ROW elements and table%ROWTYPE reside. + */ + Query_arena current_arena; + thd->set_n_backup_active_arena(thd->spcont->callers_arena, ¤t_arena); + row->row_create_items(thd, &defs); + thd->restore_active_arena(thd->spcont->callers_arena, ¤t_arena); + } + tmp.close(thd); + } + } + *nextp= m_ip + 1; + DBUG_RETURN(ret); +} + + +int +sp_instr_cursor_copy_struct::execute(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cursor_copy_struct::execute"); + int ret= m_lex_keeper.cursor_reset_lex_and_exec_core(thd, nextp, FALSE, this); + DBUG_RETURN(ret); +} + + +void +sp_instr_cursor_copy_struct::print(String *str) +{ + sp_variable *var= m_ctx->find_variable(m_var); + str->append(STRING_WITH_LEN("cursor_copy_struct ")); + str->append(var->name.str, var->name.length); + str->append('@'); + str->append_ulonglong(m_var); +} + + +/* sp_instr_error class functions */ diff --git a/sql/sp_head.h b/sql/sp_head.h index 92f17884285..37ef4b6825f 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -341,6 +341,8 @@ public: bool show_create_routine(THD *thd, int type); + MEM_ROOT *get_main_mem_root() { return &main_mem_root; } + int add_instr(sp_instr *instr); @@ -767,6 +769,42 @@ private: }; // class sp_head : public Sql_alloc +class sp_lex_cursor: public sp_lex_local, public Query_arena +{ +public: + sp_lex_cursor(THD *thd, const LEX *oldlex, MEM_ROOT *mem_root_arg) + :sp_lex_local(thd, oldlex), + Query_arena(mem_root_arg, STMT_INITIALIZED_FOR_SP) + { } + sp_lex_cursor(THD *thd, const LEX *oldlex) + :sp_lex_local(thd, oldlex), + Query_arena(thd->lex->sphead->get_main_mem_root(), STMT_INITIALIZED_FOR_SP) + { } + ~sp_lex_cursor() { free_items(); } + void cleanup_stmt() { } + Query_arena *query_arena() { return this; } + bool validate() + { + DBUG_ASSERT(sql_command == SQLCOM_SELECT); + if (result) + { + my_error(ER_SP_BAD_CURSOR_SELECT, MYF(0)); + return true; + } + return false; + } + bool stmt_finalize(THD *thd) + { + if (validate()) + return true; + sp_lex_in_use= true; + free_list= thd->free_list; + thd->free_list= NULL; + return false; + } +}; + + // // "Instructions"... // @@ -924,6 +962,9 @@ public: int reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables, sp_instr* instr); + int cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables, + sp_instr *instr); + inline uint sql_command() const { return (uint)m_lex->sql_command; @@ -1550,6 +1591,30 @@ private: }; // class sp_instr_copen : public sp_instr_stmt +/** + Initialize the structure of a cursor%ROWTYPE variable + from the LEX containing the cursor SELECT statement. +*/ +class sp_instr_cursor_copy_struct: public sp_instr +{ + /**< Prevent use of these */ + sp_instr_cursor_copy_struct(const sp_instr_cursor_copy_struct &); + void operator=(sp_instr_cursor_copy_struct &); + sp_lex_keeper m_lex_keeper; + uint m_var; +public: + sp_instr_cursor_copy_struct(uint ip, sp_pcontext *ctx, + sp_lex_cursor *lex, uint voffs) + : sp_instr(ip, ctx), m_lex_keeper(lex, FALSE), m_var(voffs) + {} + virtual ~sp_instr_cursor_copy_struct() + {} + virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); +}; + + class sp_instr_cclose : public sp_instr { sp_instr_cclose(const sp_instr_cclose &); /**< Prevent use of these */ diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc index 332f43db846..3a3ad0713b5 100644 --- a/sql/sp_pcontext.cc +++ b/sql/sp_pcontext.cc @@ -553,12 +553,13 @@ sp_pcontext::find_handler(const Sql_condition_identity &value) const } -bool sp_pcontext::add_cursor(const LEX_STRING name, sp_pcontext *param_ctx) +bool sp_pcontext::add_cursor(const LEX_STRING name, sp_pcontext *param_ctx, + sp_lex_cursor *lex) { if (m_cursors.elements() == m_max_cursor_index) ++m_max_cursor_index; - return m_cursors.append(sp_pcursor(name, param_ctx)); + return m_cursors.append(sp_pcursor(name, param_ctx, lex)); } diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index 85d0afa8feb..6d8f5e1baf8 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -288,11 +288,14 @@ public: class sp_pcursor: public LEX_STRING { class sp_pcontext *m_param_context; // Formal parameters + class sp_lex_cursor *m_lex; // The cursor statement LEX public: - sp_pcursor(const LEX_STRING &name, class sp_pcontext *param_ctx) - :LEX_STRING(name), m_param_context(param_ctx) + sp_pcursor(const LEX_STRING &name, class sp_pcontext *param_ctx, + class sp_lex_cursor *lex) + :LEX_STRING(name), m_param_context(param_ctx), m_lex(lex) { } class sp_pcontext *param_context() const { return m_param_context; } + class sp_lex_cursor *lex() const { return m_lex; } }; @@ -633,7 +636,8 @@ public: // Cursors. ///////////////////////////////////////////////////////////////////////// - bool add_cursor(const LEX_STRING name, sp_pcontext *param_ctx); + bool add_cursor(const LEX_STRING name, sp_pcontext *param_ctx, + class sp_lex_cursor *lex); /// See comment for find_variable() above. const sp_pcursor *find_cursor(const LEX_STRING name, diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index b631ad71e49..d60d7f5118f 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -286,6 +286,13 @@ bool sp_rcontext::init_var_items(THD *thd, item->row_create_items(thd, &defs)) return true; } + else if (def->is_cursor_rowtype_ref()) + { + Row_definition_list defs; + Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field); + if (!(m_var_items[idx]= item)) + return true; + } else if (def->is_row()) { Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field); @@ -341,14 +348,13 @@ bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) } -bool sp_rcontext::push_cursor(THD *thd, sp_lex_keeper *lex_keeper, - sp_instr_cpush *i) +bool sp_rcontext::push_cursor(THD *thd, sp_lex_keeper *lex_keeper) { /* We should create cursors in the callers arena, as it could be (and usually is) used in several instructions. */ - sp_cursor *c= new (callers_arena->mem_root) sp_cursor(thd, lex_keeper, i); + sp_cursor *c= new (callers_arena->mem_root) sp_cursor(thd, lex_keeper); if (c == NULL) return true; @@ -687,11 +693,10 @@ bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id, /////////////////////////////////////////////////////////////////////////// -sp_cursor::sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper, sp_instr_cpush *i): +sp_cursor::sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper): result(thd_arg), m_lex_keeper(lex_keeper), server_side_cursor(NULL), - m_i(i), m_fetch_count(0), m_row_count(0), m_found(false) @@ -731,6 +736,21 @@ int sp_cursor::open(THD *thd) } +int sp_cursor::open_view_structure_only(THD *thd) +{ + int res; + int thd_no_errors_save= thd->no_errors; + Item *limit_rows_examined= thd->lex->limit_rows_examined; + if (!(thd->lex->limit_rows_examined= new (thd->mem_root) Item_uint(thd, 0))) + return -1; + thd->no_errors= true; // Suppress ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT + res= open(thd); + thd->no_errors= thd_no_errors_save; + thd->lex->limit_rows_examined= limit_rows_examined; + return res; +} + + int sp_cursor::close(THD *thd) { if (! server_side_cursor) @@ -802,6 +822,11 @@ int sp_cursor::fetch(THD *thd, List<sp_variable> *vars) } +bool sp_cursor::export_structure(THD *thd, Row_definition_list *list) +{ + return server_side_cursor->export_structure(thd, list); +} + /////////////////////////////////////////////////////////////////////////// // sp_cursor::Select_fetch_into_spvars implementation. /////////////////////////////////////////////////////////////////////////// diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 1c47b1caff0..98464518787 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -272,7 +272,7 @@ public: /// @return error flag. /// @retval false on success. /// @retval true on error. - bool push_cursor(THD *thd, sp_lex_keeper *lex_keeper, sp_instr_cpush *i); + bool push_cursor(THD *thd, sp_lex_keeper *lex_keeper); /// Pop and delete given number of sp_cursor instance from the cursor stack. /// @@ -434,7 +434,7 @@ private: }; public: - sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper, sp_instr_cpush *i); + sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper); virtual ~sp_cursor() { destroy(); } @@ -443,6 +443,8 @@ public: int open(THD *thd); + int open_view_structure_only(THD *thd); + int close(THD *thd); my_bool is_open() @@ -459,14 +461,12 @@ public: int fetch(THD *, List<sp_variable> *vars); - sp_instr_cpush *get_instr() - { return m_i; } + bool export_structure(THD *thd, Row_definition_list *list); private: Select_fetch_into_spvars result; sp_lex_keeper *m_lex_keeper; Server_side_cursor *server_side_cursor; - sp_instr_cpush *m_i; // My push instruction ulonglong m_fetch_count; // Number of FETCH commands since last OPEN ulonglong m_row_count; // Number of successful FETCH since last OPEN bool m_found; // If last FETCH fetched a row diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 60d80387a10..40eb7046108 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -53,6 +53,10 @@ public: virtual int open(JOIN *join __attribute__((unused))); virtual void fetch(ulong num_rows); virtual void close(); + bool export_structure(THD *thd, Row_definition_list *defs) + { + return table->export_structure(thd, defs); + } virtual ~Materialized_cursor(); void on_table_fill_finished(); diff --git a/sql/sql_cursor.h b/sql/sql_cursor.h index bff47d654b3..6fa72a2005d 100644 --- a/sql/sql_cursor.h +++ b/sql/sql_cursor.h @@ -54,6 +54,11 @@ public: virtual int open(JOIN *top_level_join)= 0; virtual void fetch(ulong num_rows)= 0; virtual void close()= 0; + virtual bool export_structure(THD *thd, Row_definition_list *defs) + { + DBUG_ASSERT(0); + return true; + } virtual ~Server_side_cursor(); static void operator delete(void *ptr, size_t size); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 8f258f50902..ca35535973e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -5297,6 +5297,9 @@ LEX::sp_variable_declarations_rowtype_finalize(THD *thd, int nvars, Qualified_column_ident *ref, Item *def) { + uint coffp; + const sp_pcursor *pcursor= ref->table.str && ref->db.str ? NULL : + spcont->find_cursor(ref->m_column, &coffp, false); uint num_vars= spcont->context_var_count(); if (!def && !(def= new (thd->mem_root) Item_null(thd))) @@ -5307,24 +5310,41 @@ LEX::sp_variable_declarations_rowtype_finalize(THD *thd, int nvars, bool last= i == num_vars - 1; uint var_idx= spcont->var_context2runtime(i); sp_variable *spvar= spcont->find_context_variable(i); - /* - When parsing a qualified identifier chain, the parser does not know yet - if it's going to be a qualified column name (for %TYPE), - or a qualified table name (for %ROWTYPE). So it collects the chain - into Qualified_column_ident. - Now we know that it was actually a qualified table name (%ROWTYPE). - Create a new Table_ident from Qualified_column_ident, - shifting fields as follows: - - ref->m_column becomes table_ref->table - - ref->table becomes table_ref->db - */ - Table_ident *table_ref; - if (!(table_ref= new (thd->mem_root) Table_ident(thd, - ref->table, - ref->m_column, - false))) - return true; - spvar->field_def.set_table_rowtype_ref(table_ref); + + if (pcursor) + { + Cursor_rowtype *ref; + if (!(ref= new (thd->mem_root) Cursor_rowtype(coffp))) + return true; + spvar->field_def.set_cursor_rowtype_ref(ref); + sp_instr_cursor_copy_struct *instr= + new (thd->mem_root) sp_instr_cursor_copy_struct(sphead->instructions(), + spcont, pcursor->lex(), + spvar->offset); + if (instr == NULL || sphead->add_instr(instr)) + return true; + } + else + { + /* + When parsing a qualified identifier chain, the parser does not know yet + if it's going to be a qualified column name (for %TYPE), + or a qualified table name (for %ROWTYPE). So it collects the chain + into Qualified_column_ident. + Now we know that it was actually a qualified table name (%ROWTYPE). + Create a new Table_ident from Qualified_column_ident, + shifting fields as follows: + - ref->m_column becomes table_ref->table + - ref->table becomes table_ref->db + */ + Table_ident *table_ref; + if (!(table_ref= new (thd->mem_root) Table_ident(thd, + ref->table, + ref->m_column, + false))) + return true; + spvar->field_def.set_table_rowtype_ref(table_ref); + } spvar->field_def.field_name= spvar->name.str; spvar->default_value= def; /* The last instruction is responsible for freeing LEX. */ @@ -5485,7 +5505,8 @@ bool LEX::sp_for_loop_finalize(THD *thd, const Lex_for_loop_st &loop) /***************************************************************************/ -bool LEX::sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt, +bool LEX::sp_declare_cursor(THD *thd, const LEX_STRING name, + sp_lex_cursor *cursor_stmt, sp_pcontext *param_ctx) { uint offp; @@ -5501,7 +5522,7 @@ bool LEX::sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt, spcont->current_cursor_count()); return i == NULL || sphead->add_instr(i) || - spcont->add_cursor(name, param_ctx); + spcont->add_cursor(name, param_ctx, cursor_stmt); } @@ -6178,7 +6199,8 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, } Item_splocal *item; - if (spv->field_def.is_table_rowtype_ref()) + if (spv->field_def.is_table_rowtype_ref() || + spv->field_def.is_cursor_rowtype_ref()) { if (!(item= new (thd->mem_root) Item_splocal_row_field_by_name(thd, a, b, spv->offset, @@ -6406,7 +6428,8 @@ bool LEX::set_variable(const LEX_STRING &name1, sp_variable *spv; if (spcont && (spv= spcont->find_variable(name1, false))) { - if (spv->field_def.is_table_rowtype_ref()) + if (spv->field_def.is_table_rowtype_ref() || + spv->field_def.is_cursor_rowtype_ref()) return sphead->set_local_variable_row_field_by_name(thd, spcont, spv, name2, item, this); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 8edebaf6028..3cdf05377dd 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2936,6 +2936,12 @@ public: delete_dynamic(&plugins); } + virtual class Query_arena *query_arena() + { + DBUG_ASSERT(0); + return NULL; + } + void start(THD *thd); const char *substatement_query(THD *thd) const; @@ -3155,7 +3161,8 @@ public: bool sp_handler_declaration_init(THD *thd, int type); bool sp_handler_declaration_finalize(THD *thd, int type); - bool sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt, + bool sp_declare_cursor(THD *thd, const LEX_STRING name, + class sp_lex_cursor *cursor_stmt, sp_pcontext *param_ctx); bool sp_open_cursor(THD *thd, const LEX_STRING name, List<sp_assignment_lex> *parameters); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d3f2a674664..5ca19e34bac 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -377,7 +377,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result, res|= thd->is_error(); if (unlikely(res)) result->abort_result_set(); - if (thd->killed == ABORT_QUERY) + if (thd->killed == ABORT_QUERY && !thd->no_errors) { /* If LIMIT ROWS EXAMINED interrupted query execution, issue a warning, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 93fdff57d38..45af801272a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -783,6 +783,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) Item_param *item_param; Key_part_spec *key_part; LEX *lex; + class sp_lex_cursor *sp_cursor_stmt; LEX_STRING *lex_str_ptr; LEX_USER *lex_user; List<Condition_information_item> *cond_info_list; @@ -1710,6 +1711,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); expr_list opt_udf_expr_list udf_expr_list when_list ident_list ident_list_arg opt_expr_list +%type <sp_cursor_stmt> + sp_cursor_stmt_lex + sp_cursor_stmt + %type <var_type> option_type opt_var_type opt_var_ident_type @@ -1871,7 +1876,6 @@ END_OF_INPUT %type <num> sp_decl_idents sp_handler_type sp_hcond_list %type <spcondvalue> sp_cond sp_hcond sqlstate signal_value opt_signal_value %type <spblock> sp_decls sp_decl sp_decl_body -%type <lex> sp_cursor_stmt %type <spname> sp_name %type <spvar> sp_param_name sp_param_name_and_type %type <spvar_mode> sp_opt_inout @@ -3024,22 +3028,28 @@ sp_decl_body: } ; + +sp_cursor_stmt_lex: + { + DBUG_ASSERT(thd->lex->sphead); + if (!($$= new (thd->mem_root) sp_lex_cursor(thd, thd->lex))) + MYSQL_YYABORT; + } + ; + sp_cursor_stmt: + sp_cursor_stmt_lex { - Lex->sphead->reset_lex(thd); + DBUG_ASSERT(thd->free_list == NULL); + Lex->sphead->reset_lex(thd, $1); } select { - LEX *lex= Lex; - - DBUG_ASSERT(lex->sql_command == SQLCOM_SELECT); - - if (lex->result) - my_yyabort_error((ER_SP_BAD_CURSOR_SELECT, MYF(0))); - lex->sp_lex_in_use= TRUE; - $$= lex; - if (lex->sphead->restore_lex(thd)) + DBUG_ASSERT(Lex == $1); + if ($1->stmt_finalize(thd) || + $1->sphead->restore_lex(thd)) MYSQL_YYABORT; + $$= $1; } ; diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index c72fa8ca0e5..2360b735674 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -199,6 +199,7 @@ void ORAerror(THD *thd, const char *s) Key_part_spec *key_part; LEX *lex; sp_assignment_lex *assignment_lex; + class sp_lex_cursor *sp_cursor_stmt; LEX_STRING *lex_str_ptr; LEX_USER *lex_user; List<Condition_information_item> *cond_info_list; @@ -1150,6 +1151,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); ident_list ident_list_arg opt_expr_list decode_when_list +%type <sp_cursor_stmt> + sp_cursor_stmt_lex + sp_cursor_stmt + %type <assignment_lex> assignment_source_lex assignment_source_expr @@ -1333,7 +1338,6 @@ END_OF_INPUT %type <sp_instr_addr> sp_instr_addr %type <sp_cursor_name_and_offset> sp_cursor_name_and_offset %type <num> opt_exception_clause exception_handlers -%type <lex> sp_cursor_stmt remember_lex %type <spname> sp_name %type <spvar> sp_param_name sp_param_name_and_type %type <for_loop> sp_for_loop_index_and_bounds @@ -2609,22 +2613,27 @@ opt_parenthesized_cursor_formal_parameters: ; +sp_cursor_stmt_lex: + { + DBUG_ASSERT(thd->lex->sphead); + if (!($$= new (thd->mem_root) sp_lex_cursor(thd, thd->lex))) + MYSQL_YYABORT; + } + ; + sp_cursor_stmt: + sp_cursor_stmt_lex { - Lex->sphead->reset_lex(thd); + DBUG_ASSERT(thd->free_list == NULL); + Lex->sphead->reset_lex(thd, $1); } select { - LEX *lex= Lex; - - DBUG_ASSERT(lex->sql_command == SQLCOM_SELECT); - - if (lex->result) - my_yyabort_error((ER_SP_BAD_CURSOR_SELECT, MYF(0))); - lex->sp_lex_in_use= TRUE; - $$= lex; - if (lex->sphead->restore_lex(thd)) + DBUG_ASSERT(Lex == $1); + if ($1->stmt_finalize(thd) || + $1->sphead->restore_lex(thd)) MYSQL_YYABORT; + $$= $1; } ; @@ -3303,12 +3312,6 @@ sp_proc_stmt_goto: ; -remember_lex: - { - $$= thd->lex; - } - ; - assignment_source_lex: { DBUG_ASSERT(Lex->sphead); diff --git a/sql/table.cc b/sql/table.cc index 38cda5cc209..ee592f120d3 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -8213,3 +8213,25 @@ Field *TABLE::find_field_by_name(const char *str) const } return NULL; } + + +bool TABLE::export_structure(THD *thd, Row_definition_list *defs) +{ + for (Field **src= field; *src; src++) + { + uint offs; + if (defs->find_row_field_by_name(src[0]->field_name, &offs)) + { + my_error(ER_DUP_FIELDNAME, MYF(0), src[0]->field_name); + return true; + } + Spvar_definition *def= new (thd->mem_root) Spvar_definition(thd, *src); + if (!def) + return true; + def->flags&= (uint) ~NOT_NULL_FLAG; + if ((def->sp_prepare_create_field(thd, thd->mem_root)) || + (defs->push_back(def, thd->mem_root))) + return true; + } + return false; +} diff --git a/sql/table.h b/sql/table.h index 22dfeaaa596..4885196a817 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1441,6 +1441,7 @@ public: TMP_TABLE_PARAM *tmp_table_param, bool with_cleanup); Field *find_field_by_name(const char *str) const; + bool export_structure(THD *thd, class Row_definition_list *defs); }; |