diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 95b013a0c90..a32e1673ce3 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -157,6 +157,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/json_table.cc ../sql/opt_histogram_json.cc ../sql/sp_instr.cc + ../sql/sp_cursor.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/mysql-test/include/bool_to_char.inc b/mysql-test/include/bool_to_char.inc new file mode 100644 index 00000000000..7bebf8e6804 --- /dev/null +++ b/mysql-test/include/bool_to_char.inc @@ -0,0 +1,18 @@ +SET @sql_mode=@@sql_mode; + +SET sql_mode=ORACLE; + +DELIMITER /; +CREATE FUNCTION bool_to_char(b BOOLEAN) RETURN VARCHAR AS +BEGIN + RETURN + CASE + WHEN b IS NULL THEN 'NULL' + WHEN b THEN 'true' + WHEN NOT b THEN 'false' + END; +END; +/ +DELIMITER ;/ + +SET sql_mode=@sql_mode; diff --git a/mysql-test/include/dbms_output.inc b/mysql-test/include/dbms_output.inc new file mode 100644 index 00000000000..3d2772d1975 --- /dev/null +++ b/mysql-test/include/dbms_output.inc @@ -0,0 +1,22 @@ +SET @sql_mode=@@sql_mode; + +SET sql_mode=ORACLE; + +DELIMITER /; + +CREATE PACKAGE DBMS_OUTPUT AS + PROCEDURE PUT_LINE(s VARCHAR); +END; +/ + +CREATE PACKAGE BODY DBMS_OUTPUT AS + PROCEDURE PUT_LINE(s VARCHAR) AS + BEGIN + SELECT s AS `` FROM DUAL; + END; +END; +/ + +DELIMITER ;/ + +SET sql_mode=@sql_mode; diff --git a/mysql-test/include/fetch_one_value.inc b/mysql-test/include/fetch_one_value.inc new file mode 100644 index 00000000000..218bd66a899 --- /dev/null +++ b/mysql-test/include/fetch_one_value.inc @@ -0,0 +1,17 @@ +# Fetch one value from an open SYS_REFCURSOR + +SET @sql_mode=@@sql_mode; + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE FUNCTION fetch_one_value(c SYS_REFCURSOR) RETURN VARCHAR AS + v VARCHAR(128) :='none'; +BEGIN + IF c%ISOPEN THEN + FETCH c INTO v; + END IF; + RETURN v; +END; +/ +DELIMITER ;/ +SET sql_mode=@sql_mode; diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 7ba6f6fbb7e..19c4532f5db 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -658,6 +658,8 @@ The following specify which files/extra groups are read (specified before remain max_join_size records return an error --max-length-for-sort-data=# Max number of bytes in sorted records + --max-open-cursors=# + The maximum number of open cursors allowed per session --max-password-errors=# If there is more than this number of failed connect attempts due to invalid password, user will be blocked @@ -1817,6 +1819,7 @@ max-error-count 64 max-heap-table-size 16777216 max-join-size 18446744073709551615 max-length-for-sort-data 1024 +max-open-cursors 50 max-password-errors 18446744073709551615 max-prepared-stmt-count 16382 max-recursive-iterations 1000 @@ -1932,7 +1935,7 @@ performance-schema-max-socket-classes 10 performance-schema-max-socket-instances -1 performance-schema-max-sql-text-length 1024 performance-schema-max-stage-classes 160 -performance-schema-max-statement-classes 223 +performance-schema-max-statement-classes 227 performance-schema-max-statement-stack 10 performance-schema-max-table-handles -1 performance-schema-max-table-instances -1 diff --git a/mysql-test/main/sp-sys_refcursor.result b/mysql-test/main/sp-sys_refcursor.result new file mode 100644 index 00000000000..8c831e371ce --- /dev/null +++ b/mysql-test/main/sp-sys_refcursor.result @@ -0,0 +1,366 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# p1() does not cause "Too many open cursors" +# as on every iteration it closes an existng cursor and reopens it. +# +SET @@max_open_cursors=3; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT 1 AS a FROM DUAL; +RETURN c; +END; +$$ +CREATE PROCEDURE p1(count INT) +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE va INT; +FOR i IN 1..count +DO +SET c=f1(); +FETCH c INTO va; +END FOR; +END; +$$ +CALL p1(30); +DROP PROCEDURE p1; +DROP FUNCTION f1; +SET @@max_open_cursors=DEFAULT; +# +# Error: too many open cursors +# +SET @@max_open_cursors=3; +SET @@max_sp_recursion_depth=50; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT 1 AS a FROM DUAL; +RETURN c; +END; +$$ +CREATE PROCEDURE p1(count INT) +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE va INT; +IF count > 0 THEN +SET c=f1(); +CALL p1(count-1); +END IF; +END; +$$ +CALL p1(3); +CALL p1(4); +ERROR HY000: Too many open cursors; max 3 cursors allowed +CALL p1(3); +CALL p1(4); +ERROR HY000: Too many open cursors; max 3 cursors allowed +DROP FUNCTION f1; +DROP PROCEDURE p1; +SET max_open_cursors=DEFAULT; +SET @@max_sp_recursion_depth=DEFAULT; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4); +CREATE PROCEDURE p1() +BEGIN +DECLARE va INT; +DECLARE stage TEXT DEFAULT ''; +DECLARE c1, c2, c3, c4 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING +BEGIN +GET DIAGNOSTICS CONDITION 1 @msg= MESSAGE_TEXT; +SELECT @@max_open_cursors, stage, @msg; +END; +SET max_open_cursors=3; +SET stage='OPEN1 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok +SET stage='OPEN1 c2'; OPEN c2 FOR SELECT a FROM t1; -- Ok +SET stage='OPEN1 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok +SET stage='OPEN1 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: too many open cursors +SET max_open_cursors= 1; +-- Cursors beyond the limit are still available for FETCH +SET stage='FETCH1 c1'; FETCH c1 INTO va; SELECT 'c1', va; -- Ok +SET stage='FETCH1 c2'; FETCH c2 INTO va; SELECT 'c2', va; -- Ok +SET stage='FETCH1 c3'; FETCH c3 INTO va; SELECT 'c3', va; -- Ok +SET stage='FETCH1 c4'; FETCH c4 INTO va; -- Error: not open +-- Open cursors beyond the limit are still available for reopen +-- Reasoning: CLOSE+OPEN do not increase the total amount of open cursors +SET stage='REOPEN1 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok +SET stage='REOPEN1 c2'; OPEN c2 FOR SELECT a FROM t1; -- Ok +SET stage='REOPEN1 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok +SET stage='REOPEN1 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: too many open cursors +-- Cursors beyond the limit are still available for FETCH after reopen +SET stage='FETCH2 c1'; FETCH c1 INTO va; SELECT 'c1', va; -- Ok +SET stage='FETCH2 c2'; FETCH c2 INTO va; SELECT 'c2', va; -- Ok +SET stage='FETCH2 c3'; FETCH c3 INTO va; SELECT 'c3', va; -- Ok +SET stage='FETCH2 c4'; FETCH c4 INTO va; -- Error: not open +-- Open cursors beyond the limit are available for CLOSE +SET stage='CLOSE1 c1'; CLOSE c1; -- Ok +SET stage='CLOSE1 c2'; CLOSE c2; -- Ok +SET stage='CLOSE1 c3'; CLOSE c3; -- Ok +SET stage='CLOSE1 c4'; CLOSE c4; -- Error: not open +-- Closed cursors beyond the limit are not available for a new OPEN +SET stage='OPEN2 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok: fits the limit +SET stage='OPEN2 c2'; OPEN c2 FOR SELECT a FROM t1; -- Error: beyond the limit +SET stage='OPEN2 c3'; OPEN c3 FOR SELECT a FROM t1; -- Error: beyond the limit +SET stage='OPEN2 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: beyond the limit +-- c1 is open. Close it, so we get all cursors c1..c4 closed. +SET stage= 'CLOSE2 c1'; CLOSE c1; -- Ok +-- All cursors are closed. Now open c3. +SET stage= 'OPEN3 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok +SET stage= 'FETCH3 c3'; FETCH c3 INTO va; -- Ok +SET stage= 'CLOSE3 c3'; CLOSE c3; -- Ok +END; +$$ +CALL p1; +@@max_open_cursors stage @msg +3 OPEN1 c4 Too many open cursors; max 3 cursors allowed +c1 va +c1 1 +c2 va +c2 1 +c3 va +c3 1 +@@max_open_cursors stage @msg +1 FETCH1 c4 Cursor is not open +@@max_open_cursors stage @msg +1 REOPEN1 c4 Too many open cursors; max 1 cursors allowed +c1 va +c1 1 +c2 va +c2 1 +c3 va +c3 1 +@@max_open_cursors stage @msg +1 FETCH2 c4 Cursor is not open +@@max_open_cursors stage @msg +1 CLOSE1 c4 Cursor is not open +@@max_open_cursors stage @msg +1 OPEN2 c2 Too many open cursors; max 1 cursors allowed +@@max_open_cursors stage @msg +1 OPEN2 c3 Too many open cursors; max 1 cursors allowed +@@max_open_cursors stage @msg +1 OPEN2 c4 Too many open cursors; max 1 cursors allowed +SET max_open_cursors=DEFAULT; +DROP TABLE t1; +# +# Two consequent OPEN (without a CLOSE in beetween) are allowed +# +BEGIN NOT ATOMIC +DECLARE a INT; +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT 1; +OPEN c FOR SELECT 2; +FETCH c INTO a; +SELECT a; +END; +$$ +a +2 +# +# Many consequent OPEN (without a CLOSE in between) are allowed +# and do not cause ER_TOO_MANY_OPEN_CURSORS. +# +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +FOR i IN 0..300 +DO +OPEN c FOR SELECT 1 AS c FROM DUAL; +END FOR; +END; +$$ +# +# Simple use example (OPEN, FETCH, CLOSE) +# +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE a INT; +OPEN c FOR SELECT 1; +FETCH c INTO a; +CLOSE c; +END; +$$ +# +# Fetching from two parallel cursors +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +CREATE OR REPLACE PROCEDURE p1() +BEGIN +DECLARE a0 INT; +DECLARE a1 INT; +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT a*10 FROM t1; +OPEN c1 FOR SELECT a*20 FROM t1; +FETCH c0 INTO a0; +FETCH c1 INTO a1; +SELECT a0, a1; +CLOSE c0; +CLOSE c1; +END; +$$ +CALL p1; +a0 a1 +10 20 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# SYS_REFCURSOR alasing +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +DECLARE a INT; +OPEN c0 FOR SELECT 11 FROM DUAL UNION SELECT 12 FROM DUAL; +SET c1= c0; +FETCH c0 INTO a; +SELECT a; +OPEN c0 FOR SELECT 21 FROM DUAL UNION SELECT 22 FROM DUAL; +FETCH c1 INTO a; /* c1 now points to the new "OPEN c0" */ +SELECT a; +END; +$$ +a +11 +a +21 +# +# Function returning SYS_REFCURSOR and mysql.proc +# +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +RETURN c0; +END; +$$ +SELECT returns FROM mysql.proc WHERE name='f1'; +returns +sys_refcursor +SHOW CREATE FUNCTION f1; +Function sql_mode Create Function character_set_client collation_connection Database Collation +f1 STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` FUNCTION `f1`() RETURNS sys_refcursor +BEGIN +DECLARE c0 SYS_REFCURSOR; +RETURN c0; +END latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci +DROP FUNCTION f1; +# +# Procedure with a SYS_REFCURSOR parameter and mysql.proc +# +CREATE PROCEDURE p1(OUT a0 SYS_REFCURSOR) +BEGIN +DECLARE c0 SYS_REFCURSOR; +SET a0= c0; +END; +$$ +SELECT param_list FROM mysql.proc WHERE name='p1'; +param_list +OUT a0 SYS_REFCURSOR +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation +p1 STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`(OUT a0 SYS_REFCURSOR) +BEGIN +DECLARE c0 SYS_REFCURSOR; +SET a0= c0; +END latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci +DROP PROCEDURE p1; +# +# Returning a open cursor from a function +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT a FROM t1 ORDER BY a; +RETURN c; +END; +$$ +CREATE PROCEDURE p1() +BEGIN +DECLARE done INT DEFAULT FALSE; +DECLARE a INT; +DECLARE c SYS_REFCURSOR DEFAULT f1(); +DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; +fetch_loop: +LOOP +FETCH c INTO a; +IF done THEN +LEAVE fetch_loop; +END IF; +SELECT a; +END LOOP; +CLOSE c; +END; +$$ +CALL p1; +a +10 +a +20 +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP TABLE t1; +# +# Returning an open cursor as an OUT param +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN +OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +$$ +CREATE PROCEDURE p2() +BEGIN +DECLARE done INT DEFAULT FALSE; +DECLARE a INT; +DECLARE c SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; +CALL p1(c); +fetch_loop: +LOOP +FETCH c INTO a; +IF done THEN +LEAVE fetch_loop; +END IF; +SELECT a; +END LOOP; +CLOSE c; +END; +$$ +CALL p2; +a +10 +a +20 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +# +# A prepared statement calls its own thd->cleanup_after_query() +# Make sure it does not close SYS_REFCURSORs, +# and does not assert that all static cursors are closed. +# +CREATE PROCEDURE p1() +BEGIN +DECLARE v0, v1 VARCHAR(64); +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 CURSOR FOR SELECT 'c1val'; +OPEN c0 FOR SELECT 'c0val'; +OPEN c1; +EXECUTE IMMEDIATE 'SELECT "ps1val"'; -- PS calls thd->cleanup_after_query() +FETCH c1 INTO v1; -- this still works, no asserts happened. +FETCH c0 INTO v0; -- this still works, c0 is still open. +SELECT v0, v1; +CLOSE c1; +CLOSE c0; +END +$$ +CALL p1; +ps1val +ps1val +v0 v1 +c0val c1val +DROP PROCEDURE p1; diff --git a/mysql-test/main/sp-sys_refcursor.test b/mysql-test/main/sp-sys_refcursor.test new file mode 100644 index 00000000000..e7adcb18cec --- /dev/null +++ b/mysql-test/main/sp-sys_refcursor.test @@ -0,0 +1,387 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + + +--echo # +--echo # p1() does not cause "Too many open cursors" +--echo # as on every iteration it closes an existng cursor and reopens it. +--echo # + +SET @@max_open_cursors=3; + +DELIMITER $$; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1 AS a FROM DUAL; + RETURN c; +END; +$$ + +CREATE PROCEDURE p1(count INT) +BEGIN + DECLARE c SYS_REFCURSOR; + DECLARE va INT; + FOR i IN 1..count + DO + SET c=f1(); + FETCH c INTO va; + END FOR; +END; +$$ +DELIMITER ;$$ +CALL p1(30); +DROP PROCEDURE p1; +DROP FUNCTION f1; +SET @@max_open_cursors=DEFAULT; + + +--echo # +--echo # Error: too many open cursors +--echo # + +SET @@max_open_cursors=3; +SET @@max_sp_recursion_depth=50; + +DELIMITER $$; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1 AS a FROM DUAL; + RETURN c; +END; +$$ + +CREATE PROCEDURE p1(count INT) +BEGIN + DECLARE c SYS_REFCURSOR; + DECLARE va INT; + IF count > 0 THEN + SET c=f1(); + CALL p1(count-1); + END IF; +END; +$$ +DELIMITER ;$$ + +CALL p1(3); +--error ER_TOO_MANY_OPEN_CURSORS +CALL p1(4); +CALL p1(3); +--error ER_TOO_MANY_OPEN_CURSORS +CALL p1(4); + +DROP FUNCTION f1; +DROP PROCEDURE p1; +SET max_open_cursors=DEFAULT; +SET @@max_sp_recursion_depth=DEFAULT; + + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4); + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE va INT; + DECLARE stage TEXT DEFAULT ''; + DECLARE c1, c2, c3, c4 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING + BEGIN + GET DIAGNOSTICS CONDITION 1 @msg= MESSAGE_TEXT; + SELECT @@max_open_cursors, stage, @msg; + END; + + SET max_open_cursors=3; + SET stage='OPEN1 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok + SET stage='OPEN1 c2'; OPEN c2 FOR SELECT a FROM t1; -- Ok + SET stage='OPEN1 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok + SET stage='OPEN1 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: too many open cursors + + SET max_open_cursors= 1; + -- Cursors beyond the limit are still available for FETCH + SET stage='FETCH1 c1'; FETCH c1 INTO va; SELECT 'c1', va; -- Ok + SET stage='FETCH1 c2'; FETCH c2 INTO va; SELECT 'c2', va; -- Ok + SET stage='FETCH1 c3'; FETCH c3 INTO va; SELECT 'c3', va; -- Ok + SET stage='FETCH1 c4'; FETCH c4 INTO va; -- Error: not open + + -- Open cursors beyond the limit are still available for reopen + -- Reasoning: CLOSE+OPEN do not increase the total amount of open cursors + SET stage='REOPEN1 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok + SET stage='REOPEN1 c2'; OPEN c2 FOR SELECT a FROM t1; -- Ok + SET stage='REOPEN1 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok + SET stage='REOPEN1 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: too many open cursors + + -- Cursors beyond the limit are still available for FETCH after reopen + SET stage='FETCH2 c1'; FETCH c1 INTO va; SELECT 'c1', va; -- Ok + SET stage='FETCH2 c2'; FETCH c2 INTO va; SELECT 'c2', va; -- Ok + SET stage='FETCH2 c3'; FETCH c3 INTO va; SELECT 'c3', va; -- Ok + SET stage='FETCH2 c4'; FETCH c4 INTO va; -- Error: not open + + -- Open cursors beyond the limit are available for CLOSE + SET stage='CLOSE1 c1'; CLOSE c1; -- Ok + SET stage='CLOSE1 c2'; CLOSE c2; -- Ok + SET stage='CLOSE1 c3'; CLOSE c3; -- Ok + SET stage='CLOSE1 c4'; CLOSE c4; -- Error: not open + + -- Closed cursors beyond the limit are not available for a new OPEN + SET stage='OPEN2 c1'; OPEN c1 FOR SELECT a FROM t1; -- Ok: fits the limit + SET stage='OPEN2 c2'; OPEN c2 FOR SELECT a FROM t1; -- Error: beyond the limit + SET stage='OPEN2 c3'; OPEN c3 FOR SELECT a FROM t1; -- Error: beyond the limit + SET stage='OPEN2 c4'; OPEN c4 FOR SELECT a FROM t1; -- Error: beyond the limit + + -- c1 is open. Close it, so we get all cursors c1..c4 closed. + SET stage= 'CLOSE2 c1'; CLOSE c1; -- Ok + + -- All cursors are closed. Now open c3. + SET stage= 'OPEN3 c3'; OPEN c3 FOR SELECT a FROM t1; -- Ok + SET stage= 'FETCH3 c3'; FETCH c3 INTO va; -- Ok + SET stage= 'CLOSE3 c3'; CLOSE c3; -- Ok +END; +$$ +DELIMITER ;$$ +CALL p1; +SET max_open_cursors=DEFAULT; +DROP TABLE t1; + +--echo # +--echo # Two consequent OPEN (without a CLOSE in beetween) are allowed +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE a INT; + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1; + OPEN c FOR SELECT 2; + FETCH c INTO a; + SELECT a; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Many consequent OPEN (without a CLOSE in between) are allowed +--echo # and do not cause ER_TOO_MANY_OPEN_CURSORS. +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + FOR i IN 0..300 + DO + OPEN c FOR SELECT 1 AS c FROM DUAL; + END FOR; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Simple use example (OPEN, FETCH, CLOSE) +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE a INT; + OPEN c FOR SELECT 1; + FETCH c INTO a; + CLOSE c; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Fetching from two parallel cursors +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +DELIMITER $$; +CREATE OR REPLACE PROCEDURE p1() +BEGIN + DECLARE a0 INT; + DECLARE a1 INT; + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT a*10 FROM t1; + OPEN c1 FOR SELECT a*20 FROM t1; + FETCH c0 INTO a0; + FETCH c1 INTO a1; + SELECT a0, a1; + CLOSE c0; + CLOSE c1; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # SYS_REFCURSOR alasing +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + DECLARE a INT; + OPEN c0 FOR SELECT 11 FROM DUAL UNION SELECT 12 FROM DUAL; + SET c1= c0; + FETCH c0 INTO a; + SELECT a; + OPEN c0 FOR SELECT 21 FROM DUAL UNION SELECT 22 FROM DUAL; + FETCH c1 INTO a; /* c1 now points to the new "OPEN c0" */ + SELECT a; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Function returning SYS_REFCURSOR and mysql.proc +--echo # + +DELIMITER $$; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + RETURN c0; +END; +$$ +DELIMITER ;$$ +SELECT returns FROM mysql.proc WHERE name='f1'; +SHOW CREATE FUNCTION f1; +DROP FUNCTION f1; + + +--echo # +--echo # Procedure with a SYS_REFCURSOR parameter and mysql.proc +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1(OUT a0 SYS_REFCURSOR) +BEGIN + DECLARE c0 SYS_REFCURSOR; + SET a0= c0; +END; +$$ +DELIMITER ;$$ +SELECT param_list FROM mysql.proc WHERE name='p1'; +SHOW CREATE PROCEDURE p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Returning a open cursor from a function +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT a FROM t1 ORDER BY a; + RETURN c; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE done INT DEFAULT FALSE; + DECLARE a INT; + DECLARE c SYS_REFCURSOR DEFAULT f1(); + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; +fetch_loop: + LOOP + FETCH c INTO a; + IF done THEN + LEAVE fetch_loop; + END IF; + SELECT a; + END LOOP; + CLOSE c; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP TABLE t1; + + +--echo # +--echo # Returning an open cursor as an OUT param +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN + OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +CREATE PROCEDURE p2() +BEGIN + DECLARE done INT DEFAULT FALSE; + DECLARE a INT; + DECLARE c SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + CALL p1(c); +fetch_loop: + LOOP + FETCH c INTO a; + IF done THEN + LEAVE fetch_loop; + END IF; + SELECT a; + END LOOP; + CLOSE c; +END; +$$ +DELIMITER ;$$ +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; + +--echo # +--echo # A prepared statement calls its own thd->cleanup_after_query() +--echo # Make sure it does not close SYS_REFCURSORs, +--echo # and does not assert that all static cursors are closed. +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE v0, v1 VARCHAR(64); + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 CURSOR FOR SELECT 'c1val'; + OPEN c0 FOR SELECT 'c0val'; + OPEN c1; + EXECUTE IMMEDIATE 'SELECT "ps1val"'; -- PS calls thd->cleanup_after_query() + FETCH c1 INTO v1; -- this still works, no asserts happened. + FETCH c0 INTO v0; -- this still works, c0 is still open. + SELECT v0, v1; + CLOSE c1; + CLOSE c0; +END +$$ +DELIMITER ;$$ + +CALL p1; +DROP PROCEDURE p1; diff --git a/mysql-test/main/sp_validation.result b/mysql-test/main/sp_validation.result index f76dfeb5e98..484a63dc819 100644 --- a/mysql-test/main/sp_validation.result +++ b/mysql-test/main/sp_validation.result @@ -1234,7 +1234,7 @@ ALTER TABLE t1 ADD COLUMN b INT DEFAULT 1; CALL p1(3); x 3 -ERROR 21000: Operand should contain 1 column(s) +ERROR HY000: Illegal parameter data type row for operation 'not' ALTER TABLE t1 DROP COLUMN a; CALL p1(3); x diff --git a/mysql-test/main/sp_validation.test b/mysql-test/main/sp_validation.test index bf1f147deec..8d448b5f8f0 100644 --- a/mysql-test/main/sp_validation.test +++ b/mysql-test/main/sp_validation.test @@ -1768,7 +1768,7 @@ UPDATE t1 SET a = 1; ALTER TABLE t1 ADD COLUMN b INT DEFAULT 1; ---error ER_OPERAND_COLUMNS +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION CALL p1(3); ALTER TABLE t1 DROP COLUMN a; diff --git a/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-alias.result b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-alias.result new file mode 100644 index 00000000000..d095aaa2d6b --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-alias.result @@ -0,0 +1,225 @@ +SET sql_mode=ORACLE; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Aliasing +# +CREATE PROCEDURE p1(task VARCHAR) AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +v0 INT; +v1 INT; +BEGIN +OPEN c0 FOR SELECT 100 FROM DUAL UNION SELECT 101 FROM DUAL; +c1:= c0; +FETCH c0 INTO v0; -- fetch 100 +FETCH c1 INTO v1; -- fetch 101 +DBMS_OUTPUT.PUT_LINE('actual=' || v0 || ' ' || v1 || ' ' || 'expected=100 101'); +IF task LIKE '%close_c0%' THEN +CLOSE c0; +END IF; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || +'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +OPEN c1 FOR SELECT 200 FROM DUAL UNION SELECT 201 FROM DUAL; +FETCH c0 INTO v0; -- fetch 200 from the new OPEN c1 +FETCH c1 INTO v1; -- fetch 201 from the new OPEN c1 +DBMS_OUTPUT.PUT_LINE('actual=' || v0 || ' ' || v1 || ' ' || 'expected=200 201'); +IF task LIKE '%close_c1%' THEN +CLOSE c1; +END IF; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || +'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +END; +/ +CALL p1(''); + +actual=100 101 expected=100 101 + +c0%ISOPEN=true c1%ISOPEN=true + +actual=200 201 expected=200 201 + +c0%ISOPEN=true c1%ISOPEN=true +CALL p1('close_c0'); + +actual=100 101 expected=100 101 + +c0%ISOPEN=false c1%ISOPEN=false + +actual=200 201 expected=200 201 + +c0%ISOPEN=true c1%ISOPEN=true +CALL p1('close_c1'); + +actual=100 101 expected=100 101 + +c0%ISOPEN=true c1%ISOPEN=true + +actual=200 201 expected=200 201 + +c0%ISOPEN=false c1%ISOPEN=false +CALL p1('close_c0 close_c1'); + +actual=100 101 expected=100 101 + +c0%ISOPEN=false c1%ISOPEN=false + +actual=200 201 expected=200 201 + +c0%ISOPEN=false c1%ISOPEN=false +DROP PROCEDURE p1; +CREATE PROCEDURE p1(task VARCHAR) AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +v INT; +BEGIN +OPEN c0 FOR SELECT 1 FROM DUAL; +CLOSE c0; +CASE task +WHEN 'c0:=c1' THEN c0:=c1; -- Expect: Cursor is not open +WHEN 'c1:=c0' THEN c1:=c0; -- Expect: v is set to 2 +ELSE NULL; -- Expect: Cursor is not open +END CASE; +OPEN c1 FOR SELECT 2 FROM DUAL; +FETCH c0 INTO v; +CLOSE c1; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1(''); +ERROR 24000: Cursor is not open +CALL p1('c0:=c1'); +ERROR 24000: Cursor is not open +CALL p1('c1:=c0'); + +v=2 +DROP PROCEDURE p1; +# +# Aliasing: different variable scope +# +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +v INT; +BEGIN +DECLARE +c1 SYS_REFCURSOR; +BEGIN +OPEN c1 FOR SELECT 1 AS c FROM DUAL; +c0:= c1; +END; +-- Although c1 who opened the cursor goes out of the scope here, +-- the alias still works: +FETCH c0 INTO v; +DBMS_OUTPUT.PUT_LINE('v='||v); +END; +/ +CALL p1; + +v=1 +DROP PROCEDURE p1; +# +# A comprex script with many OPEN, FETCH, CLOSE statements +# +CREATE OR REPLACE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +v0 VARCHAR(32); +v1 VARCHAR(32); +BEGIN +DBMS_OUTPUT.PUT_LINE('test1'); +OPEN c0 FOR SELECT '10' FROM DUAL UNION SELECT '11' FROM DUAL; +c1:= c0; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +DBMS_OUTPUT.PUT_LINE('test1a'); +CLOSE c1; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +BEGIN +FETCH c0 INTO v0; +EXCEPTION +WHEN OTHERS THEN v0:=''; +END; +BEGIN +FETCH c1 INTO v1; +EXCEPTION +WHEN OTHERS THEN v1:=''; +END; +DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); +DBMS_OUTPUT.PUT_LINE('test2:'); +OPEN c1 FOR SELECT '20' FROM DUAL UNION SELECT '21' FROM DUAL UNION SELECT '22' FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +BEGIN +FETCH c0 INTO v0; +EXCEPTION +WHEN OTHERS THEN v0:=''; +END; +BEGIN +FETCH c1 INTO v1; +EXCEPTION +WHEN OTHERS THEN v1:=''; +END; +DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); +DBMS_OUTPUT.PUT_LINE('test2a'); +CLOSE c1; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' ||bool_to_char(c1%ISOPEN)); +BEGIN +FETCH c0 INTO v0; +EXCEPTION +WHEN OTHERS THEN v0:=''; +END; +BEGIN +FETCH c1 INTO v1; +EXCEPTION +WHEN OTHERS THEN v1:=''; +END; +DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); +DBMS_OUTPUT.PUT_LINE('test3'); +OPEN c0 FOR SELECT '30' FROM DUAL UNION SELECT '31' FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' ||bool_to_char(c1%ISOPEN)); +FETCH c0 INTO v0; +FETCH c1 INTO v1; +DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); +DBMS_OUTPUT.PUT_LINE('test4'); +OPEN c0 FOR SELECT 'c0-40' FROM DUAL UNION SELECT 'c0-41' FROM DUAL; +OPEN c1 FOR SELECT 'c1-40' FROM DUAL UNION SELECT 'c1-41' FROM DUAL; +FETCH c0 INTO v0; +FETCH c1 INTO v1; +DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); +END; +/ +CALL p1; + +test1 + +c0%ISOPEN=true c1%ISOPEN=true + +test1a + +c0%ISOPEN=false c1%ISOPEN=false + +v0= v1= + +test2: + +c0%ISOPEN=true c1%ISOPEN=true + +v0=20 v1=21 + +test2a + +c0%ISOPEN=false c1%ISOPEN=false + +v0= v1= + +test3 + +c0%ISOPEN=true c1%ISOPEN=true + +v0=30 v1=31 + +test4 + +v0=c1-40 v1=c1-41 +DROP PROCEDURE p1; +DROP PACKAGE DBMS_OUTPUT; +DROP FUNCTION bool_to_char; diff --git a/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-func_hybrid.result b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-func_hybrid.result new file mode 100644 index 00000000000..ed8e4a158ab --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor-func_hybrid.result @@ -0,0 +1,111 @@ +SET sql_mode=ORACLE; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Hybrid functions +# +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +v INT; +BEGIN +OPEN c1 FOR SELECT 10 FROM DUAL; +c2:= CASE WHEN c0 IS NULL THEN c1 ELSE c0 END; +FETCH c2 INTO v; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1; + +v=10 +DROP PROCEDURE p1; +CREATE PROCEDURE p1(switch INT) AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +c3 SYS_REFCURSOR; +c4 SYS_REFCURSOR; +cx SYS_REFCURSOR; +v INT; +BEGIN +OPEN c0 FOR SELECT 10 FROM DUAL; +OPEN c1 FOR SELECT 11 FROM DUAL; +OPEN c2 FOR SELECT 12 FROM DUAL; +OPEN c3 FOR SELECT 13 FROM DUAL; +OPEN c4 FOR SELECT 14 FROM DUAL; +cx:= DECODE(switch, 0, c0, 1, c1, 2, c2, 3, c3, c4); +FETCH cx INTO v; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1(0); + +v=10 +CALL p1(1); + +v=11 +CALL p1(2); + +v=12 +CALL p1(3); + +v=13 +CALL p1(4); + +v=14 +CALL p1(5); + +v=14 +DROP PROCEDURE p1; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +v INT; +BEGIN +OPEN c1 FOR SELECT 10 FROM DUAL; +c2:= COALESCE(c0,c1); +FETCH c2 INTO v; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1; + +v=10 +DROP PROCEDURE p1; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +v INT; +BEGIN +OPEN c1 FOR SELECT 10 FROM DUAL; +c2:= IF(false, c0,c1); +FETCH c2 INTO v; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1; + +v=10 +DROP PROCEDURE p1; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +v INT; +BEGIN +OPEN c1 FOR SELECT 10 FROM DUAL; +c2:= GREATEST(c0,c1); +FETCH c2 INTO v; +DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +CALL p1; +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'greatest' +DROP PROCEDURE p1; +DROP PACKAGE dbms_output; +DROP FUNCTION bool_to_char; +SET sql_mode=DEFAULT; diff --git a/mysql-test/suite/compat/oracle/r/sp-sys_refcursor.result b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor.result new file mode 100644 index 00000000000..9f629984cf5 --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-sys_refcursor.result @@ -0,0 +1,643 @@ +SET sql_mode=ORACLE; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Error: Unknown cursor and wrong variable data type in OPEN, FETCH, CLOSE +# +BEGIN +OPEN c FOR SELECT 1 AS c FROM DUAL; +END; +/ +ERROR 42000: Undeclared variable: c +DECLARE +c INT; +BEGIN +OPEN c FOR SELECT 1 AS c FROM DUAL; +END; +/ +ERROR HY000: Illegal parameter data type int for operation 'OPEN' +DECLARE +c INT; +BEGIN +CLOSE c; +END; +/ +ERROR HY000: Illegal parameter data type int for operation 'CLOSE' +DECLARE +a INT; +c INT; +BEGIN +FETCH c INTO a; +END; +/ +ERROR HY000: Illegal parameter data type int for operation 'FETCH' +# +# Error: Closing a not open cursor +# +DECLARE +c SYS_REFCURSOR; +BEGIN +CLOSE c; +END; +/ +ERROR 24000: Cursor is not open +# +# Error: Fetching from a not open cursor +# +DECLARE +a INT; +c SYS_REFCURSOR; +BEGIN +FETCH c INTO a; +END; +/ +ERROR 24000: Cursor is not open +# +# Error: fetching beyond the available number of records +# sql_mode=ORACLE preserves the variable value +# +DECLARE +a INT; +c SYS_REFCURSOR; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE(a); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE(a); +END; +/ + +1 + +1 +# +# Two consequent OPEN (without a CLOSE in beetween) are allowed +# +DECLARE +a INT; +c SYS_REFCURSOR; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +OPEN c FOR SELECT 2 FROM DUAL; +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE(a); +END; +/ + +2 +# +# Many consequent OPEN (without a CLOSE in between) are allowed +# and do not cause ER_TOO_MANY_OPEN_CURSORS. +# +SET max_open_cursors=2; +DECLARE +c SYS_REFCURSOR; +BEGIN +FOR i IN 1..3 +LOOP +OPEN c FOR SELECT 1 AS c FROM DUAL; +END LOOP; +END; +/ +SET max_open_cursors=DEFAULT; +# +# Simple use example (OPEN, FETCH, CLOSE) +# +DECLARE +c SYS_REFCURSOR; +a INT; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +FETCH c INTO a; +CLOSE c; +DBMS_OUTPUT.PUT_LINE(a); +END; +/ + +1 +# +# Fetching from two parallel cursors +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +CREATE PROCEDURE p1() AS +a0 INT; +a1 INT; +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT a*10 FROM t1; +OPEN c1 FOR SELECT a*20 FROM t1; +FETCH c0 INTO a0; +FETCH c1 INTO a1; +DBMS_OUTPUT.PUT_LINE(a0 || ' ' || a1); +CLOSE c0; +CLOSE c1; +END; +/ +CALL p1; + +10 20 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Returning an open cursor from a function +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +CREATE FUNCTION f1 RETURN SYS_REFCURSOR AS +c SYS_REFCURSOR; +BEGIN +OPEN c FOR SELECT a FROM t1 ORDER BY a; +RETURN c; +END; +/ +CREATE PROCEDURE p1 AS +a INT; +c SYS_REFCURSOR DEFAULT f1(); +BEGIN +LOOP +FETCH c INTO a; +EXIT WHEN c%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +CLOSE c; +END; +/ +CALL p1; + +10 + +20 +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP TABLE t1; +# +# Returning SYS_REFCURSOR from a function: too many open cursors +# +SET max_open_cursors=2; +CREATE OR REPLACE FUNCTION f1 RETURN SYS_REFCURSOR IS +c SYS_REFCURSOR; +BEGIN +OPEN c FOR SELECT 1 AS a FROM DUAL; +RETURN c; +END; +/ +DECLARE +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +a INT; +BEGIN +c0:= f1(); +FETCH c0 INTO a; +c1:= f1(); +FETCH c1 INTO a; +c2:= f1(); +FETCH c2 INTO a; +END; +/ +ERROR HY000: Too many open cursors; max 2 cursors allowed +DROP FUNCTION f1; +SET max_open_cursors=DEFAULT; +# +# Returning an open cursor as an OUT param +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +INSERT INTO t1 VALUES (20); +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN +OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +/ +CREATE PROCEDURE p2 AS +a INT; +c SYS_REFCURSOR; +BEGIN +p1(c); +LOOP +FETCH c INTO a; +EXIT WHEN c%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +CLOSE c; +END; +/ +CALL p2; + +10 + +20 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +# +# Returning an open cursor as an OUT param: Too many open cursors +# +SET @@max_open_cursors=2; +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN +OPEN c FOR VALUES (10),(20); +END; +/ +CREATE PROCEDURE p2 AS +a INT; +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +BEGIN +p1(c0); +LOOP +FETCH c0 INTO a; +EXIT WHEN c0%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +p1(c1); +LOOP +FETCH c1 INTO a; +EXIT WHEN c1%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +p1(c2); +END; +/ +CALL p2; + +10 + +20 + +10 + +20 +ERROR HY000: Too many open cursors; max 2 cursors allowed +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET @@max_open_cursors=DEFAULT; +# +# Returning an open cursor as an OUT param: no "Too many open cursors" +# +SET @@max_open_cursors=2; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +INSERT INTO t1 VALUES (20); +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN +OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +/ +CREATE PROCEDURE p2 AS +a INT; +c SYS_REFCURSOR; +BEGIN +FOR i IN 1..5 +LOOP +p1(c); -- This closes the cursor and reopens it in p1 +LOOP +FETCH c INTO a; +EXIT WHEN c%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +END LOOP; +CLOSE c; +END; +/ +CALL p2; + +10 + +20 + +10 + +20 + +10 + +20 + +10 + +20 + +10 + +20 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +SET @@max_open_cursors=DEFAULT; +# +# Returning an open cursor as an INOUT param: no "Too many open cursors" +# +SET @@max_open_cursors=2; +CREATE PROCEDURE p1(c INOUT SYS_REFCURSOR) AS +BEGIN +OPEN c FOR VALUES (10), (20); +END; +/ +CREATE PROCEDURE p2 AS +a INT; +c SYS_REFCURSOR; +BEGIN +FOR i IN 1..5 +LOOP +p1(c); -- This closes the cursor and reopens it in p1 +LOOP +FETCH c INTO a; +EXIT WHEN c%NOTFOUND; +DBMS_OUTPUT.PUT_LINE(a); +END LOOP; +END LOOP; +CLOSE c; +END; +/ +CALL p2; + +10 + +20 + +10 + +20 + +10 + +20 + +10 + +20 + +10 + +20 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET @@max_open_cursors=DEFAULT; +# +# Function returning SYS_REFCURSOR and mysql.proc +# +CREATE FUNCTION f1() RETURN SYS_REFCURSOR AS +c0 SYS_REFCURSOR; +BEGIN +RETURN c0; +END; +/ +SELECT returns FROM mysql.proc WHERE name='f1'; +returns +sys_refcursor +SHOW CREATE FUNCTION f1; +Function sql_mode Create Function character_set_client collation_connection Database Collation +f1 PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ORACLE,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS,NO_AUTO_CREATE_USER,SIMULTANEOUS_ASSIGNMENT CREATE DEFINER="root"@"localhost" FUNCTION "f1"() RETURN sys_refcursor +AS +c0 SYS_REFCURSOR; +BEGIN +RETURN c0; +END latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci +DROP FUNCTION f1; +# +# Procedure with a SYS_REFCURSOR parameter and mysql.proc +# +CREATE PROCEDURE p1(a0 OUT SYS_REFCURSOR) AS +c0 SYS_REFCURSOR; +BEGIN +a0:= c0; +END; +/ +SELECT param_list FROM mysql.proc WHERE name='p1'; +param_list +a0 OUT SYS_REFCURSOR +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation +p1 PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ORACLE,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS,NO_AUTO_CREATE_USER,SIMULTANEOUS_ASSIGNMENT CREATE DEFINER="root"@"localhost" PROCEDURE "p1"(a0 OUT SYS_REFCURSOR) +AS +c0 SYS_REFCURSOR; +BEGIN +a0:= c0; +END latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci +DROP PROCEDURE p1; +# +# NULL predicate +# +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +v INT; +BEGIN +DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); +OPEN c0 FOR SELECT 1 FROM DUAL; +DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); +FETCH c0 INTO v; +DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); +CLOSE c0; +DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); +END; +/ +CALL p1; + +true + +false + +false + +false +DROP PROCEDURE p1; +# +# Cursor attributes on a not open SYS_REFCURSOR +# +DECLARE +c SYS_REFCURSOR; +BEGIN +DBMS_OUTPUT.PUT_LINE('c%ISOPEN=' || bool_to_char(c%ISOPEN)); +END; +/ + +c%ISOPEN=false +DECLARE +c SYS_REFCURSOR; +BEGIN +DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +END; +/ +ERROR 24000: Cursor is not open +DECLARE +c SYS_REFCURSOR; +BEGIN +DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +END; +/ +ERROR 24000: Cursor is not open +DECLARE +c SYS_REFCURSOR; +BEGIN +DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +END; +/ +ERROR 24000: Cursor is not open +# +# Cursor attributes on an open SYS_REFCURSOR +# +DECLARE +c SYS_REFCURSOR; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c%ISOPEN=' || bool_to_char(c%ISOPEN)); +END; +/ + +c%ISOPEN=true +DECLARE +c SYS_REFCURSOR; +a INT; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +END; +/ + +c%FOUND=NULL + +c%FOUND=true + +c%FOUND=false +DECLARE +c SYS_REFCURSOR; +a INT; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +END; +/ + +c%NOTFOUND=NULL + +c%NOTFOUND=false + +c%NOTFOUND=true +DECLARE +c SYS_REFCURSOR; +a INT; +BEGIN +OPEN c FOR SELECT 1 FROM DUAL; +DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +FETCH c INTO a; +DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +END; +/ + +c%ROWCOUNT=0 + +c%ROWCOUNT=1 + +c%ROWCOUNT=1 +# +# - Returning a never opened cursor does not cause ER_TOO_MANY_OPEN_CURSORS +# - Returning an opened+closed cursor does not cause ER_TOO_MANY_OPEN_CURSORS +# - Only returning an opened cursor causes ER_TOO_MANY_OPEN_CURSORS +# +SET @@max_open_cursors=2; +CREATE FUNCTION f1(task VARCHAR) RETURN SYS_REFCURSOR AS +c SYS_REFCURSOR := NULL; +BEGIN +IF task LIKE '%open%' THEN +OPEN c FOR SELECT 1 FROM DUAL; +END IF; +IF task LIKE '%close%' THEN +CLOSE c; +END IF; +RETURN c; +END; +/ +CREATE PROCEDURE p1(task VARCHAR) AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +BEGIN +c0:= f1(task); +DBMS_OUTPUT.PUT_LINE('0' || ' ' || CASE WHEN c0 IS NULL THEN '' ELSE '' END || +' ' || bool_to_char(c0%ISOPEN)); +c1:= f1(task); +DBMS_OUTPUT.PUT_LINE('1' || ' ' || CASE WHEN c1 IS NULL THEN '' ELSE '' END || +' ' || bool_to_char(c1%ISOPEN)); +c2:= f1(task); +DBMS_OUTPUT.PUT_LINE('2' || ' ' || CASE WHEN c2 IS NULL THEN '' ELSE '' END || +' ' || bool_to_char(c2%ISOPEN)); +END; +/ +CALL p1('none'); + +0 false + +1 false + +2 false +CALL p1('open_close'); + +0 false + +1 false + +2 false +CALL p1('open'); + +0 true + +1 true +ERROR HY000: Too many open cursors; max 2 cursors allowed +DROP PROCEDURE p1; +DROP FUNCTION f1; +SET @@max_open_cursors=DEFAULT; +# +# Cursor variables cannot be declared as part of a package +# +CREATE PACKAGE pkg AS +FUNCTION f1 RETURN INT; +END; +/ +CREATE PACKAGE BODY pkg AS +cur SYS_REFCURSOR; -- This is wrong (the top PACKAGE BODY frame) +FUNCTION f1 RETURN INT AS +BEGIN +RETURN 1; +END; +END; +/ +ERROR HY000: 'sys_refcursor' is not allowed in this context +CREATE PACKAGE BODY pkg AS +vc INT := 0; +FUNCTION f1 RETURN INT AS +cur SYS_REFCURSOR; +BEGIN +RETURN vc; +END; +BEGIN +DECLARE +cur SYS_REFCURSOR; -- This is OK (executable section) +BEGIN +OPEN cur FOR SELECT 1 AS c FROM DUAL; +FETCH cur INTO vc; +CLOSE cur; +END; +END; +/ +SELECT pkg.f1() FROM DUAL; +pkg.f1() +1 +DROP PACKAGE pkg; +DROP FUNCTION bool_to_char; +DROP PACKAGE DBMS_OUTPUT; diff --git a/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-alias.test b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-alias.test new file mode 100644 index 00000000000..99e3167b2cf --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-alias.test @@ -0,0 +1,192 @@ +SET sql_mode=ORACLE; + +# Helper functions and packages +--disable_query_log +--source include/dbms_output.inc +--source include/bool_to_char.inc +--enable_query_log + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + + +--echo # +--echo # Aliasing +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(task VARCHAR) AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + v0 INT; + v1 INT; +BEGIN + OPEN c0 FOR SELECT 100 FROM DUAL UNION SELECT 101 FROM DUAL; + c1:= c0; + FETCH c0 INTO v0; -- fetch 100 + FETCH c1 INTO v1; -- fetch 101 + DBMS_OUTPUT.PUT_LINE('actual=' || v0 || ' ' || v1 || ' ' || 'expected=100 101'); + IF task LIKE '%close_c0%' THEN + CLOSE c0; + END IF; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || + 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); + OPEN c1 FOR SELECT 200 FROM DUAL UNION SELECT 201 FROM DUAL; + FETCH c0 INTO v0; -- fetch 200 from the new OPEN c1 + FETCH c1 INTO v1; -- fetch 201 from the new OPEN c1 + DBMS_OUTPUT.PUT_LINE('actual=' || v0 || ' ' || v1 || ' ' || 'expected=200 201'); + IF task LIKE '%close_c1%' THEN + CLOSE c1; + END IF; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || + 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); +END; +/ +DELIMITER ;/ +CALL p1(''); +CALL p1('close_c0'); +CALL p1('close_c1'); +CALL p1('close_c0 close_c1'); +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1(task VARCHAR) AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + v INT; +BEGIN + OPEN c0 FOR SELECT 1 FROM DUAL; + CLOSE c0; + CASE task + WHEN 'c0:=c1' THEN c0:=c1; -- Expect: Cursor is not open + WHEN 'c1:=c0' THEN c1:=c0; -- Expect: v is set to 2 + ELSE NULL; -- Expect: Cursor is not open + END CASE; + OPEN c1 FOR SELECT 2 FROM DUAL; + FETCH c0 INTO v; + CLOSE c1; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +--error ER_SP_CURSOR_NOT_OPEN +CALL p1(''); +--error ER_SP_CURSOR_NOT_OPEN +CALL p1('c0:=c1'); +CALL p1('c1:=c0'); +DROP PROCEDURE p1; + + +--echo # +--echo # Aliasing: different variable scope +--echo # + +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + v INT; +BEGIN + DECLARE + c1 SYS_REFCURSOR; + BEGIN + OPEN c1 FOR SELECT 1 AS c FROM DUAL; + c0:= c1; + END; + -- Although c1 who opened the cursor goes out of the scope here, + -- the alias still works: + FETCH c0 INTO v; + DBMS_OUTPUT.PUT_LINE('v='||v); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # A comprex script with many OPEN, FETCH, CLOSE statements +--echo # + +DELIMITER /; + +CREATE OR REPLACE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + v0 VARCHAR(32); + v1 VARCHAR(32); +BEGIN + DBMS_OUTPUT.PUT_LINE('test1'); + OPEN c0 FOR SELECT '10' FROM DUAL UNION SELECT '11' FROM DUAL; + c1:= c0; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); + + DBMS_OUTPUT.PUT_LINE('test1a'); + CLOSE c1; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); + BEGIN + FETCH c0 INTO v0; + EXCEPTION + WHEN OTHERS THEN v0:=''; + END; + BEGIN + FETCH c1 INTO v1; + EXCEPTION + WHEN OTHERS THEN v1:=''; + END; + DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); + + DBMS_OUTPUT.PUT_LINE('test2:'); + OPEN c1 FOR SELECT '20' FROM DUAL UNION SELECT '21' FROM DUAL UNION SELECT '22' FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' || bool_to_char(c1%ISOPEN)); + BEGIN + FETCH c0 INTO v0; + EXCEPTION + WHEN OTHERS THEN v0:=''; + END; + BEGIN + FETCH c1 INTO v1; + EXCEPTION + WHEN OTHERS THEN v1:=''; + END; + DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); + + DBMS_OUTPUT.PUT_LINE('test2a'); + CLOSE c1; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' ||bool_to_char(c1%ISOPEN)); + BEGIN + FETCH c0 INTO v0; + EXCEPTION + WHEN OTHERS THEN v0:=''; + END; + BEGIN + FETCH c1 INTO v1; + EXCEPTION + WHEN OTHERS THEN v1:=''; + END; + DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); + + DBMS_OUTPUT.PUT_LINE('test3'); + OPEN c0 FOR SELECT '30' FROM DUAL UNION SELECT '31' FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c0%ISOPEN=' || bool_to_char(c0%ISOPEN) || ' ' || 'c1%ISOPEN=' ||bool_to_char(c1%ISOPEN)); + FETCH c0 INTO v0; + FETCH c1 INTO v1; + DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); + + DBMS_OUTPUT.PUT_LINE('test4'); + OPEN c0 FOR SELECT 'c0-40' FROM DUAL UNION SELECT 'c0-41' FROM DUAL; + OPEN c1 FOR SELECT 'c1-40' FROM DUAL UNION SELECT 'c1-41' FROM DUAL; + FETCH c0 INTO v0; + FETCH c1 INTO v1; + DBMS_OUTPUT.PUT_LINE('v0=' || v0 || ' v1=' || v1); + +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DROP PACKAGE DBMS_OUTPUT; +DROP FUNCTION bool_to_char; diff --git a/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-func_hybrid.test b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-func_hybrid.test new file mode 100644 index 00000000000..d55609233f7 --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor-func_hybrid.test @@ -0,0 +1,143 @@ +SET sql_mode=ORACLE; + +# Helper functions and packages +--disable_query_log +--source include/dbms_output.inc +--source include/bool_to_char.inc +--enable_query_log + + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Hybrid functions +--echo # + +# +# CASE +# + +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + v INT; +BEGIN + OPEN c1 FOR SELECT 10 FROM DUAL; + c2:= CASE WHEN c0 IS NULL THEN c1 ELSE c0 END; + FETCH c2 INTO v; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +# +# Oracle style DECODE(switch , when1 , then1 [, when2 , then2]... [, default] ) +# + +DELIMITER /; +CREATE PROCEDURE p1(switch INT) AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + c3 SYS_REFCURSOR; + c4 SYS_REFCURSOR; + cx SYS_REFCURSOR; + v INT; +BEGIN + OPEN c0 FOR SELECT 10 FROM DUAL; + OPEN c1 FOR SELECT 11 FROM DUAL; + OPEN c2 FOR SELECT 12 FROM DUAL; + OPEN c3 FOR SELECT 13 FROM DUAL; + OPEN c4 FOR SELECT 14 FROM DUAL; + + cx:= DECODE(switch, 0, c0, 1, c1, 2, c2, 3, c3, c4); + FETCH cx INTO v; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +CALL p1(0); +CALL p1(1); +CALL p1(2); +CALL p1(3); +CALL p1(4); +CALL p1(5); +DROP PROCEDURE p1; + + +# +# COALESCE +# + +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + v INT; +BEGIN + OPEN c1 FOR SELECT 10 FROM DUAL; + c2:= COALESCE(c0,c1); + FETCH c2 INTO v; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +# +# IF +# + +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + v INT; +BEGIN + OPEN c1 FOR SELECT 10 FROM DUAL; + c2:= IF(false, c0,c1); + FETCH c2 INTO v; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + +# +# GREATEST +# +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + v INT; +BEGIN + OPEN c1 FOR SELECT 10 FROM DUAL; + c2:= GREATEST(c0,c1); + FETCH c2 INTO v; + DBMS_OUTPUT.PUT_LINE('v=' || v); +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; + + +DROP PACKAGE dbms_output; +DROP FUNCTION bool_to_char; + +SET sql_mode=DEFAULT; diff --git a/mysql-test/suite/compat/oracle/t/sp-sys_refcursor.test b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor.test new file mode 100644 index 00000000000..fd37809eb50 --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-sys_refcursor.test @@ -0,0 +1,646 @@ +SET sql_mode=ORACLE; + +# Helper functions and packages +--disable_query_log +--source include/dbms_output.inc +--source include/bool_to_char.inc +--enable_query_log + + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + + +--echo # +--echo # Error: Unknown cursor and wrong variable data type in OPEN, FETCH, CLOSE +--echo # + +DELIMITER /; + +--error ER_SP_UNDECLARED_VAR +BEGIN + OPEN c FOR SELECT 1 AS c FROM DUAL; +END; +/ + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + c INT; +BEGIN + OPEN c FOR SELECT 1 AS c FROM DUAL; +END; +/ + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + c INT; +BEGIN + CLOSE c; +END; +/ + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + a INT; + c INT; +BEGIN + FETCH c INTO a; +END; +/ + +DELIMITER ;/ + + +--echo # +--echo # Error: Closing a not open cursor +--echo # + +DELIMITER /; +--error ER_SP_CURSOR_NOT_OPEN +DECLARE + c SYS_REFCURSOR; +BEGIN + CLOSE c; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # Error: Fetching from a not open cursor +--echo # + +DELIMITER /; +--error ER_SP_CURSOR_NOT_OPEN +DECLARE + a INT; + c SYS_REFCURSOR; +BEGIN + FETCH c INTO a; +END; +/ +DELIMITER ;/ + +--echo # +--echo # Error: fetching beyond the available number of records +--echo # sql_mode=ORACLE preserves the variable value +--echo # + +DELIMITER /; +DECLARE + a INT; + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE(a); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE(a); +END; +/ +DELIMITER ;/ + + +--echo # +--echo # Two consequent OPEN (without a CLOSE in beetween) are allowed +--echo # + +DELIMITER /; +DECLARE + a INT; + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + OPEN c FOR SELECT 2 FROM DUAL; + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE(a); +END; +/ +DELIMITER ;/ + + +--echo # +--echo # Many consequent OPEN (without a CLOSE in between) are allowed +--echo # and do not cause ER_TOO_MANY_OPEN_CURSORS. +--echo # + +SET max_open_cursors=2; +DELIMITER /; +DECLARE + c SYS_REFCURSOR; +BEGIN + FOR i IN 1..3 + LOOP + OPEN c FOR SELECT 1 AS c FROM DUAL; + END LOOP; +END; +/ +DELIMITER ;/ +SET max_open_cursors=DEFAULT; + + +--echo # +--echo # Simple use example (OPEN, FETCH, CLOSE) +--echo # + +DELIMITER /; +DECLARE + c SYS_REFCURSOR; + a INT; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + FETCH c INTO a; + CLOSE c; + DBMS_OUTPUT.PUT_LINE(a); +END; +/ +DELIMITER ;/ + + +--echo # +--echo # Fetching from two parallel cursors +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +DELIMITER /; +CREATE PROCEDURE p1() AS + a0 INT; + a1 INT; + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT a*10 FROM t1; + OPEN c1 FOR SELECT a*20 FROM t1; + FETCH c0 INTO a0; + FETCH c1 INTO a1; + DBMS_OUTPUT.PUT_LINE(a0 || ' ' || a1); + CLOSE c0; + CLOSE c1; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # Returning an open cursor from a function +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER /; +CREATE FUNCTION f1 RETURN SYS_REFCURSOR AS + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT a FROM t1 ORDER BY a; + RETURN c; +END; +/ +CREATE PROCEDURE p1 AS + a INT; + c SYS_REFCURSOR DEFAULT f1(); +BEGIN + LOOP + FETCH c INTO a; + EXIT WHEN c%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + CLOSE c; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP TABLE t1; + + +--echo # +--echo # Returning SYS_REFCURSOR from a function: too many open cursors +--echo # + +SET max_open_cursors=2; +DELIMITER /; +CREATE OR REPLACE FUNCTION f1 RETURN SYS_REFCURSOR IS + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT 1 AS a FROM DUAL; + RETURN c; +END; +/ +--error ER_TOO_MANY_OPEN_CURSORS +DECLARE + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + a INT; +BEGIN + c0:= f1(); + FETCH c0 INTO a; + c1:= f1(); + FETCH c1 INTO a; + c2:= f1(); + FETCH c2 INTO a; +END; +/ +DELIMITER ;/ +DROP FUNCTION f1; +SET max_open_cursors=DEFAULT; + + +--echo # +--echo # Returning an open cursor as an OUT param +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +INSERT INTO t1 VALUES (20); +DELIMITER /; +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN + OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +/ +CREATE PROCEDURE p2 AS + a INT; + c SYS_REFCURSOR; +BEGIN + p1(c); + LOOP + FETCH c INTO a; + EXIT WHEN c%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + CLOSE c; +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; + +--echo # +--echo # Returning an open cursor as an OUT param: Too many open cursors +--echo # + +SET @@max_open_cursors=2; +DELIMITER /; +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN + OPEN c FOR VALUES (10),(20); +END; +/ +CREATE PROCEDURE p2 AS + a INT; + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; +BEGIN + p1(c0); + + LOOP + FETCH c0 INTO a; + EXIT WHEN c0%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + + p1(c1); + + LOOP + FETCH c1 INTO a; + EXIT WHEN c1%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + + p1(c2); +END; +/ +DELIMITER ;/ +--error ER_TOO_MANY_OPEN_CURSORS +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET @@max_open_cursors=DEFAULT; + + +--echo # +--echo # Returning an open cursor as an OUT param: no "Too many open cursors" +--echo # + +SET @@max_open_cursors=2; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +INSERT INTO t1 VALUES (20); +DELIMITER /; +CREATE PROCEDURE p1(c OUT SYS_REFCURSOR) AS +BEGIN + OPEN c FOR SELECT a FROM t1 ORDER BY a; +END; +/ +CREATE PROCEDURE p2 AS + a INT; + c SYS_REFCURSOR; +BEGIN + FOR i IN 1..5 + LOOP + p1(c); -- This closes the cursor and reopens it in p1 + LOOP + FETCH c INTO a; + EXIT WHEN c%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + END LOOP; + CLOSE c; +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +SET @@max_open_cursors=DEFAULT; + + +--echo # +--echo # Returning an open cursor as an INOUT param: no "Too many open cursors" +--echo # + +SET @@max_open_cursors=2; +DELIMITER /; +CREATE PROCEDURE p1(c INOUT SYS_REFCURSOR) AS +BEGIN + OPEN c FOR VALUES (10), (20); +END; +/ +CREATE PROCEDURE p2 AS + a INT; + c SYS_REFCURSOR; +BEGIN + FOR i IN 1..5 + LOOP + p1(c); -- This closes the cursor and reopens it in p1 + LOOP + FETCH c INTO a; + EXIT WHEN c%NOTFOUND; + DBMS_OUTPUT.PUT_LINE(a); + END LOOP; + END LOOP; + CLOSE c; +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET @@max_open_cursors=DEFAULT; + + +--echo # +--echo # Function returning SYS_REFCURSOR and mysql.proc +--echo # + +DELIMITER /; +CREATE FUNCTION f1() RETURN SYS_REFCURSOR AS + c0 SYS_REFCURSOR; +BEGIN + RETURN c0; +END; +/ +DELIMITER ;/ +SELECT returns FROM mysql.proc WHERE name='f1'; +SHOW CREATE FUNCTION f1; +DROP FUNCTION f1; + + +--echo # +--echo # Procedure with a SYS_REFCURSOR parameter and mysql.proc +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(a0 OUT SYS_REFCURSOR) AS + c0 SYS_REFCURSOR; +BEGIN + a0:= c0; +END; +/ +DELIMITER ;/ +SELECT param_list FROM mysql.proc WHERE name='p1'; +SHOW CREATE PROCEDURE p1; +DROP PROCEDURE p1; + + +--echo # +--echo # NULL predicate +--echo # + +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + v INT; +BEGIN + DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); + OPEN c0 FOR SELECT 1 FROM DUAL; + DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); + FETCH c0 INTO v; + DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); + CLOSE c0; + DBMS_OUTPUT.PUT_LINE(bool_to_char(c0 IS NULL)); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Cursor attributes on a not open SYS_REFCURSOR +--echo # + +DELIMITER /; + +DECLARE + c SYS_REFCURSOR; +BEGIN + DBMS_OUTPUT.PUT_LINE('c%ISOPEN=' || bool_to_char(c%ISOPEN)); +END; +/ + +--error ER_SP_CURSOR_NOT_OPEN +DECLARE + c SYS_REFCURSOR; +BEGIN + DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +END; +/ + +--error ER_SP_CURSOR_NOT_OPEN +DECLARE + c SYS_REFCURSOR; +BEGIN + DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +END; +/ + +--error ER_SP_CURSOR_NOT_OPEN +DECLARE + c SYS_REFCURSOR; +BEGIN + DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +END; +/ + +DELIMITER ;/ + + +--echo # +--echo # Cursor attributes on an open SYS_REFCURSOR +--echo # + +DELIMITER /; + +DECLARE + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c%ISOPEN=' || bool_to_char(c%ISOPEN)); +END; +/ + +DECLARE + c SYS_REFCURSOR; + a INT; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%FOUND=' || bool_to_char(c%FOUND)); +END; +/ + +DECLARE + c SYS_REFCURSOR; + a INT; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%NOTFOUND=' || bool_to_char(c%NOTFOUND)); +END; +/ + +DECLARE + c SYS_REFCURSOR; + a INT; +BEGIN + OPEN c FOR SELECT 1 FROM DUAL; + DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); + FETCH c INTO a; + DBMS_OUTPUT.PUT_LINE('c%ROWCOUNT=' || c%ROWCOUNT); +END; +/ + +DELIMITER ;/ + + +--echo # +--echo # - Returning a never opened cursor does not cause ER_TOO_MANY_OPEN_CURSORS +--echo # - Returning an opened+closed cursor does not cause ER_TOO_MANY_OPEN_CURSORS +--echo # - Only returning an opened cursor causes ER_TOO_MANY_OPEN_CURSORS +--echo # + +SET @@max_open_cursors=2; +DELIMITER /; +CREATE FUNCTION f1(task VARCHAR) RETURN SYS_REFCURSOR AS + c SYS_REFCURSOR := NULL; +BEGIN + IF task LIKE '%open%' THEN + OPEN c FOR SELECT 1 FROM DUAL; + END IF; + IF task LIKE '%close%' THEN + CLOSE c; + END IF; + RETURN c; +END; +/ +CREATE PROCEDURE p1(task VARCHAR) AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; +BEGIN + c0:= f1(task); + DBMS_OUTPUT.PUT_LINE('0' || ' ' || CASE WHEN c0 IS NULL THEN '' ELSE '' END || + ' ' || bool_to_char(c0%ISOPEN)); + c1:= f1(task); + DBMS_OUTPUT.PUT_LINE('1' || ' ' || CASE WHEN c1 IS NULL THEN '' ELSE '' END || + ' ' || bool_to_char(c1%ISOPEN)); + c2:= f1(task); + DBMS_OUTPUT.PUT_LINE('2' || ' ' || CASE WHEN c2 IS NULL THEN '' ELSE '' END || + ' ' || bool_to_char(c2%ISOPEN)); +END; +/ +DELIMITER ;/ +CALL p1('none'); +CALL p1('open_close'); +--error ER_TOO_MANY_OPEN_CURSORS +CALL p1('open'); + +DROP PROCEDURE p1; +DROP FUNCTION f1; +SET @@max_open_cursors=DEFAULT; + + +--echo # +--echo # Cursor variables cannot be declared as part of a package +--echo # + +DELIMITER /; +CREATE PACKAGE pkg AS + FUNCTION f1 RETURN INT; +END; +/ + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE PACKAGE BODY pkg AS + cur SYS_REFCURSOR; -- This is wrong (the top PACKAGE BODY frame) + FUNCTION f1 RETURN INT AS + BEGIN + RETURN 1; + END; +END; +/ + +CREATE PACKAGE BODY pkg AS + vc INT := 0; + FUNCTION f1 RETURN INT AS + cur SYS_REFCURSOR; + BEGIN + RETURN vc; + END; +BEGIN + DECLARE + cur SYS_REFCURSOR; -- This is OK (executable section) + BEGIN + OPEN cur FOR SELECT 1 AS c FROM DUAL; + FETCH cur INTO vc; + CLOSE cur; + END; +END; +/ +DELIMITER ;/ +SELECT pkg.f1() FROM DUAL; +DROP PACKAGE pkg; + + +DROP FUNCTION bool_to_char; +DROP PACKAGE DBMS_OUTPUT; diff --git a/mysql-test/suite/perfschema/r/max_program_zero.result b/mysql-test/suite/perfschema/r/max_program_zero.result index b6c5b6cb094..52c1bca300b 100644 --- a/mysql-test/suite/perfschema/r/max_program_zero.result +++ b/mysql-test/suite/perfschema/r/max_program_zero.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 1 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/ortho_iter.result b/mysql-test/suite/perfschema/r/ortho_iter.result index 84295bea375..f86e36a2f9a 100644 --- a/mysql-test/suite/perfschema/r/ortho_iter.result +++ b/mysql-test/suite/perfschema/r/ortho_iter.result @@ -251,7 +251,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/privilege_table_io.result b/mysql-test/suite/perfschema/r/privilege_table_io.result index 29c53568525..2428f33f5dc 100644 --- a/mysql-test/suite/perfschema/r/privilege_table_io.result +++ b/mysql-test/suite/perfschema/r/privilege_table_io.result @@ -57,7 +57,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_idle.result b/mysql-test/suite/perfschema/r/start_server_disable_idle.result index 76ff8d00822..64801ab66b4 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_idle.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_idle.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_stages.result b/mysql-test/suite/perfschema/r/start_server_disable_stages.result index afc8828819e..45981957280 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_stages.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_stages.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_statements.result b/mysql-test/suite/perfschema/r/start_server_disable_statements.result index 2f5c31b00ea..9af76d83050 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_statements.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_statements.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result index 46f97178b05..ca7f6b8dd62 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_waits.result b/mysql-test/suite/perfschema/r/start_server_disable_waits.result index 032003b8e10..6e0161ed54a 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_waits.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_waits.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_innodb.result b/mysql-test/suite/perfschema/r/start_server_innodb.result index 11ad1e282aa..8cf80b93f53 100644 --- a/mysql-test/suite/perfschema/r/start_server_innodb.result +++ b/mysql-test/suite/perfschema/r/start_server_innodb.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_index.result b/mysql-test/suite/perfschema/r/start_server_low_index.result index c4369d46068..7e8fe690b17 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_index.result +++ b/mysql-test/suite/perfschema/r/start_server_low_index.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result index 8fa82cb0d02..4d1cb015a24 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_account.result b/mysql-test/suite/perfschema/r/start_server_no_account.result index 73a700b74bd..2792e6d7dc1 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_account.result +++ b/mysql-test/suite/perfschema/r/start_server_no_account.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result index 8274a2769dc..c4232ed9511 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result index b2ec4497457..40da0ca5c18 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_class.result b/mysql-test/suite/perfschema/r/start_server_no_file_class.result index 5deedeb23c2..47ed347ec01 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result index da1a0a49de9..e8f2bf2d1d4 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_host.result b/mysql-test/suite/perfschema/r/start_server_no_host.result index 25a5d62456b..e950f5da637 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_host.result +++ b/mysql-test/suite/perfschema/r/start_server_no_host.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_index.result b/mysql-test/suite/perfschema/r/start_server_no_index.result index e11ab79baeb..7d4f26fd281 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_index.result +++ b/mysql-test/suite/perfschema/r/start_server_no_index.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mdl.result b/mysql-test/suite/perfschema/r/start_server_no_mdl.result index b1c2f14476b..2aa8cbd4fe0 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mdl.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result index a43fb12f044..a06e8f9dec3 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result index 78d73054b85..9b1f3b82a65 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result index 73544e45a63..59a069b9d7e 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result index 4c70b3e103e..7457043306a 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result +++ b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result index b88a2a81068..63dabc347cb 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result index 39af0e229c2..ccaa42e83e4 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result index 6cc428ecbe1..3999628386e 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result index b7863472fb6..948b387fe20 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result index 41cb4418083..fbf00476285 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 0 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result index eed414918d7..d9be2628d73 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 0 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result index ad8f016444c..bb5d321fe45 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 0 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result index f6b9bd84b0f..44e737acd15 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result index cd3572be605..46fdff79e72 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result index e57f64bf8ba..fdbe36a5253 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result index bdf93f8d19e..c46f94e9dc0 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result index c5df6a822c1..7a268671270 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 0 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result index 09949bd084b..58fa73720ed 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 0 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result index 55ebb92cff3..6ad4458cc22 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result index a9460537ae2..c6cbc429bf3 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result index edcf8b3c21e..65c3177088a 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result index cbb643e9f40..27b02b89cd2 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result index d81003ef32b..3ad471bbcf2 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_user.result b/mysql-test/suite/perfschema/r/start_server_no_user.result index 371da659871..798308bba1e 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_user.result +++ b/mysql-test/suite/perfschema/r/start_server_no_user.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result index f17ca67f1b1..2509213844f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result index f305d1f6205..9c1168d54b4 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_off.result b/mysql-test/suite/perfschema/r/start_server_off.result index e70a7d8b1ab..d98a4f48412 100644 --- a/mysql-test/suite/perfschema/r/start_server_off.result +++ b/mysql-test/suite/perfschema/r/start_server_off.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_on.result b/mysql-test/suite/perfschema/r/start_server_on.result index 11ad1e282aa..8cf80b93f53 100644 --- a/mysql-test/suite/perfschema/r/start_server_on.result +++ b/mysql-test/suite/perfschema/r/start_server_on.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_variables.result b/mysql-test/suite/perfschema/r/start_server_variables.result index cc6278c6c6a..a0c49020d25 100644 --- a/mysql-test/suite/perfschema/r/start_server_variables.result +++ b/mysql-test/suite/perfschema/r/start_server_variables.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result index 7f6b77d2d61..d834936b273 100644 --- a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result +++ b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result @@ -135,7 +135,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 223 +performance_schema_max_statement_classes 227 performance_schema_max_statement_stack 2 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/sys_vars/r/max_open_cursors_basic.result b/mysql-test/suite/sys_vars/r/max_open_cursors_basic.result new file mode 100644 index 00000000000..0515dade6a3 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/max_open_cursors_basic.result @@ -0,0 +1,184 @@ +SET @start_global_value = @@global.max_open_cursors; +SET @start_session_value = @@session.max_open_cursors; +SELECT @start_session_value = @start_global_value; +@start_session_value = @start_global_value +1 +'#--------------------FN_DYNVARS_077_01-------------------------#' +SET @@global.max_open_cursors = 1677721610; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '1677721610' +SET @@global.max_open_cursors = DEFAULT; +SELECT @@global.max_open_cursors > 0; +@@global.max_open_cursors > 0 +1 +SET @@session.max_open_cursors = 1677721610; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '1677721610' +SET @@session.max_open_cursors = DEFAULT; +SELECT @@session.max_open_cursors > 0; +@@session.max_open_cursors > 0 +1 +'#--------------------FN_DYNVARS_077_03-------------------------#' +SET @@global.max_open_cursors = 16384; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +16384 +SET @@global.max_open_cursors = 16385; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +16385 +SET @@global.max_open_cursors = 65535; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65535 +SET @@global.max_open_cursors = 4294967294; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '4294967294' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65536 +SET @@global.max_open_cursors = 4294967295; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '4294967295' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65536 +'#--------------------FN_DYNVARS_077_04-------------------------#' +SET @@session.max_open_cursors = 16384; +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +16384 +SET @@session.max_open_cursors = 16385; +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +16385 +SET @@session.max_open_cursors = 65535; +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65535 +SET @@session.max_open_cursors = 4294967294; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '4294967294' +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65536 +SET @@session.max_open_cursors = 4294967295; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '4294967295' +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65536 +'#------------------FN_DYNVARS_077_05-----------------------#' +SET @@global.max_open_cursors = -1; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '-1' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +0 +SET @@global.max_open_cursors = -1024; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '-1024' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +0 +SET @@global.max_open_cursors = 1024; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +1024 +SET @@global.max_open_cursors = 16383; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +16383 +SET @@global.max_open_cursors = 4294967296; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65536 +SET @@global.max_open_cursors = 65530.34; +ERROR 42000: Incorrect argument type to variable 'max_open_cursors' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65536 +SET @@global.max_open_cursors = test; +ERROR 42000: Incorrect argument type to variable 'max_open_cursors' +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +65536 +SET @@session.max_open_cursors = -1; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '-1' +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +0 +SET @@session.max_open_cursors = 16383; +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +16383 +SET @@session.max_open_cursors = 4294967296; +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65536 +SET @@session.max_open_cursors = 65530.34; +ERROR 42000: Incorrect argument type to variable 'max_open_cursors' +SET @@session.max_open_cursors = 10737418241; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '10737418241' +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65536 +SET @@session.max_open_cursors = test; +ERROR 42000: Incorrect argument type to variable 'max_open_cursors' +SELECT @@session.max_open_cursors; +@@session.max_open_cursors +65536 +'#------------------FN_DYNVARS_077_06-----------------------#' +SELECT @@global.max_open_cursors = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='max_open_cursors'; +@@global.max_open_cursors = VARIABLE_VALUE +1 +'#------------------FN_DYNVARS_077_07-----------------------#' +SELECT @@session.max_open_cursors = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='max_open_cursors'; +@@session.max_open_cursors = VARIABLE_VALUE +1 +'#------------------FN_DYNVARS_077_08-----------------------#' +SET @@global.max_open_cursors = TRUE; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +1 +SET @@global.max_open_cursors = FALSE; +SELECT @@global.max_open_cursors; +@@global.max_open_cursors +0 +'#---------------------FN_DYNVARS_077_09----------------------#' +SET @@global.max_open_cursors = 163845; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '163845' +SELECT @@max_open_cursors = @@global.max_open_cursors; +@@max_open_cursors = @@global.max_open_cursors +1 +'#---------------------FN_DYNVARS_077_10----------------------#' +SET @@max_open_cursors = 16777216; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '16777216' +SELECT @@max_open_cursors = @@local.max_open_cursors; +@@max_open_cursors = @@local.max_open_cursors +1 +SELECT @@local.max_open_cursors = @@session.max_open_cursors; +@@local.max_open_cursors = @@session.max_open_cursors +1 +'#---------------------FN_DYNVARS_077_11----------------------#' +SET max_open_cursors = 316777216; +Warnings: +Warning 1292 Truncated incorrect max_open_cursors value: '316777216' +SELECT @@max_open_cursors; +@@max_open_cursors +65536 +SELECT local.max_open_cursors; +ERROR 42S02: Unknown table 'local' in SELECT +SELECT session.max_open_cursors; +ERROR 42S02: Unknown table 'session' in SELECT +SELECT max_open_cursors = @@session.max_open_cursors; +ERROR 42S22: Unknown column 'max_open_cursors' in 'SELECT' +SET @@global.max_open_cursors = @start_global_value; diff --git a/mysql-test/suite/sys_vars/r/max_open_cursors_func.result b/mysql-test/suite/sys_vars/r/max_open_cursors_func.result new file mode 100644 index 00000000000..7e732a3ed80 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/max_open_cursors_func.result @@ -0,0 +1,21 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +SET @@max_open_cursors=1; +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 CURSOR FOR SELECT 'c1val' FROM DUAL; +OPEN c0 FOR SELECT 'c0val' FROM DUAL; +OPEN c1; +END; +$$ +ERROR HY000: Too many open cursors; max 1 cursors allowed +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 CURSOR FOR SELECT 'c1val' FROM DUAL; +OPEN c1; +OPEN c0 FOR SELECT 'c0val' FROM DUAL; +END; +$$ +ERROR HY000: Too many open cursors; max 1 cursors allowed +SET @@max_open_cursors=DEFAULT; diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 2c1da015576..fb801246bdb 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -2032,6 +2032,16 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME MAX_OPEN_CURSORS +VARIABLE_SCOPE SESSION +VARIABLE_TYPE INT UNSIGNED +VARIABLE_COMMENT The maximum number of open cursors allowed per session +NUMERIC_MIN_VALUE 0 +NUMERIC_MAX_VALUE 65536 +NUMERIC_BLOCK_SIZE 1 +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_PASSWORD_ERRORS VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 00f5095a49d..4271bab38e0 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -2232,6 +2232,16 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME MAX_OPEN_CURSORS +VARIABLE_SCOPE SESSION +VARIABLE_TYPE INT UNSIGNED +VARIABLE_COMMENT The maximum number of open cursors allowed per session +NUMERIC_MIN_VALUE 0 +NUMERIC_MAX_VALUE 65536 +NUMERIC_BLOCK_SIZE 1 +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_PASSWORD_ERRORS VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED diff --git a/mysql-test/suite/sys_vars/t/max_open_cursors_basic.test b/mysql-test/suite/sys_vars/t/max_open_cursors_basic.test new file mode 100644 index 00000000000..ab856c00b2f --- /dev/null +++ b/mysql-test/suite/sys_vars/t/max_open_cursors_basic.test @@ -0,0 +1,207 @@ +############## mysql-test\t\max_open_cursors_basic.test ############### +# # +# Variable Name: max_open_cursors # +# Scope: GLOBAL | SESSION # +# Access Type: Dynamic # +# Data Type: numeric # +# Default Value: 16777216 # +# Range: 0-65535 # +# # +# # +# Creation Date: 2025-02-10 # +# Author: Salman # +# # +# Description: Test Cases of Dynamic System Variable max_open_cursors # +# that checks the behavior of this variable in the following ways# +# * Default Value # +# * Valid & Invalid values # +# * Scope & Access method # +# * Data Integrity # +# # +############################################################################### + +--source include/load_sysvars.inc + +############################################################### +# START OF max_open_cursors TESTS # +############################################################### + + +############################################################# +# Save initial value # +############################################################# + +SET @start_global_value = @@global.max_open_cursors; +SET @start_session_value = @@session.max_open_cursors; +SELECT @start_session_value = @start_global_value; + + +--echo '#--------------------FN_DYNVARS_077_01-------------------------#' +############################################################### +# Display the DEFAULT value of max_open_cursors # +############################################################### + +SET @@global.max_open_cursors = 1677721610; +SET @@global.max_open_cursors = DEFAULT; +SELECT @@global.max_open_cursors > 0; + +SET @@session.max_open_cursors = 1677721610; +SET @@session.max_open_cursors = DEFAULT; +SELECT @@session.max_open_cursors > 0; + + +--echo '#--------------------FN_DYNVARS_077_03-------------------------#' +############################################################################ +# Change the value of max_open_cursors to a valid value for GLOBAL Scope # +############################################################################ + +SET @@global.max_open_cursors = 16384; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 16385; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 65535; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 4294967294; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 4294967295; +SELECT @@global.max_open_cursors; + +--echo '#--------------------FN_DYNVARS_077_04-------------------------#' +############################################################################# +# Change the value of max_open_cursors to a valid value for SESSION Scope # +############################################################################# + +SET @@session.max_open_cursors = 16384; +SELECT @@session.max_open_cursors; +SET @@session.max_open_cursors = 16385; +SELECT @@session.max_open_cursors; +SET @@session.max_open_cursors = 65535; +SELECT @@session.max_open_cursors; +SET @@session.max_open_cursors = 4294967294; +SELECT @@session.max_open_cursors; +SET @@session.max_open_cursors = 4294967295; +SELECT @@session.max_open_cursors; + +--echo '#------------------FN_DYNVARS_077_05-----------------------#' +############################################################## +# Change the value of max_open_cursors to an invalid value # +############################################################## + +SET @@global.max_open_cursors = -1; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = -1024; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 1024; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = 16383; +SELECT @@global.max_open_cursors; +--disable_warnings +SET @@global.max_open_cursors = 4294967296; +--enable_warnings +--replace_result 4294966272 4294967296 +SELECT @@global.max_open_cursors; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.max_open_cursors = 65530.34; +--replace_result 4294966272 4294967296 +SELECT @@global.max_open_cursors; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.max_open_cursors = test; +--replace_result 4294966272 4294967296 +SELECT @@global.max_open_cursors; + +SET @@session.max_open_cursors = -1; +SELECT @@session.max_open_cursors; +SET @@session.max_open_cursors = 16383; +SELECT @@session.max_open_cursors; +--disable_warnings +SET @@session.max_open_cursors = 4294967296; +--enable_warnings +--replace_result 4294966272 4294967296 +SELECT @@session.max_open_cursors; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@session.max_open_cursors = 65530.34; +SET @@session.max_open_cursors = 10737418241; +--replace_result 4294966272 10737418240 +SELECT @@session.max_open_cursors; + +--Error ER_WRONG_TYPE_FOR_VAR +SET @@session.max_open_cursors = test; +--replace_result 4294966272 10737418240 +SELECT @@session.max_open_cursors; + + +--echo '#------------------FN_DYNVARS_077_06-----------------------#' +#################################################################### +# Check if the value in GLOBAL Table matches value in variable # +#################################################################### + + +SELECT @@global.max_open_cursors = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='max_open_cursors'; + +--echo '#------------------FN_DYNVARS_077_07-----------------------#' +#################################################################### +# Check if the value in SESSION Table matches value in variable # +#################################################################### + +SELECT @@session.max_open_cursors = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='max_open_cursors'; + + +--echo '#------------------FN_DYNVARS_077_08-----------------------#' +#################################################################### +# Check if TRUE and FALSE values can be used on variable # +#################################################################### + +SET @@global.max_open_cursors = TRUE; +SELECT @@global.max_open_cursors; +SET @@global.max_open_cursors = FALSE; +SELECT @@global.max_open_cursors; + + +--echo '#---------------------FN_DYNVARS_077_09----------------------#' +################################################################################# +# Check if accessing variable with and without GLOBAL point to same variable # +################################################################################# + +SET @@global.max_open_cursors = 163845; +SELECT @@max_open_cursors = @@global.max_open_cursors; + + +--echo '#---------------------FN_DYNVARS_077_10----------------------#' +######################################################################################################## +# Check if accessing variable with SESSION,LOCAL and without SCOPE points to same session variable # +######################################################################################################## + +SET @@max_open_cursors = 16777216; +SELECT @@max_open_cursors = @@local.max_open_cursors; +SELECT @@local.max_open_cursors = @@session.max_open_cursors; + + +--echo '#---------------------FN_DYNVARS_077_11----------------------#' +############################################################################# +# Check if max_open_cursors can be accessed with and without @@ sign # +############################################################################# + +SET max_open_cursors = 316777216; +SELECT @@max_open_cursors; +--Error ER_UNKNOWN_TABLE +SELECT local.max_open_cursors; +--Error ER_UNKNOWN_TABLE +SELECT session.max_open_cursors; +--Error ER_BAD_FIELD_ERROR +SELECT max_open_cursors = @@session.max_open_cursors; + + +#################################### +# Restore initial value # +#################################### + +SET @@global.max_open_cursors = @start_global_value; + + +####################################################### +# END OF max_open_cursors TESTS # +####################################################### diff --git a/mysql-test/suite/sys_vars/t/max_open_cursors_func.test b/mysql-test/suite/sys_vars/t/max_open_cursors_func.test new file mode 100644 index 00000000000..44fd180620a --- /dev/null +++ b/mysql-test/suite/sys_vars/t/max_open_cursors_func.test @@ -0,0 +1,31 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +SET @@max_open_cursors=1; + +# Make sure @@max_open_cursors counts both static cursors and SYS_REFCURSORs. +DELIMITER $$; +--error ER_TOO_MANY_OPEN_CURSORS +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 CURSOR FOR SELECT 'c1val' FROM DUAL; + OPEN c0 FOR SELECT 'c0val' FROM DUAL; + OPEN c1; +END; +$$ +DELIMITER ;$$ + +# Same as above, but with the opposite OPEN order +DELIMITER $$; +--error ER_TOO_MANY_OPEN_CURSORS +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 CURSOR FOR SELECT 'c1val' FROM DUAL; + OPEN c1; + OPEN c0 FOR SELECT 'c0val' FROM DUAL; +END; +$$ +DELIMITER ;$$ + +SET @@max_open_cursors=DEFAULT; diff --git a/plugin/type_cursor/CMakeLists.txt b/plugin/type_cursor/CMakeLists.txt new file mode 100644 index 00000000000..daa5b9b7e41 --- /dev/null +++ b/plugin/type_cursor/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2023-2025, MariaDB corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + +MYSQL_ADD_PLUGIN(type_cursor plugin.cc RECOMPILE_FOR_EMBEDDED + MANDATORY COMPONENT Cursor) diff --git a/plugin/type_cursor/mysql-test/type_cursor/suite.pm b/plugin/type_cursor/mysql-test/type_cursor/suite.pm new file mode 100644 index 00000000000..4dd5acf636a --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/suite.pm @@ -0,0 +1,7 @@ +package My::Suite::Type_cursor; + +@ISA = qw(My::Suite); + +sub is_default { 1 } + +bless { }; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.result new file mode 100644 index 00000000000..ff7c07d25ca --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.result @@ -0,0 +1,241 @@ +# +# Helper routines +# +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# sql_mode=DEFAULT: TYPE OF declaration for a single SYS_REFCURSOR: +# +CREATE PROCEDURE p1() +BEGIN +DECLARE v00 INT; +DECLARE c10 SYS_REFCURSOR; +DECLARE c11 TYPE OF c10; +OPEN c11 FOR SELECT 1; +FETCH c11 INTO v00; +SELECT c10, c11, v00, refs(0,4) AS refs; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set v00@0 NULL +1 set c10@1 NULL +2 set c11@2 NULL +3 copen STMT.cursor[c11@2] +4 cfetch STMT.cursor[c11@2] v00@0 +5 stmt 0 "SELECT c10, c11, v00, refs(0,4) AS refs" +6 destruct sys_refcursor c11@2 +7 destruct sys_refcursor c10@1 +CALL p1; +c10 c11 v00 refs +NULL 0 1 [1 NULL NULL NULL NULL] +DROP PROCEDURE p1; +# +# sql_mode=ORACLE" %TYPE declaration for a single SYS_REFCURSOR: +# +SET sql_mode=ORACLE; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +c1 c0%TYPE; +v1 INT; +v2 INT; +BEGIN +OPEN c0 FOR SELECT 1 AS c1, 2 AS c2 FROM DUAL; +c1:= c0; +FETCH c1 INTO v1, v2; +DBMS_OUTPUT.PUT_LINE(v1 ||' '|| v2); +END; +/ +CALL p1; + +1 2 +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; +# +# Anchored TYPE OF declarations for a ROW of SYS_REFCURSORs: +# DECLARE r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR); +# DECLARE r11 TYPE OF r10; +# +CREATE PROCEDURE open1(INOUT c00 SYS_REFCURSOR, value INT) +BEGIN +OPEN c00 FOR SELECT value; +END; +/ +SHOW PROCEDURE CODE open1 +/ +Pos Instruction +0 copen STMT.cursor[c00@0] +CREATE PROCEDURE open2(INOUT r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR)) +BEGIN +CALL open1(r00.c00, 20); +CALL open1(r00.c01, 21); +END; +/ +SHOW PROCEDURE CODE open2 +/ +Pos Instruction +0 stmt 88 "CALL open1(r00.c00, 20)" +1 stmt 88 "CALL open1(r00.c01, 21)" +CREATE PROCEDURE fetch1(c00 SYS_REFCURSOR, OUT v00 INT) +BEGIN +FETCH c00 INTO v00; +END; +/ +SHOW PROCEDURE CODE fetch1 +/ +Pos Instruction +0 cfetch STMT.cursor[c00@0] v00@1 +CREATE PROCEDURE fetch2(r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR), +OUT v00 ROW(i00 INT, i01 INT)) +BEGIN +CALL fetch1(r00.c00, v00.i00); +CALL fetch1(r00.c01, v00.i01); +END; +/ +SHOW PROCEDURE CODE fetch2 +/ +Pos Instruction +0 stmt 88 "CALL fetch1(r00.c00, v00.i00)" +1 stmt 88 "CALL fetch1(r00.c01, v00.i01)" +CREATE PROCEDURE close1(c00 SYS_REFCURSOR) +BEGIN +CLOSE c00; +END; +/ +SHOW PROCEDURE CODE close1 +/ +Pos Instruction +0 cclose STMT.cursor[c00@0] +CREATE PROCEDURE close2(r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR)) +BEGIN +CALL close1(r00.c00); +CALL close1(r00.c01); +END; +/ +SHOW PROCEDURE CODE close2 +/ +Pos Instruction +0 stmt 88 "CALL close1(r00.c00)" +1 stmt 88 "CALL close1(r00.c01)" +CREATE PROCEDURE p1() +BEGIN +DECLARE v00 ROW(i00 INT, i01 iNT); +DECLARE r10 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR); +DECLARE r11 TYPE OF r10; +CALL open2(r11); +CALL fetch2(r11, v00); +CALL close2(r11); +SELECT r11.c00, r11.c01, refs(0,3) AS refs, v00.i00, v00.i01; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set v00@0 NULL +1 set r10@1 NULL +2 set r11@2 NULL +3 stmt 88 "CALL open2(r11)" +4 stmt 88 "CALL fetch2(r11, v00)" +5 stmt 88 "CALL close2(r11)" +6 stmt 0 "SELECT r11.c00, r11.c01, refs(0,3) AS..." +7 destruct row r11@2 +8 destruct row r10@1 +CALL p1; +r11.c00 r11.c01 refs v00.i00 v00.i01 +0 1 [1 1 NULL NULL] 20 21 +DROP PROCEDURE p1; +DROP PROCEDURE open1; +DROP PROCEDURE open2; +DROP PROCEDURE fetch1; +DROP PROCEDURE fetch2; +DROP PROCEDURE close1; +DROP PROCEDURE close2; +# +# This declaration raises "Illegal parameter data type": +# DECLARE r00 ROW TYPE OF static_cursor_with_refcursor_fields; +# But only of the execution really comes into its block. +# +CREATE PROCEDURE p1(declare_row_type_of BOOLEAN) +BEGIN +DECLARE v00 INT; +DECLARE v01 INT; +DECLARE c00 SYS_REFCURSOR; +DECLARE c01 SYS_REFCURSOR; +DECLARE s00 CURSOR FOR SELECT c00, c01; +OPEN c00 FOR SELECT 10; +OPEN c01 FOR SELECT 11; +IF declare_row_type_of THEN +BEGIN +DECLARE r00 ROW TYPE OF s00; +END; +END IF; +FETCH c00 INTO v00; +FETCH c01 INTO v01; +SELECT c00, c01, refs(0,4) AS refs, v00, v01; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set v00@1 NULL +1 set v01@2 NULL +2 set c00@3 NULL +3 set c01@4 NULL +4 cpush s00@0 +5 copen STMT.cursor[c00@3] +6 copen STMT.cursor[c01@4] +7 jump_if_not 10(10) declare_row_type_of@0 +8 cursor_copy_struct s00 r00@5 +9 set r00@5 NULL +10 cfetch STMT.cursor[c00@3] v00@1 +11 cfetch STMT.cursor[c01@4] v01@2 +12 stmt 0 "SELECT c00, c01, refs(0,4) AS refs, v..." +13 cpop 1 +14 destruct sys_refcursor c01@4 +15 destruct sys_refcursor c00@3 +CALL p1(false); +c00 c01 refs v00 v01 +0 1 [1 1 NULL NULL NULL] 10 11 +CALL p1(true); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +DROP PROCEDURE p1; +# +# sql_mode=ORACLE: +# static_cursor_with_refcursor_field%ROWTYPE +# +SET sql_mode=ORACLE; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT 1 AS c1, 2 AS c2 FROM DUAL; +DECLARE +r c0%ROWTYPE; -- This is considered as a table c0. +BEGIN +CREATE TABLE t1 AS SELECT r.c1 AS c1, r.c2 AS c2; +END; +END; +/ +CALL p1; +ERROR 42S02: Table 'test.c0' doesn't exist +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; +# +# Fetching from a SYS_REFCURSOR into a %ROWTYPE variable +# +SET sql_mode=ORACLE; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (1, 'b1'); +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +r0 t1%ROWTYPE; +BEGIN +OPEN c0 FOR SELECT * FROM t1; +FETCH c0 INTO r0; +DBMS_OUTPUT.PUT_LINE(r0.a ||' '|| r0.b); +END; +/ +CALL p1; + +1 b1 +DROP TABLE t1; +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.test new file mode 100644 index 00000000000..f4ac5944af7 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-anchored.test @@ -0,0 +1,223 @@ +--source include/have_debug.inc + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--source include/dbms_output.inc +--enable_result_log +--enable_query_log + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # sql_mode=DEFAULT: TYPE OF declaration for a single SYS_REFCURSOR: +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE v00 INT; + DECLARE c10 SYS_REFCURSOR; + DECLARE c11 TYPE OF c10; + OPEN c11 FOR SELECT 1; + FETCH c11 INTO v00; + SELECT c10, c11, v00, refs(0,4) AS refs; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # sql_mode=ORACLE" %TYPE declaration for a single SYS_REFCURSOR: +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + c1 c0%TYPE; + v1 INT; + v2 INT; +BEGIN + OPEN c0 FOR SELECT 1 AS c1, 2 AS c2 FROM DUAL; + c1:= c0; + FETCH c1 INTO v1, v2; + DBMS_OUTPUT.PUT_LINE(v1 ||' '|| v2); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; + + +--echo # +--echo # Anchored TYPE OF declarations for a ROW of SYS_REFCURSORs: +--echo # DECLARE r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR); +--echo # DECLARE r11 TYPE OF r10; +--echo # + +DELIMITER /; +CREATE PROCEDURE open1(INOUT c00 SYS_REFCURSOR, value INT) +BEGIN + OPEN c00 FOR SELECT value; +END; +/ +SHOW PROCEDURE CODE open1 +/ +CREATE PROCEDURE open2(INOUT r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR)) +BEGIN + CALL open1(r00.c00, 20); + CALL open1(r00.c01, 21); +END; +/ +SHOW PROCEDURE CODE open2 +/ +CREATE PROCEDURE fetch1(c00 SYS_REFCURSOR, OUT v00 INT) +BEGIN + FETCH c00 INTO v00; +END; +/ +SHOW PROCEDURE CODE fetch1 +/ +CREATE PROCEDURE fetch2(r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR), + OUT v00 ROW(i00 INT, i01 INT)) +BEGIN + CALL fetch1(r00.c00, v00.i00); + CALL fetch1(r00.c01, v00.i01); +END; +/ +SHOW PROCEDURE CODE fetch2 +/ +CREATE PROCEDURE close1(c00 SYS_REFCURSOR) +BEGIN + CLOSE c00; +END; +/ +SHOW PROCEDURE CODE close1 +/ +CREATE PROCEDURE close2(r00 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR)) +BEGIN + CALL close1(r00.c00); + CALL close1(r00.c01); +END; +/ +SHOW PROCEDURE CODE close2 +/ +CREATE PROCEDURE p1() +BEGIN + DECLARE v00 ROW(i00 INT, i01 iNT); + DECLARE r10 ROW(c00 SYS_REFCURSOR, c01 SYS_REFCURSOR); + DECLARE r11 TYPE OF r10; + CALL open2(r11); + CALL fetch2(r11, v00); + CALL close2(r11); + SELECT r11.c00, r11.c01, refs(0,3) AS refs, v00.i00, v00.i01; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +CALL p1; +DROP PROCEDURE p1; +DROP PROCEDURE open1; +DROP PROCEDURE open2; +DROP PROCEDURE fetch1; +DROP PROCEDURE fetch2; +DROP PROCEDURE close1; +DROP PROCEDURE close2; + + +--echo # +--echo # This declaration raises "Illegal parameter data type": +--echo # DECLARE r00 ROW TYPE OF static_cursor_with_refcursor_fields; +--echo # But only of the execution really comes into its block. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(declare_row_type_of BOOLEAN) +BEGIN + DECLARE v00 INT; + DECLARE v01 INT; + DECLARE c00 SYS_REFCURSOR; + DECLARE c01 SYS_REFCURSOR; + DECLARE s00 CURSOR FOR SELECT c00, c01; + OPEN c00 FOR SELECT 10; + OPEN c01 FOR SELECT 11; + IF declare_row_type_of THEN + BEGIN + DECLARE r00 ROW TYPE OF s00; + END; + END IF; + FETCH c00 INTO v00; + FETCH c01 INTO v01; + SELECT c00, c01, refs(0,4) AS refs, v00, v01; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +CALL p1(false); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p1(true); +DROP PROCEDURE p1; + + +--echo # +--echo # sql_mode=ORACLE: +--echo # static_cursor_with_refcursor_field%ROWTYPE +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT 1 AS c1, 2 AS c2 FROM DUAL; + DECLARE + r c0%ROWTYPE; -- This is considered as a table c0. + BEGIN + CREATE TABLE t1 AS SELECT r.c1 AS c1, r.c2 AS c2; + END; +END; +/ +DELIMITER ;/ +--error ER_NO_SUCH_TABLE +CALL p1; +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; + + +--echo # +--echo # Fetching from a SYS_REFCURSOR into a %ROWTYPE variable +--echo # + +SET sql_mode=ORACLE; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (1, 'b1'); +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + r0 t1%ROWTYPE; +BEGIN + OPEN c0 FOR SELECT * FROM t1; + FETCH c0 INTO r0; + DBMS_OUTPUT.PUT_LINE(r0.a ||' '|| r0.b); +END; +/ +DELIMITER ;/ +CALL p1; +DROP TABLE t1; +DROP PROCEDURE p1; +SET sql_mode=DEFAULT; + + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +DROP PACKAGE dbms_output; +--enable_result_log +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.result new file mode 100644 index 00000000000..0e3745699fb --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.result @@ -0,0 +1,131 @@ +SET NAMES utf8mb4; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# CAST(sys_refcursor_expr AS CHAR) is allowed +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; +OPEN c1 FOR SELECT 20; +SELECT CAST(c0 AS CHAR) AS col0, CAST(c1 AS CHAR) AS col1; +CREATE TABLE t1 AS +SELECT CAST(c0 AS CHAR) AS col0, CAST(c1 AS CHAR) AS col1; +SHOW CREATE TABLE t1; +SELECT * FROM t1; +DROP TABLE t1; +END; +$$ +col0 col1 +0 1 +Table Create Table +t1 CREATE TABLE `t1` ( + `col0` varchar(6) DEFAULT NULL, + `col1` varchar(6) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +col0 col1 +0 1 +# +# Type cast to other data types +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS SIGNED); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'cast_as_signed' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS UNSIGNED); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'cast_as_unsigned' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS DOUBLE); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'double_typecast' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS FLOAT); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'float_typecast' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS DECIMAL(10,0)); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'decimal_typecast' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS TIME); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'cast_as_time' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS DATE); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'cast_as_date' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CAST(c0 AS DATETIME); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'cast_as_datetime' +# +# Type cast from other data types +# +BEGIN NOT ATOMIC +DECLARE a INT; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a DOUBLE; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a FLOAT; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a DECIMAL; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a CHAR(30); +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a TIME; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a DATE; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +BEGIN NOT ATOMIC +DECLARE a DATETIME; +SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.test new file mode 100644 index 00000000000..fbbbac09815 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cast.test @@ -0,0 +1,179 @@ +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # CAST(sys_refcursor_expr AS CHAR) is allowed +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; + OPEN c1 FOR SELECT 20; + SELECT CAST(c0 AS CHAR) AS col0, CAST(c1 AS CHAR) AS col1; + CREATE TABLE t1 AS + SELECT CAST(c0 AS CHAR) AS col0, CAST(c1 AS CHAR) AS col1; + SHOW CREATE TABLE t1; + SELECT * FROM t1; + DROP TABLE t1; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Type cast to other data types +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS SIGNED); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS UNSIGNED); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS DOUBLE); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS FLOAT); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS DECIMAL(10,0)); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS TIME); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS DATE); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CAST(c0 AS DATETIME); +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Type cast from other data types +--echo # + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a INT; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a DOUBLE; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a FLOAT; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a DECIMAL; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a CHAR(30); + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a TIME; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a DATE; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_UNKNOWN_OPERATOR +BEGIN NOT ATOMIC + DECLARE a DATETIME; + SELECT CAST(a AS SYS_REFCURSOR); +END; +$$ +DELIMITER ;$$ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.result new file mode 100644 index 00000000000..b238166c4bd --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.result @@ -0,0 +1,40 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Using sys_refcursor operations on non-cursor variables +# +BEGIN NOT ATOMIC +DECLARE cur INT; +OPEN cur FOR SELECT 1 AS c FROM DUAL; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation 'OPEN' +BEGIN NOT ATOMIC +DECLARE cur, var INT; +FETCH cur INTO var; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation 'FETCH' +BEGIN NOT ATOMIC +DECLARE cur INT; +CLOSE cur; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation 'CLOSE' +BEGIN NOT ATOMIC +DECLARE a INT; +DECLARE c INT; +FETCH c INTO a; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation 'FETCH' +SET sql_mode=ORACLE; +DECLARE +cur INT; +BEGIN +SELECT cur%isopen; +END; +$$ +ERROR HY000: Illegal parameter data type int for operation '%cursor_attr' +SET sql_mode=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.test new file mode 100644 index 00000000000..088163887e4 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-cursor_op-bad.test @@ -0,0 +1,57 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Using sys_refcursor operations on non-cursor variables +--echo # + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE cur INT; + OPEN cur FOR SELECT 1 AS c FROM DUAL; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE cur, var INT; + FETCH cur INTO var; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE cur INT; + CLOSE cur; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE a INT; + DECLARE c INT; + FETCH c INTO a; +END; +$$ +DELIMITER ;$$ + +SET sql_mode=ORACLE; +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + cur INT; +BEGIN + SELECT cur%isopen; +END; +$$ +DELIMITER ;$$ +SET sql_mode=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.result new file mode 100644 index 00000000000..087edcf4b57 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.result @@ -0,0 +1,16 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE AGGREGATE FUNCTION f1(x int) RETURNS INT +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE mini INT DEFAULT 0; +DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN mini; +LOOP +FETCH GROUP NEXT ROW; +SET mini= mini+x; +FETCH GROUP NEXT ROW; +END LOOP; +END; +/ +ERROR HY000: 'sys_refcursor' is not allowed in this context diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.test new file mode 100644 index 00000000000..ef28d989857 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-custom_aggregate_functions.test @@ -0,0 +1,19 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +DELIMITER /; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE AGGREGATE FUNCTION f1(x int) RETURNS INT +BEGIN + DECLARE c SYS_REFCURSOR; + DECLARE mini INT DEFAULT 0; + DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN mini; + LOOP + FETCH GROUP NEXT ROW; + SET mini= mini+x; + FETCH GROUP NEXT ROW; + END LOOP; +END; +/ +DELIMITER ;/ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.result new file mode 100644 index 00000000000..f924e0bf59b --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.result @@ -0,0 +1,317 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# A small OPEN+FETCH+CLOSE example +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE a, b INT; +OPEN c FOR SELECT 1, 2; +FETCH c INTO a, b; +CLOSE c; +SELECT c, CURSOR_REF_COUNT(c) AS cnt, a, b; +END; +$$ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set c@0 NULL +1 set a@1 NULL +2 set b@2 NULL +3 copen STMT.cursor[c@0] +4 cfetch STMT.cursor[c@0] a@1 b@2 +5 cclose STMT.cursor[c@0] +6 stmt 0 "SELECT c, CURSOR_REF_COUNT(c) AS cnt,..." +7 destruct sys_refcursor c@0 +CALL p1; +c cnt a b +0 1 1 2 +DROP PROCEDURE p1; +# +# Nested blocks +# +SET sql_mode=ORACLE; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT 'c0'; +DECLARE +c1 SYS_REFCURSOR; +BEGIN +OPEN c1 FOR SELECT 'c1'; +DECLARE +c2 SYS_REFCURSOR; +BEGIN +OPEN c2 FOR SELECT 'c2'; +DECLARE +c3 SYS_REFCURSOR; +BEGIN +OPEN c3 FOR SELECT 'c3'; +SELECT c3, c3%ISOPEN AS op, CURSOR_REF_COUNT(c3) AS cnt; +END; +SELECT c2, c2%ISOPEN AS op, CURSOR_REF_COUNT(c2) AS cnt; +END; +SELECT c1, c1%ISOPEN AS op, CURSOR_REF_COUNT(c1) AS cnt; +END; +SELECT c0, c0%ISOPEN AS op, CURSOR_REF_COUNT(c0) AS cnt; +SELECT +CURSOR_REF_COUNT(0) AS cnt0, +CURSOR_REF_COUNT(1) AS cnt1, +CURSOR_REF_COUNT(2) AS cnt2, +CURSOR_REF_COUNT(3) AS cnt3; +END; +/ +CREATE PROCEDURE p2 AS +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT 'p2-c0'; +CALL p1; +END; +/ +CREATE PROCEDURE p3 AS +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT 'p3-c0'; +CALL p2; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set c0@0 NULL +1 copen STMT.cursor[c0@0] +2 set c1@1 NULL +3 copen STMT.cursor[c1@1] +4 set c2@2 NULL +5 copen STMT.cursor[c2@2] +6 set c3@3 NULL +7 copen STMT.cursor[c3@3] +8 stmt 0 "SELECT c3, c3%ISOPEN AS op, CURSOR_RE..." +9 destruct sys_refcursor c3@3 +10 stmt 0 "SELECT c2, c2%ISOPEN AS op, CURSOR_RE..." +11 destruct sys_refcursor c2@2 +12 stmt 0 "SELECT c1, c1%ISOPEN AS op, CURSOR_RE..." +13 destruct sys_refcursor c1@1 +14 stmt 0 "SELECT c0, c0%ISOPEN AS op, CURSOR_RE..." +15 stmt 0 "SELECT CURSOR_REF_COUNT(0) AS cnt0, C..." +16 destruct sys_refcursor c0@0 +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c0@0 NULL +1 copen STMT.cursor[c0@0] +2 stmt 88 "CALL p1" +3 destruct sys_refcursor c0@0 +SHOW PROCEDURE CODE p3; +Pos Instruction +0 set c0@0 NULL +1 copen STMT.cursor[c0@0] +2 stmt 88 "CALL p2" +3 destruct sys_refcursor c0@0 +CALL p1; +c3 op cnt +3 1 1 +c2 op cnt +2 1 1 +c1 op cnt +1 1 1 +c0 op cnt +0 1 1 +cnt0 cnt1 cnt2 cnt3 +1 0 0 0 +CALL p2; +c3 op cnt +4 1 1 +c2 op cnt +3 1 1 +c1 op cnt +2 1 1 +c0 op cnt +1 1 1 +cnt0 cnt1 cnt2 cnt3 +1 1 0 0 +CALL p3; +c3 op cnt +5 1 1 +c2 op cnt +4 1 1 +c1 op cnt +3 1 1 +c0 op cnt +2 1 1 +cnt0 cnt1 cnt2 cnt3 +1 1 1 0 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP PROCEDURE p3; +SET sql_mode=DEFAULT; +# +# Setting a cursor variable to itself does not change ref count +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0 = c0; -- neither directly +SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0 = COALESCE(c0); -- nor through an expression +SELECT 'p1-3' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; +stage c0 cnt_c0 cnt_0 +p1-1 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-2 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-3 0 1 1 +DROP PROCEDURE p1; +# +# Setting a cursor variable from not-NULL to NULL +# decrements ref count at its old position +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0 = NULL; +SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; +stage c0 cnt_c0 cnt_0 +p1-1 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-2 NULL NULL 0 +DROP PROCEDURE p1; +# +# Setting a cursor variable to a never opened cursor variable +# decrements ref count at its old position +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0 = c1; +SELECT 'p2-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; +stage c0 cnt_c0 cnt_0 +p1-1 0 1 1 +stage c0 cnt_c0 cnt_0 +p2-1 NULL NULL 0 +DROP PROCEDURE p1; +# +# Multiple OPEN of the same cursor variable reuses +# the cursor at the same offset. Ref count stays 1. +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c0 FOR SELECT 1; +SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c0 FOR SELECT 1; +SELECT 'p1-3' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c0 FOR SELECT 1; +SELECT 'p1-4' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c0 FOR SELECT 1; +SELECT 'p1-5' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c0 FOR SELECT 1; +SELECT 'p1-6' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0= NULL; +SELECT 'p1-7' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; +stage c0 cnt_c0 cnt_0 +p1-1 NULL NULL NULL +stage c0 cnt_c0 cnt_0 +p1-2 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-3 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-4 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-5 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-6 0 1 1 +stage c0 cnt_c0 cnt_0 +p1-7 NULL NULL 0 +DROP PROCEDURE p1; +# +# Multiple assignment to the same variable does not increase ref count. +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +BEGIN +DECLARE c1 SYS_REFCURSOR; +DECLARE c2 SYS_REFCURSOR; +SELECT 'stage 0' AS ``; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SELECT 'stage 1' AS ``; +OPEN c1 FOR SELECT 1 AS c FROM DUAL; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SELECT 'stage 2' AS ``; +SET c0 = c1; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SET c0= c1; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SET c0= c1; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SELECT 'stage 3' AS ``; +SET c2= c1; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SET c2= c1; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +SET c2= NULL; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; +END; +SELECT 'stage 4' AS ``; +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt_c0; +FETCH c0 INTO v; +SELECT CONCAT('v=',v); +SET c0=COALESCE(NULL); -- Reset c0 to NULL +SELECT c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; + +stage 0 +c0 cnt0 c1 cnt1 +NULL NULL NULL NULL + +stage 1 +c0 cnt0 c1 cnt1 +NULL NULL 0 1 + +stage 2 +c0 cnt0 c1 cnt1 +0 2 0 2 +c0 cnt0 c1 cnt1 +0 2 0 2 +c0 cnt0 c1 cnt1 +0 2 0 2 + +stage 3 +c0 cnt0 c1 cnt1 +0 3 0 3 +c0 cnt0 c1 cnt1 +0 3 0 3 +c0 cnt0 c1 cnt1 +0 2 0 2 + +stage 4 +c0 cnt_c0 +0 1 +CONCAT('v=',v) +v=1 +c0 cnt_c0 cnt_0 +NULL NULL 0 +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.test new file mode 100644 index 00000000000..b21c840e066 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-debug.test @@ -0,0 +1,228 @@ +--source include/have_debug.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # A small OPEN+FETCH+CLOSE example +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c SYS_REFCURSOR; + DECLARE a, b INT; + OPEN c FOR SELECT 1, 2; + FETCH c INTO a, b; + CLOSE c; + SELECT c, CURSOR_REF_COUNT(c) AS cnt, a, b; +END; +$$ +DELIMITER ;$$ +SHOW PROCEDURE CODE p1; +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Nested blocks +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT 'c0'; + DECLARE + c1 SYS_REFCURSOR; + BEGIN + OPEN c1 FOR SELECT 'c1'; + DECLARE + c2 SYS_REFCURSOR; + BEGIN + OPEN c2 FOR SELECT 'c2'; + DECLARE + c3 SYS_REFCURSOR; + BEGIN + OPEN c3 FOR SELECT 'c3'; + SELECT c3, c3%ISOPEN AS op, CURSOR_REF_COUNT(c3) AS cnt; + END; + SELECT c2, c2%ISOPEN AS op, CURSOR_REF_COUNT(c2) AS cnt; + END; + SELECT c1, c1%ISOPEN AS op, CURSOR_REF_COUNT(c1) AS cnt; + END; + SELECT c0, c0%ISOPEN AS op, CURSOR_REF_COUNT(c0) AS cnt; + SELECT + CURSOR_REF_COUNT(0) AS cnt0, + CURSOR_REF_COUNT(1) AS cnt1, + CURSOR_REF_COUNT(2) AS cnt2, + CURSOR_REF_COUNT(3) AS cnt3; +END; +/ +CREATE PROCEDURE p2 AS + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT 'p2-c0'; + CALL p1; +END; +/ +CREATE PROCEDURE p3 AS + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT 'p3-c0'; + CALL p2; +END; +/DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +SHOW PROCEDURE CODE p3; +CALL p1; +CALL p2; +CALL p3; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP PROCEDURE p3; +SET sql_mode=DEFAULT; + + +--echo # +--echo # Setting a cursor variable to itself does not change ref count +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0 = c0; -- neither directly + SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0 = COALESCE(c0); -- nor through an expression + SELECT 'p1-3' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Setting a cursor variable from not-NULL to NULL +--echo # decrements ref count at its old position +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0 = NULL; + SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Setting a cursor variable to a never opened cursor variable +--echo # decrements ref count at its old position +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0 = c1; + SELECT 'p2-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + +--echo # +--echo # Multiple OPEN of the same cursor variable reuses +--echo # the cursor at the same offset. Ref count stays 1. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + SELECT 'p1-1' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c0 FOR SELECT 1; + SELECT 'p1-2' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c0 FOR SELECT 1; + SELECT 'p1-3' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c0 FOR SELECT 1; + SELECT 'p1-4' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c0 FOR SELECT 1; + SELECT 'p1-5' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c0 FOR SELECT 1; + SELECT 'p1-6' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0= NULL; + SELECT 'p1-7' AS stage, c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + + +--echo # +--echo # Multiple assignment to the same variable does not increase ref count. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + BEGIN + DECLARE c1 SYS_REFCURSOR; + DECLARE c2 SYS_REFCURSOR; + + SELECT 'stage 0' AS ``; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + + SELECT 'stage 1' AS ``; + OPEN c1 FOR SELECT 1 AS c FROM DUAL; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + + SELECT 'stage 2' AS ``; + SET c0 = c1; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + SET c0= c1; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + SET c0= c1; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + + SELECT 'stage 3' AS ``; + SET c2= c1; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + SET c2= c1; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + SET c2= NULL; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt0, c1, CURSOR_REF_COUNT(c1) AS cnt1; + END; + + SELECT 'stage 4' AS ``; + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt_c0; + FETCH c0 INTO v; + SELECT CONCAT('v=',v); + SET c0=COALESCE(NULL); -- Reset c0 to NULL + SELECT c0, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.result new file mode 100644 index 00000000000..9afad7b7d57 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.result @@ -0,0 +1,51 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# DEFAULT clause in SYS_REFCURSOR declarations +# +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR DEFAULT NULL; +END; +$$ +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR DEFAULT NULL; +DECLARE c1 SYS_REFCURSOR DEFAULT c0; +END; +$$ +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR DEFAULT NULL; +OPEN c0 FOR SELECT 'c0-value'; +BEGIN +DECLARE c1 SYS_REFCURSOR DEFAULT c0; +DECLARE v VARCHAR(30); +FETCH c1 INTO v; +SELECT v; +END; +END; +$$ +v +c0-value +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR DEFAULT NULL; +DECLARE c1 SYS_REFCURSOR DEFAULT DEFAULT(c0); +END; +$$ +ERROR 42000: Incorrect column name 'c0' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR DEFAULT 0; +END; +$$ +ERROR HY000: Cannot cast 'int' as 'sys_refcursor' in assignment of `c` +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR DEFAULT IGNORE; +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 'IGNORE; +END' at line 2 +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR DEFAULT DEFAULT; +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 '; +END' at line 2 diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.test new file mode 100644 index 00000000000..3bceca0ff27 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-default.test @@ -0,0 +1,70 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # DEFAULT clause in SYS_REFCURSOR declarations +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR DEFAULT NULL; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR DEFAULT NULL; + DECLARE c1 SYS_REFCURSOR DEFAULT c0; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR DEFAULT NULL; + OPEN c0 FOR SELECT 'c0-value'; + BEGIN + DECLARE c1 SYS_REFCURSOR DEFAULT c0; + DECLARE v VARCHAR(30); + FETCH c1 INTO v; + SELECT v; + END; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_WRONG_COLUMN_NAME +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR DEFAULT NULL; + DECLARE c1 SYS_REFCURSOR DEFAULT DEFAULT(c0); +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR DEFAULT 0; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_PARSE_ERROR +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR DEFAULT IGNORE; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_PARSE_ERROR +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR DEFAULT DEFAULT; +END; +$$ +DELIMITER ;$$ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.result new file mode 100644 index 00000000000..ac5027ffc78 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.result @@ -0,0 +1,171 @@ +SET NAMES utf8mb4; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Helper routines +# +CREATE FUNCTION refs(first INT, last INT) RETURNS TEXT +BEGIN +DECLARE res TEXT DEFAULT '['; +FOR i IN first..last +DO +SET res= CONCAT(res, COALESCE(CURSOR_REF_COUNT(i), 'NULL')); +IF i < last THEN +SET res= CONCAT(res, '\t'); +END IF; +END FOR; +SET res= CONCAT(res, ']'); +RETURN res; +END; +/ +CREATE PROCEDURE show_cursor_and_refs(stage VARCHAR(32), +curs VARCHAR(32), +first INT, last INT) +BEGIN +SELECT stage, COALESCE(curs, 'NULL') AS curs, refs(first, last) AS refs; +END; +/ +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +IF @log <> '' THEN +SET @log= CONCAT(@log, '\n'); +END IF; +SET @log= CONCAT(@log, 'ff0-0','\t', +COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', +refs(0,5), '\n'); +OPEN c0 FOR SELECT 10; +SET @log= CONCAT(@log, 'ff0-1','\t', +COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', +refs(0,5)); +RETURN c0; +END; +/ +SHOW FUNCTION CODE ff0; +Pos Instruction +0 set c0@0 NULL +1 jump_if_not 3(3) @`log` <> '' +2 stmt 31 "SET @log= CONCAT(@log, '\n')" +3 stmt 31 "SET @log= CONCAT(@log, 'ff0-0','\t', ..." +4 copen STMT.cursor[c0@0] +5 stmt 31 "SET @log= CONCAT(@log, 'ff0-1','\t', ..." +6 freturn sys_refcursor c0@0 +CREATE FUNCTION ff1(c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN +IF @log <> '' THEN +SET @log= CONCAT(@log, '\n'); +END IF; +SET @log= CONCAT(@log, 'ff1-0','\t', +COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', +refs(0,5)); +RETURN c0; +END; +/ +SHOW FUNCTION CODE ff1; +Pos Instruction +0 jump_if_not 2(2) @`log` <> '' +1 stmt 31 "SET @log= CONCAT(@log, '\n')" +2 stmt 31 "SET @log= CONCAT(@log, 'ff1-0','\t', ..." +3 freturn sys_refcursor c0@0 +# +# DO statement cleans ref counters +# +CREATE PROCEDURE p2() +BEGIN +CALL show_cursor_and_refs('p2-0', '-', 0, 5); +SET @log= ''; +DO ff0(), ff0(); +SELECT @log; +CALL show_cursor_and_refs('p2-1', '-', 0, 5); +END; +/ +CALL p2; +stage curs refs +p2-0 - [NULL NULL NULL NULL NULL NULL] +@log +ff0-0 NULL [NULL NULL NULL NULL NULL NULL] +ff0-1 0 [1 NULL NULL NULL NULL NULL] +ff0-0 NULL [0 NULL NULL NULL NULL NULL] +ff0-1 0 [1 NULL NULL NULL NULL NULL] +stage curs refs +p2-1 - [0 NULL NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 5); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +CREATE PROCEDURE p2() +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); +OPEN p2c0 FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); +SET @log= ''; +DO ff1(p2c0), ff1(p2c0); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +CALL p2; +stage curs refs +p2-0 NULL [NULL NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 5); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +CREATE PROCEDURE p2() +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); +OPEN p2c0 FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); +DO COALESCE(p2c0), COALESCE(p2c0); +CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +CALL p2; +stage curs refs +p2-0 NULL [NULL NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 5); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# DO + EXECUTE IMMEDIATE +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +CALL show_cursor_and_refs('p1-0', CAST(c0 AS CHAR), 0, 1); +OPEN c0 FOR SELECT 10; +CALL show_cursor_and_refs('p1-1', CAST(c0 AS CHAR), 0, 1); +EXECUTE IMMEDIATE 'DO ?' USING c0; +CALL show_cursor_and_refs('p1-2', CAST(c0 AS CHAR), 0, 1); +EXECUTE IMMEDIATE 'DO ?' USING c0; +CALL show_cursor_and_refs('p1-3', CAST(c0 AS CHAR), 0, 1); +SET c0= NULL; +CALL show_cursor_and_refs('p1-0', CAST(c0 AS CHAR), 0, 1); +END; +/ +CALL p1; +p1-0 NULL [NULL NULL] +p1-1 0 [1 NULL] +p1-2 0 [1 NULL] +p1-3 0 [1 NULL] +p1-0 NULL [0 NULL] +DROP PROCEDURE p1; +DROP FUNCTION ff0; +DROP FUNCTION ff1; +DROP PROCEDURE show_cursor_and_refs; +DROP FUNCTION refs; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.test new file mode 100644 index 00000000000..4911a7ab98a --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-do-debug.test @@ -0,0 +1,96 @@ +--source include/have_debug.inc + +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--source type_sys_refcursor-helper_routines-debug-create.inc + +--echo # +--echo # DO statement cleans ref counters +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + CALL show_cursor_and_refs('p2-0', '-', 0, 5); + SET @log= ''; + DO ff0(), ff0(); + SELECT @log; + CALL show_cursor_and_refs('p2-1', '-', 0, 5); +END; +/ +DELIMITER ;/ +CALL p2; +CALL show_cursor_and_refs('/p2', '-', 0, 5); +DROP PROCEDURE p2; + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); + OPEN p2c0 FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); + SET @log= ''; + DO ff1(p2c0), ff1(p2c0); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +DELIMITER ;/ +CALL p2; +CALL show_cursor_and_refs('/p2', '-', 0, 5); +DROP PROCEDURE p2; + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); + OPEN p2c0 FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); + DO COALESCE(p2c0), COALESCE(p2c0); + CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +DELIMITER ;/ +CALL p2; +CALL show_cursor_and_refs('/p2', '-', 0, 5); +DROP PROCEDURE p2; + + +--echo # +--echo # DO + EXECUTE IMMEDIATE +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + CALL show_cursor_and_refs('p1-0', CAST(c0 AS CHAR), 0, 1); + + OPEN c0 FOR SELECT 10; + CALL show_cursor_and_refs('p1-1', CAST(c0 AS CHAR), 0, 1); + + EXECUTE IMMEDIATE 'DO ?' USING c0; + CALL show_cursor_and_refs('p1-2', CAST(c0 AS CHAR), 0, 1); + + EXECUTE IMMEDIATE 'DO ?' USING c0; + CALL show_cursor_and_refs('p1-3', CAST(c0 AS CHAR), 0, 1); + + SET c0= NULL; + CALL show_cursor_and_refs('p1-0', CAST(c0 AS CHAR), 0, 1); + +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p1; +--enable_column_names +DROP PROCEDURE p1; + + +--source type_sys_refcursor-helper_routines-debug-drop.inc diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.result new file mode 100644 index 00000000000..15c7959258f --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.result @@ -0,0 +1,63 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Dyadic operations +# +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE i INT; +SELECT c + i; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and int for operation '+' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE i INT; +SELECT c - i; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and int for operation '-' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE i INT; +SELECT c * i; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and int for operation '*' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE i INT; +SELECT c / i; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and int for operation '/' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +DECLARE i INT; +SELECT c % i; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and int for operation 'MOD' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +IF c = TRUE THEN +SELECT 'TRUE'; +ELSE +SELECT 'NOT TRUE'; +END IF; +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and boolean for operation '=' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT STR_TO_DATE(c, '%W, %M %e, %Y'); +END; +$$ +ERROR HY000: Illegal parameter data types sys_refcursor and varchar for operation 'str_to_date' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT DATE_FORMAT('2009-10-04 22:23:00', c); -- can_return_text +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'date_format' diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.test new file mode 100644 index 00000000000..0cf4e2936d4 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyadic_op.test @@ -0,0 +1,89 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Dyadic operations +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SELECT c + i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SELECT c - i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SELECT c * i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SELECT c / i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SELECT c % i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + IF c = TRUE THEN + SELECT 'TRUE'; + ELSE + SELECT 'NOT TRUE'; + END IF; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT STR_TO_DATE(c, '%W, %M %e, %Y'); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT DATE_FORMAT('2009-10-04 22:23:00', c); -- can_return_text +END; +$$ +DELIMITER ;$$ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.result new file mode 100644 index 00000000000..d87186a0174 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.result @@ -0,0 +1,19 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +OPEN c1 FOR SELECT 1; +RETURN c1; +END; +/ +SET @a=(SELECT COLUMN_CREATE(1, f1())); +SELECT COLUMN_GET(@a, 1 AS INT); +COLUMN_GET(@a, 1 AS INT) +1 +SELECT COLUMN_GET(@a, 1 AS SYS_REFCURSOR); +ERROR HY000: Operator does not exist: 'CAST(expr AS sys_refcursor)' +DROP FUNCTION f1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.test new file mode 100644 index 00000000000..a8fe458ba84 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-dyncol.test @@ -0,0 +1,27 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +DELIMITER /; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + OPEN c1 FOR SELECT 1; + RETURN c1; +END; +/ +DELIMITER ;/ + +# SYS_REFCURSOR works like an integer. +# QQ: Perhaps SYS_REFCURSOR should be disallowed as a COLUMN_CREATE() parameter. + +SET @a=(SELECT COLUMN_CREATE(1, f1())); +SELECT COLUMN_GET(@a, 1 AS INT); + +# COLUMN_GET(dyncol, 1 AS SYS_REFCURSOR) is not supported +--error ER_UNKNOWN_OPERATOR +SELECT COLUMN_GET(@a, 1 AS SYS_REFCURSOR); + +DROP FUNCTION f1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.result new file mode 100644 index 00000000000..1b8bbccc7fd --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.result @@ -0,0 +1,114 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# IF expr +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +IF c0 THEN +SELECT 'TRUE'; +ELSE +SELECT 'NOT TRUE'; +END IF; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'IF' +# +# CASE expr WHEN..THEN +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +CASE c0 WHEN TRUE THEN +SELECT 'TRUE'; +ELSE +SELECT 'NOT TRUE'; +END CASE; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'EXPRESSION CACHE (e.g. SUBSELECT)' +# +# CASE WHEN expr THEN +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +CASE +WHEN c0 THEN +SELECT 'TRUE'; +ELSE +SELECT 'NOT TRUE'; +END CASE; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'IF' +# +# UNTIL expr +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; +REPEAT +FETCH c0 INTO v; +UNTIL c0 +END REPEAT; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'IF' +# +# WHILE expr +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; +WHILE c0 DO +FETCH c0 INTO v; +END WHILE; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'IF' +# +# EXIT WHEN expr +# +SET sql_mode=ORACLE; +DECLARE +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR SELECT 1; +FOR i IN 0..5 +LOOP +EXIT WHEN c0; +END LOOP; +END; +/ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'IF' +SET sql_mode=DEFAULT; +# +# RAISE expr +# +SET sql_mode=ORACLE; +DECLARE +c0 SYS_REFCURSOR; +BEGIN +RAISE c0; +EXCEPTION +WHEN OTHERS THEN SELECT 'Got some exception'; +END; +/ +ERROR 42000: Undefined CONDITION: c0 +SET sql_mode=DEFAULT; +# +# EXCEPTION WHEN expr +# +SET sql_mode=ORACLE; +DECLARE +c0 SYS_REFCURSOR; +BEGIN +SELECT 1; +EXCEPTION +WHEN c0 THEN RETURN 'Got exception c0'; +END; +/ +ERROR 42000: Undefined CONDITION: c0 +SET sql_mode=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.test new file mode 100644 index 00000000000..63c1af0a16d --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-flow_control.test @@ -0,0 +1,153 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # IF expr +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + IF c0 THEN + SELECT 'TRUE'; + ELSE + SELECT 'NOT TRUE'; + END IF; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # CASE expr WHEN..THEN +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + CASE c0 WHEN TRUE THEN + SELECT 'TRUE'; + ELSE + SELECT 'NOT TRUE'; + END CASE; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # CASE WHEN expr THEN +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + CASE + WHEN c0 THEN + SELECT 'TRUE'; + ELSE + SELECT 'NOT TRUE'; + END CASE; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # UNTIL expr +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; + REPEAT + FETCH c0 INTO v; + UNTIL c0 + END REPEAT; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # WHILE expr +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; + WHILE c0 DO + FETCH c0 INTO v; + END WHILE; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # EXIT WHEN expr +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR SELECT 1; + FOR i IN 0..5 + LOOP + EXIT WHEN c0; + END LOOP; +END; +/ +DELIMITER ;/ +SET sql_mode=DEFAULT; + + +--echo # +--echo # RAISE expr +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +--error ER_SP_COND_MISMATCH +DECLARE + c0 SYS_REFCURSOR; +BEGIN + RAISE c0; +EXCEPTION + WHEN OTHERS THEN SELECT 'Got some exception'; +END; +/ +DELIMITER ;/ +SET sql_mode=DEFAULT; + + +--echo # +--echo # EXCEPTION WHEN expr +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +--error ER_SP_COND_MISMATCH +DECLARE + c0 SYS_REFCURSOR; +BEGIN + SELECT 1; +EXCEPTION + WHEN c0 THEN RETURN 'Got exception c0'; +END; +/ +DELIMITER ;/ +SET sql_mode=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.result new file mode 100644 index 00000000000..dc0efab22fe --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.result @@ -0,0 +1,657 @@ +CREATE FUNCTION f1cs1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +RETURN c0; +END; +$$ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Hybrid functions +# +# +# SET var=COALESCE() +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +DECLARE c2 SYS_REFCURSOR; +SELECT 'p1-0' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +OPEN c2 FOR SELECT 1; +SELECT 'p1-1' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0= COALESCE(c1, c2); +SELECT 'p1-2' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c2= NULL; +SELECT 'p1-3' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +SET c0= NULL; +SELECT 'p1-4' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p1; +stage c0 c1 c2 cnt_c0 cnt_0 +p1-0 NULL NULL NULL NULL NULL +stage c0 c1 c2 cnt_c0 cnt_0 +p1-1 NULL NULL 0 NULL 1 +stage c0 c1 c2 cnt_c0 cnt_0 +p1-2 0 NULL 0 2 2 +stage c0 c1 c2 cnt_c0 cnt_0 +p1-3 0 NULL NULL 1 1 +stage c0 c1 c2 cnt_c0 cnt_0 +p1-4 NULL NULL NULL NULL 0 +DROP PROCEDURE p1; +# +# SET var=CASE +# +SET sql_mode=ORACLE; +CREATE PROCEDURE p1(task VARCHAR(32)) AS +c0 SYS_REFCURSOR; +c1 SYS_REFCURSOR; +c2 SYS_REFCURSOR; +v INT; +BEGIN +IF task LIKE '%open_c0%' THEN +OPEN c0 FOR SELECT 1; +END IF; +SELECT 'p1-1' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; +OPEN c1 FOR SELECT 11 FROM DUAL; +SELECT 'p1-2' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; +c2:= CASE WHEN c0 IS NULL THEN c1 ELSE c0 END; +SELECT 'p1-3' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; +FETCH c2 INTO v; +SELECT v; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(32)) AS +BEGIN +SELECT 'p2-0' AS stage, CURSOR_REF_COUNT(0) AS cnt_0; +CALL p1(task); +SELECT 'p2-1' AS stage, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +CALL p2(''); +stage cnt_0 +p2-0 NULL +stage c0 c1 c2 cnt_0 cnt_1 +p1-1 NULL NULL NULL NULL NULL +stage c0 c1 c2 cnt_0 cnt_1 +p1-2 NULL 0 NULL 1 NULL +stage c0 c1 c2 cnt_0 cnt_1 +p1-3 NULL 0 0 2 NULL +v +11 +stage cnt_0 +p2-1 0 +CALL p2('open_c0'); +stage cnt_0 +p2-0 NULL +stage c0 c1 c2 cnt_0 cnt_1 +p1-1 0 NULL NULL 1 NULL +stage c0 c1 c2 cnt_0 cnt_1 +p1-2 0 1 NULL 1 1 +stage c0 c1 c2 cnt_0 cnt_1 +p1-3 0 1 0 2 1 +v +1 +stage cnt_0 +p2-1 0 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET sql_mode=DEFAULT; +# +# COALESCE in select list +# +SELECT +COALESCE(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +cl_f1 cnt_0 cnt_1 +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `cl_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1` from `test`.`seq_1_to_5` +EXECUTE IMMEDIATE 'SELECT +COALESCE(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5'; +cl_f1 cnt_0 cnt_1 +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5'; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `cl_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1` from `test`.`seq_1_to_5` +SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` +EXECUTE IMMEDIATE 'SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5'; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5'; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` +# +# COALESCE in WHERE +# +SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1 cnt_0 cnt_1 cnt_2 +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` where coalesce(`f1cs1`(),`f1cs1`()) is not null +SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3` from `test`.`seq_1_to_5` where coalesce(`f1cs1`(),`f1cs1`()) is not null +SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL AND +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +EXPLAIN EXTENDED SELECT +COALESCE(f1cs1(),f1cs1()) AS c_f1_0, +COALESCE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1(),f1cs1()) IS NOT NULL AND +COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,coalesce(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4` from `test`.`seq_1_to_5` where coalesce(`f1cs1`(),`f1cs1`()) is not null and coalesce(`f1cs1`(),`f1cs1`()) is not null +SELECT +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1()) IS NOT NULL; +cnt_0 cnt_1 +0 NULL +0 NULL +0 NULL +0 NULL +0 NULL +EXPLAIN EXTENDED SELECT +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5 +WHERE +COALESCE(f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1` from `test`.`seq_1_to_5` where coalesce(`f1cs1`()) is not null +# +# IFNULL in select list +# +SELECT +IFNULL(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +cl_f1 cnt_0 cnt_1 +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `cl_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1` from `test`.`seq_1_to_5` +EXECUTE IMMEDIATE 'SELECT +IFNULL(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5'; +cl_f1 cnt_0 cnt_1 +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +0 1 NULL +EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS cl_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5'; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `cl_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1` from `test`.`seq_1_to_5` +SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` +EXECUTE IMMEDIATE 'SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5'; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +0 1 1 1 NULL +EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5'; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` +# +# IFNULL in WHERE +# +SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +c_f1 cnt_0 cnt_1 cnt_2 +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` where ifnull(`f1cs1`(),`f1cs1`()) is not null +SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +0 1 1 1 NULL NULL +EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3` from `test`.`seq_1_to_5` where ifnull(`f1cs1`(),`f1cs1`()) is not null +SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL AND +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +EXPLAIN EXTENDED SELECT +IFNULL(f1cs1(),f1cs1()) AS c_f1_0, +IFNULL(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +IFNULL(f1cs1(),f1cs1()) IS NOT NULL AND +IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,ifnull(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4` from `test`.`seq_1_to_5` where ifnull(`f1cs1`(),`f1cs1`()) is not null and ifnull(`f1cs1`(),`f1cs1`()) is not null +# +# LAST_VALUE in select list +# +SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +c_f1 cnt_0 cnt_1 cnt_2 +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +0 1 NULL NULL +EXPLAIN EXTENDED SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select last_value(`f1cs1`(),`f1cs1`()) AS `c_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2` from `test`.`seq_1_to_5` +SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +0 1 1 1 NULL NULL NULL +EXPLAIN EXTENDED SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using index +Warnings: +Note 1003 select last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4` from `test`.`seq_1_to_5` +# +# LAST_VALUE in WHERE +# +SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 +1 1 1 NULL NULL NULL +1 1 1 NULL NULL NULL +1 1 1 NULL NULL NULL +1 1 1 NULL NULL NULL +1 1 1 NULL NULL NULL +EXPLAIN EXTENDED SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select last_value(`f1cs1`(),`f1cs1`()) AS `c_f1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4` from `test`.`seq_1_to_5` where last_value(`f1cs1`(),`f1cs1`()) is not null +SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4, +CURSOR_REF_COUNT(5) AS cnt_5, +CURSOR_REF_COUNT(6) AS cnt_6 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 cnt_5 cnt_6 +1 2 1 1 1 NULL NULL NULL NULL +1 2 1 1 1 NULL NULL NULL NULL +1 2 1 1 1 NULL NULL NULL NULL +1 2 1 1 1 NULL NULL NULL NULL +1 2 1 1 1 NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4, +CURSOR_REF_COUNT(5) AS cnt_5, +CURSOR_REF_COUNT(6) AS cnt_6 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4`,cursor_ref_count(5) AS `cnt_5`,cursor_ref_count(6) AS `cnt_6` from `test`.`seq_1_to_5` where last_value(`f1cs1`(),`f1cs1`()) is not null +SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4, +CURSOR_REF_COUNT(5) AS cnt_5, +CURSOR_REF_COUNT(6) AS cnt_6, +CURSOR_REF_COUNT(7) AS cnt_7, +CURSOR_REF_COUNT(8) AS cnt_8 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL AND +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +c_f1_0 c_f1_1 cnt_0 cnt_1 cnt_2 cnt_3 cnt_4 cnt_5 cnt_6 cnt_7 cnt_8 +2 3 1 1 1 1 NULL NULL NULL NULL NULL +2 3 1 1 1 1 NULL NULL NULL NULL NULL +2 3 1 1 1 1 NULL NULL NULL NULL NULL +2 3 1 1 1 1 NULL NULL NULL NULL NULL +2 3 1 1 1 1 NULL NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, +LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, +CURSOR_REF_COUNT(0) AS cnt_0, +CURSOR_REF_COUNT(1) AS cnt_1, +CURSOR_REF_COUNT(2) AS cnt_2, +CURSOR_REF_COUNT(3) AS cnt_3, +CURSOR_REF_COUNT(4) AS cnt_4, +CURSOR_REF_COUNT(5) AS cnt_5, +CURSOR_REF_COUNT(6) AS cnt_6, +CURSOR_REF_COUNT(7) AS cnt_7, +CURSOR_REF_COUNT(8) AS cnt_8 +FROM seq_1_to_5 +WHERE +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL AND +LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE seq_1_to_5 index NULL PRIMARY 8 NULL 5 100.00 Using where; Using index +Warnings: +Note 1003 select last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_0`,last_value(`f1cs1`(),`f1cs1`()) AS `c_f1_1`,cursor_ref_count(0) AS `cnt_0`,cursor_ref_count(1) AS `cnt_1`,cursor_ref_count(2) AS `cnt_2`,cursor_ref_count(3) AS `cnt_3`,cursor_ref_count(4) AS `cnt_4`,cursor_ref_count(5) AS `cnt_5`,cursor_ref_count(6) AS `cnt_6`,cursor_ref_count(7) AS `cnt_7`,cursor_ref_count(8) AS `cnt_8` from `test`.`seq_1_to_5` where last_value(`f1cs1`(),`f1cs1`()) is not null and last_value(`f1cs1`(),`f1cs1`()) is not null +DROP FUNCTION f1cs1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.test new file mode 100644 index 00000000000..013a6147754 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid-debug.test @@ -0,0 +1,328 @@ +--source include/have_debug.inc +--source include/have_sequence.inc + +DELIMITER $$; +CREATE FUNCTION f1cs1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + RETURN c0; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Hybrid functions +--echo # + +--echo # +--echo # SET var=COALESCE() +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + DECLARE c2 SYS_REFCURSOR; + SELECT 'p1-0' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + OPEN c2 FOR SELECT 1; + SELECT 'p1-1' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0= COALESCE(c1, c2); + SELECT 'p1-2' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c2= NULL; + SELECT 'p1-3' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; + SET c0= NULL; + SELECT 'p1-4' AS stage, c0, c1, c2, CURSOR_REF_COUNT(c0) AS cnt_c0, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # SET var=CASE +--echo # + +SET sql_mode=ORACLE; + +DELIMITER /; +CREATE PROCEDURE p1(task VARCHAR(32)) AS + c0 SYS_REFCURSOR; + c1 SYS_REFCURSOR; + c2 SYS_REFCURSOR; + v INT; +BEGIN + IF task LIKE '%open_c0%' THEN + OPEN c0 FOR SELECT 1; + END IF; + SELECT 'p1-1' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; + OPEN c1 FOR SELECT 11 FROM DUAL; + SELECT 'p1-2' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; + c2:= CASE WHEN c0 IS NULL THEN c1 ELSE c0 END; + SELECT 'p1-3' AS stage, c0, c1, c2, CURSOR_REF_COUNT(0) AS cnt_0, CURSOR_REF_COUNT(1) AS cnt_1; + FETCH c2 INTO v; + SELECT v; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(32)) AS +BEGIN + SELECT 'p2-0' AS stage, CURSOR_REF_COUNT(0) AS cnt_0; + CALL p1(task); + SELECT 'p2-1' AS stage, CURSOR_REF_COUNT(0) AS cnt_0; +END; +/ +DELIMITER ;/ +CALL p2(''); +CALL p2('open_c0'); +DROP PROCEDURE p1; +DROP PROCEDURE p2; + +SET sql_mode=DEFAULT; + + +--echo # +--echo # COALESCE in select list +--echo # + +let $select= SELECT + COALESCE(f1cs1(),f1cs1()) AS cl_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; +eval EXECUTE IMMEDIATE '$select'; +eval EXECUTE IMMEDIATE 'EXPLAIN EXTENDED $select'; + +let $select= SELECT + COALESCE(f1cs1(),f1cs1()) AS c_f1_0, + COALESCE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; +eval EXECUTE IMMEDIATE '$select'; +eval EXECUTE IMMEDIATE 'EXPLAIN EXTENDED $select'; + + +--echo # +--echo # COALESCE in WHERE +--echo # + +let $select= SELECT + COALESCE(f1cs1(),f1cs1()) AS c_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE + COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + COALESCE(f1cs1(),f1cs1()) AS c_f1_0, + COALESCE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE + COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + COALESCE(f1cs1(),f1cs1()) AS c_f1_0, + COALESCE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE + COALESCE(f1cs1(),f1cs1()) IS NOT NULL AND + COALESCE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5 +WHERE + COALESCE(f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +--echo # +--echo # IFNULL in select list +--echo # + +let $select= SELECT + IFNULL(f1cs1(),f1cs1()) AS cl_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; +eval EXECUTE IMMEDIATE '$select'; +eval EXECUTE IMMEDIATE 'EXPLAIN EXTENDED $select'; + +let $select= SELECT + IFNULL(f1cs1(),f1cs1()) AS c_f1_0, + IFNULL(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; +eval EXECUTE IMMEDIATE '$select'; +eval EXECUTE IMMEDIATE 'EXPLAIN EXTENDED $select'; + + +--echo # +--echo # IFNULL in WHERE +--echo # + +let $select= SELECT + IFNULL(f1cs1(),f1cs1()) AS c_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5 +WHERE + IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + IFNULL(f1cs1(),f1cs1()) AS c_f1_0, + IFNULL(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3 +FROM seq_1_to_5 +WHERE + IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + IFNULL(f1cs1(),f1cs1()) AS c_f1_0, + IFNULL(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE + IFNULL(f1cs1(),f1cs1()) IS NOT NULL AND + IFNULL(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + +--echo # +--echo # LAST_VALUE in select list +--echo # + +let $select= SELECT + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; + +let $select= SELECT + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5; +eval $select; +eval EXPLAIN EXTENDED $select; + + +--echo # +--echo # LAST_VALUE in WHERE +--echo # + +let $select= SELECT + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4 +FROM seq_1_to_5 +WHERE + LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4, + CURSOR_REF_COUNT(5) AS cnt_5, + CURSOR_REF_COUNT(6) AS cnt_6 +FROM seq_1_to_5 +WHERE + LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + + +let $select= SELECT + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_0, + LAST_VALUE(f1cs1(),f1cs1()) AS c_f1_1, + CURSOR_REF_COUNT(0) AS cnt_0, + CURSOR_REF_COUNT(1) AS cnt_1, + CURSOR_REF_COUNT(2) AS cnt_2, + CURSOR_REF_COUNT(3) AS cnt_3, + CURSOR_REF_COUNT(4) AS cnt_4, + CURSOR_REF_COUNT(5) AS cnt_5, + CURSOR_REF_COUNT(6) AS cnt_6, + CURSOR_REF_COUNT(7) AS cnt_7, + CURSOR_REF_COUNT(8) AS cnt_8 +FROM seq_1_to_5 +WHERE + LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL AND + LAST_VALUE(f1cs1(),f1cs1()) IS NOT NULL; +eval $select; +eval EXPLAIN EXTENDED $select; + +# +# Cleanup +# + +DROP FUNCTION f1cs1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.result new file mode 100644 index 00000000000..c990a31ae30 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.result @@ -0,0 +1,45 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Hybrid functions +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +SELECT LEAST(c0, c1); +END; +/ +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'least' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +SELECT GREATEST(c0, c1); +END; +/ +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'greatest' +# +# This test covers Item::val_ref_from_item() and its DBUG_ASSERT: +# A SYS_REFCURSOR expressions is allowed to be mixed only +# with another SYS_REFCURSOR expression, or with explicit NULL. +# +CREATE FUNCTION f1(switch BOOLEAN, f1c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN +RETURN IF(switch, NULL, f1c0); +END; +/ +CREATE PROCEDURE p1() +BEGIN +DECLARE p1c0 SYS_REFCURSOR; +SELECT f1(FALSE, p1c0) AS c1, f1(TRUE, p1c0) AS c2; +OPEN p1c0 FOR SELECT 1; +SELECT f1(FALSE, p1c0) AS c1, f1(TRUE, p1c0) AS c2; +END; +/ +CALL p1; +c1 c2 +NULL NULL +c1 c2 +0 NULL +DROP FUNCTION f1; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.test new file mode 100644 index 00000000000..bb38e5c7ed0 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_hybrid.test @@ -0,0 +1,55 @@ +--source include/have_debug.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Hybrid functions +--echo # + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + SELECT LEAST(c0, c1); +END; +/ +DELIMITER ;/ + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + SELECT GREATEST(c0, c1); +END; +/ +DELIMITER ;/ + + +--echo # +--echo # This test covers Item::val_ref_from_item() and its DBUG_ASSERT: +--echo # A SYS_REFCURSOR expressions is allowed to be mixed only +--echo # with another SYS_REFCURSOR expression, or with explicit NULL. +--echo # + +DELIMITER /; +CREATE FUNCTION f1(switch BOOLEAN, f1c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN + RETURN IF(switch, NULL, f1c0); +END; +/ +CREATE PROCEDURE p1() +BEGIN + DECLARE p1c0 SYS_REFCURSOR; + SELECT f1(FALSE, p1c0) AS c1, f1(TRUE, p1c0) AS c2; + OPEN p1c0 FOR SELECT 1; + SELECT f1(FALSE, p1c0) AS c1, f1(TRUE, p1c0) AS c2; +END; +/ +DELIMITER ;/ +CALL p1; +DROP FUNCTION f1; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.result new file mode 100644 index 00000000000..0e56535d8a7 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.result @@ -0,0 +1,40 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# String functions +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CONCAT(c0); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'concat' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT CONCAT_WS(',', c0, c0); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'concat_ws' +SET sql_mode=ORACLE; +DECLARE +c0 SYS_REFCURSOR; +BEGIN +SELECT c0 || ' ' || ' ' || c0; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'concat' +SET sql_mode=DEFAULT; +# +# String sum functions +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT GROUP_CONCAT(c0) FROM seq_1_to_4; +END; +/ +CALL p1; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'group_concat(' +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.test new file mode 100644 index 00000000000..40d355817fc --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-func_str.test @@ -0,0 +1,65 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # String functions +--echo # + +# CONCAT + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CONCAT(c0); +END; +$$ +DELIMITER ;$$ + + +# CONCAT_WS + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT CONCAT_WS(',', c0, c0); +END; +$$ +DELIMITER ;$$ + + +# Concatenation operator || + +SET sql_mode=ORACLE; +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + c0 SYS_REFCURSOR; +BEGIN + SELECT c0 || ' ' || ' ' || c0; +END; +$$ +DELIMITER ;$$ +SET sql_mode=DEFAULT; + + +--echo # +--echo # String sum functions +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT GROUP_CONCAT(c0) FROM seq_1_to_4; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-create.inc b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-create.inc new file mode 100644 index 00000000000..41af2a6e8bb --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-create.inc @@ -0,0 +1,74 @@ +--echo # +--echo # Helper routines +--echo # + +# Return a string with ref counters for cursors in the given range, +# with format '[ cnt0 cnt1 cnt2 cnt 3 cnt4 cnt5 ]', for example: +# '[ 2 1 NULL NULL NULL]' + +DELIMITER /; +CREATE FUNCTION refs(first INT, last INT) RETURNS TEXT +BEGIN + DECLARE res TEXT DEFAULT '['; + FOR i IN first..last + DO + SET res= CONCAT(res, COALESCE(CURSOR_REF_COUNT(i), 'NULL')); + IF i < last THEN + SET res= CONCAT(res, '\t'); + END IF; + END FOR; + SET res= CONCAT(res, ']'); + RETURN res; +END; +/ +DELIMITER ;/ + +# Show a cursor and ref counters in the given range +DELIMITER /; +CREATE PROCEDURE show_cursor_and_refs(stage VARCHAR(32), + curs VARCHAR(32), + first INT, last INT) +BEGIN + SELECT stage, COALESCE(curs, 'NULL') AS curs, refs(first, last) AS refs; +END; +/ +DELIMITER ;/ + + +# Returns a new open cursor with logging +DELIMITER /; +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + IF @log <> '' THEN + SET @log= CONCAT(@log, '\n'); + END IF; + SET @log= CONCAT(@log, 'ff0-0','\t', + COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', + refs(0,5), '\n'); + OPEN c0 FOR SELECT 10; + SET @log= CONCAT(@log, 'ff0-1','\t', + COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', + refs(0,5)); + RETURN c0; +END; +/ +DELIMITER ;/ +SHOW FUNCTION CODE ff0; + + +# Returns an existing cursor passed to the IN parameter, with logging +DELIMITER /; +CREATE FUNCTION ff1(c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN + IF @log <> '' THEN + SET @log= CONCAT(@log, '\n'); + END IF; + SET @log= CONCAT(@log, 'ff1-0','\t', + COALESCE(CAST(c0 AS CHAR),'NULL'), '\t', + refs(0,5)); + RETURN c0; +END; +/ +DELIMITER ;/ +SHOW FUNCTION CODE ff1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-drop.inc b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-drop.inc new file mode 100644 index 00000000000..821ccb9685b --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-helper_routines-debug-drop.inc @@ -0,0 +1,7 @@ +# +# Drop helper routines +# +DROP FUNCTION ff0; +DROP FUNCTION ff1; +DROP PROCEDURE show_cursor_and_refs; +DROP FUNCTION refs; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.result new file mode 100644 index 00000000000..7dfcd9572b1 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.result @@ -0,0 +1,197 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +SET default_storage_engine=InnoDB; +# +# OPEN sys_ref_cursor FOR SELECT ... FOR UPDATE +# +SELECT @@autocommit; +@@autocommit +1 +SELECT @@transaction_isolation; +@@transaction_isolation +REPEATABLE-READ +SELECT @@default_storage_engine; +@@default_storage_engine +InnoDB +CREATE TABLE t1 ( +id INT PRIMARY KEY, +worker VARCHAR(32) DEFAULT '', +ts TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `worker` varchar(32) DEFAULT '', + `ts` timestamp(6) NOT NULL DEFAULT current_timestamp(6), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE TABLE t2 (a VARCHAR(128)) ENGINE=MEMORY; +CREATE PROCEDURE p2(for_update BOOL, do_fetch BOOL) +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE v INT; +START TRANSACTION; +IF for_update THEN +OPEN c FOR SELECT id FROM t1 WHERE id=0 FOR UPDATE; +ELSE +OPEN c FOR SELECT id FROM t1 WHERE id=0; +END IF; +IF do_fetch THEN +FETCH c INTO v; +END IF; +-- signal to the other thread that OPEN happened +INSERT INTO t2 VALUES +('The exact value does not matter in t2. Only COUNT(*) matters'); +IF NOT for_update THEN +-- If FOR UPDATE is not specified then other thread is not locked +-- Let the other thread finish INSERT. +DO SLEEP(30); -- This query will be killed by the other thread +END IF; +INSERT INTO t1 VALUES (12, 'p2', SYSDATE(6)); +CLOSE c; +COMMIT; +END; +/ +CREATE PROCEDURE p1(for_update BOOL) +BEGIN +DECLARE v INT; +DECLARE session_id INT; +START TRANSACTION; +IF for_update THEN +SET v=(SELECT id FROM t1 WHERE id=0 FOR UPDATE); +ELSE +SET v=(SELECT id FROM t1 WHERE id=0); +END IF; +INSERT INTO t1 VALUES (11, 'p1', SYSDATE(6)); +COMMIT; +-- Check if the other thread is executing the SLEEP +-- statement and kill it to avoid waiting +SET session_id= (SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE INFO LIKE '%SLEEP(%)'); +SELECT CONCAT('p1: session_id IS NOT NULL:', session_id IS NOT NULL) AS msg; +IF session_id IS NOT NULL +THEN +KILL QUERY session_id; +END IF; +END; +/ +------------ for_update=0 do_fetch=0 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(0, 0); +connection default; +CALL p1(0); +msg +p1: session_id IS NOT NULL:1 +connection con2; +disconnect con2; +connection default; +# Without FOR UPDATE: p1 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +11 p1 +12 p2 +------------ for_update=0 do_fetch=1 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(0, 1); +connection default; +CALL p1(0); +msg +p1: session_id IS NOT NULL:1 +connection con2; +disconnect con2; +connection default; +# Without FOR UPDATE: p1 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +11 p1 +12 p2 +------------ for_update=1 do_fetch=0 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(1, 0); +connection default; +CALL p1(1); +msg +p1: session_id IS NOT NULL:0 +connection con2; +disconnect con2; +connection default; +# With FOR UPDATE: p2 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +12 p2 +11 p1 +------------ for_update=1 do_fetch=1 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(1, 1); +connection default; +CALL p1(1); +msg +p1: session_id IS NOT NULL:0 +connection con2; +disconnect con2; +connection default; +# With FOR UPDATE: p2 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +12 p2 +11 p1 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +DROP TABLE t2; +SET default_storage_engine=DEFAULT; +# +# MDEV-36377 Assertion `thd->lex == sp_instr_lex' failed in LEX *sp_lex_instr::parse_expr(THD *, sp_head *, LEX *) +# +CREATE TABLE t (a INT) ENGINE=INNODB; +CREATE PROCEDURE p (OUT c sys_refcursor) +BEGIN +OPEN c FOR SELECT a FROM t ; +END; +$ +CREATE TEMPORARY TABLE t (c INT) ENGINE=INNODB; +SET GLOBAL innodb_file_per_table=0; +Warnings: +Warning 1287 '@@innodb_file_per_table' is deprecated and will be removed in a future release +SET innodb_compression_default=ON; +CALL p (@a); +ERROR 42S22: Unknown column 'a' in 'SELECT' +CREATE OR REPLACE TEMPORARY TABLE t (c INT) ENGINE=INNODB; +ERROR HY000: Can't create table `test`.`t` (errno: 140 "Wrong create options") +PREPARE s FROM 'CALL p(?)'; +EXECUTE s USING @a; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'SET user_variable' +CALL p(@a); +CALL p; +ERROR 42000: Incorrect number of arguments for PROCEDURE test.p; expected 1, got 0 +DROP PROCEDURE p; +DROP TABLE t; +SET GLOBAL innodb_file_per_table=DEFAULT; +Warnings: +Warning 1287 '@@innodb_file_per_table' is deprecated and will be removed in a future release diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.test new file mode 100644 index 00000000000..1d3c62d6aaf --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-innodb.test @@ -0,0 +1,40 @@ +--source include/have_innodb.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +SET default_storage_engine=InnoDB; +--source type_sys_refcursor-select_for_update.inc +SET default_storage_engine=DEFAULT; + +--echo # +--echo # MDEV-36377 Assertion `thd->lex == sp_instr_lex' failed in LEX *sp_lex_instr::parse_expr(THD *, sp_head *, LEX *) +--echo # + +--source include/have_innodb.inc +CREATE TABLE t (a INT) ENGINE=INNODB; +--delimiter $ +CREATE PROCEDURE p (OUT c sys_refcursor) +BEGIN + OPEN c FOR SELECT a FROM t ; +END; +$ +--delimiter ; +CREATE TEMPORARY TABLE t (c INT) ENGINE=INNODB; +SET GLOBAL innodb_file_per_table=0; +SET innodb_compression_default=ON; +--error ER_BAD_FIELD_ERROR +CALL p (@a); + +--error ER_CANT_CREATE_TABLE +CREATE OR REPLACE TEMPORARY TABLE t (c INT) ENGINE=INNODB; +PREPARE s FROM 'CALL p(?)'; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +EXECUTE s USING @a; +CALL p(@a); +--error ER_SP_WRONG_NO_OF_ARGS +CALL p; +DROP PROCEDURE p; +DROP TABLE t; +SET GLOBAL innodb_file_per_table=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.result new file mode 100644 index 00000000000..cceff7b203d --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.result @@ -0,0 +1,168 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +SET default_storage_engine=MyISAM; +# +# OPEN sys_ref_cursor FOR SELECT ... FOR UPDATE +# +SELECT @@autocommit; +@@autocommit +1 +SELECT @@transaction_isolation; +@@transaction_isolation +REPEATABLE-READ +SELECT @@default_storage_engine; +@@default_storage_engine +MyISAM +CREATE TABLE t1 ( +id INT PRIMARY KEY, +worker VARCHAR(32) DEFAULT '', +ts TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `worker` varchar(32) DEFAULT '', + `ts` timestamp(6) NOT NULL DEFAULT current_timestamp(6), + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE TABLE t2 (a VARCHAR(128)) ENGINE=MEMORY; +CREATE PROCEDURE p2(for_update BOOL, do_fetch BOOL) +BEGIN +DECLARE c SYS_REFCURSOR; +DECLARE v INT; +START TRANSACTION; +IF for_update THEN +OPEN c FOR SELECT id FROM t1 WHERE id=0 FOR UPDATE; +ELSE +OPEN c FOR SELECT id FROM t1 WHERE id=0; +END IF; +IF do_fetch THEN +FETCH c INTO v; +END IF; +-- signal to the other thread that OPEN happened +INSERT INTO t2 VALUES +('The exact value does not matter in t2. Only COUNT(*) matters'); +IF NOT for_update THEN +-- If FOR UPDATE is not specified then other thread is not locked +-- Let the other thread finish INSERT. +DO SLEEP(30); -- This query will be killed by the other thread +END IF; +INSERT INTO t1 VALUES (12, 'p2', SYSDATE(6)); +CLOSE c; +COMMIT; +END; +/ +CREATE PROCEDURE p1(for_update BOOL) +BEGIN +DECLARE v INT; +DECLARE session_id INT; +START TRANSACTION; +IF for_update THEN +SET v=(SELECT id FROM t1 WHERE id=0 FOR UPDATE); +ELSE +SET v=(SELECT id FROM t1 WHERE id=0); +END IF; +INSERT INTO t1 VALUES (11, 'p1', SYSDATE(6)); +COMMIT; +-- Check if the other thread is executing the SLEEP +-- statement and kill it to avoid waiting +SET session_id= (SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE INFO LIKE '%SLEEP(%)'); +SELECT CONCAT('p1: session_id IS NOT NULL:', session_id IS NOT NULL) AS msg; +IF session_id IS NOT NULL +THEN +KILL QUERY session_id; +END IF; +END; +/ +------------ for_update=0 do_fetch=0 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(0, 0); +connection default; +CALL p1(0); +msg +p1: session_id IS NOT NULL:1 +connection con2; +disconnect con2; +connection default; +# Without FOR UPDATE: p1 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +11 p1 +12 p2 +------------ for_update=0 do_fetch=1 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(0, 1); +connection default; +CALL p1(0); +msg +p1: session_id IS NOT NULL:1 +connection con2; +disconnect con2; +connection default; +# Without FOR UPDATE: p1 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +11 p1 +12 p2 +------------ for_update=1 do_fetch=0 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(1, 0); +connection default; +CALL p1(1); +msg +p1: session_id IS NOT NULL:0 +connection con2; +disconnect con2; +connection default; +# With FOR UPDATE: p2 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +12 p2 +11 p1 +------------ for_update=1 do_fetch=1 +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +BEGIN; +INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); +COMMIT; +connect con2,localhost,root; +connection con2; +CALL p2(1, 1); +connection default; +CALL p1(1); +msg +p1: session_id IS NOT NULL:0 +connection con2; +disconnect con2; +connection default; +# With FOR UPDATE: p2 inserted first +SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; +id worker +12 p2 +11 p1 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +DROP TABLE t2; +SET default_storage_engine=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.test new file mode 100644 index 00000000000..6d4819ca532 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-myisam.test @@ -0,0 +1,7 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +SET default_storage_engine=MyISAM; +--source type_sys_refcursor-select_for_update.inc +SET default_storage_engine=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.result new file mode 100644 index 00000000000..cc1f398ebd9 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.result @@ -0,0 +1,52 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# SYS_REFCURSOR in packages +# +CREATE PACKAGE pkg +PROCEDURE p1(); +PROCEDURE show_body_items(); +END; +$$ +CREATE PACKAGE BODY pkg +DECLARE a, b INT; +PROCEDURE p1() +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT 1, 2; +FETCH c INTO a, b; +CLOSE c; +END; +PROCEDURE show_body_items() +BEGIN +SELECT a,b; +END; +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT 1, 2; +FETCH c INTO a, b; +CLOSE c; +END; +END; +$$ +SHOW PROCEDURE CODE pkg.p1; +Pos Instruction +0 set c@0 NULL +1 copen STMT.cursor[c@0] +2 cfetch STMT.cursor[c@0] PACKAGE_BODY.a@0 PACKAGE_BODY.b@1 +3 cclose STMT.cursor[c@0] +4 destruct sys_refcursor c@0 +SHOW PACKAGE BODY CODE pkg; +Pos Instruction +0 set a@0 NULL +1 set b@1 NULL +2 set c@2 NULL +3 copen STMT.cursor[c@2] +4 cfetch STMT.cursor[c@2] a@0 b@1 +5 cclose STMT.cursor[c@2] +6 destruct sys_refcursor c@2 +CALL pkg.show_body_items(); +a b +1 2 +DROP PACKAGE pkg; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.test new file mode 100644 index 00000000000..f48f82a1288 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-package-debug.test @@ -0,0 +1,42 @@ +--source include/have_debug.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # SYS_REFCURSOR in packages +--echo # + +DELIMITER $$; +CREATE PACKAGE pkg + PROCEDURE p1(); + PROCEDURE show_body_items(); +END; +$$ +CREATE PACKAGE BODY pkg + DECLARE a, b INT; + PROCEDURE p1() + BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1, 2; + FETCH c INTO a, b; + CLOSE c; + END; + PROCEDURE show_body_items() + BEGIN + SELECT a,b; + END; +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1, 2; + FETCH c INTO a, b; + CLOSE c; + END; +END; +$$ +DELIMITER ;$$ +SHOW PROCEDURE CODE pkg.p1; +SHOW PACKAGE BODY CODE pkg; +CALL pkg.show_body_items(); +DROP PACKAGE pkg; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.result new file mode 100644 index 00000000000..47b14af413b --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.result @@ -0,0 +1,19 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE PROCEDURE p1() +BEGIN +CREATE TABLE t1 ( +num TINYINT(1) NOT NULL +) +PARTITION BY LIST (num) +( +PARTITION p0 VALUES IN (CURSOR_REC_COUNT(1)), +PARTITION px DEFAULT +); +END +/ +ERROR 42000: Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed near '), +PARTITION px DEFAULT +); +END' at line 8 diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.test new file mode 100644 index 00000000000..2a31668993f --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-partition-debug.test @@ -0,0 +1,24 @@ +--source include/have_partition.inc +--source include/have_debug.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +# CURSOR_REC_COUNT() is not deterministic + +DELIMITER /; +--error ER_PARSE_ERROR +CREATE PROCEDURE p1() +BEGIN + CREATE TABLE t1 ( + num TINYINT(1) NOT NULL + ) + PARTITION BY LIST (num) + ( + PARTITION p0 VALUES IN (CURSOR_REC_COUNT(1)), + PARTITION px DEFAULT + ); +END +/ +DELIMITER ;/ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.result new file mode 100644 index 00000000000..11acf7e655d --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.result @@ -0,0 +1,164 @@ +# +# Helper routines +# +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# IS [NOT] NULL +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +c0, +c0 IS NULL AS nl_0, +c0 IS NOT NULL AS nnl_0, +c1, +c1 IS NULL AS nl_1, +c1 IS NOT NULL AS nnl_1, +refs(0,3) AS refs +FROM seq_1_to_5; +END; +/ +CALL p1; +c0 nl_0 nnl_0 c1 nl_1 nnl_1 refs +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +EXECUTE IMMEDIATE 'SELECT + ? AS c0, + ? IS NULL AS nl_0, + ? IS NOT NULL AS nnl_0, + ? AS c1, + ? IS NULL AS nl_1, + ? IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5' USING c0, c0, c0, c1, c1, c1; +END; +/ +CALL p1; +c0 nl_0 nnl_0 c1 nl_1 nnl_1 refs +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +COALESCE(c0) AS c_c0, +COALESCE(c0) IS NULL AS nl_0, +COALESCE(c0) IS NOT NULL AS nnl_0, +COALESCE(c1) AS c_c1, +COALESCE(c1) IS NULL AS nl_1, +COALESCE(c1) IS NOT NULL AS nnl_1, +refs(0,3) AS refs +FROM seq_1_to_5; +END; +/ +CALL p1; +c_c0 nl_0 nnl_0 c_c1 nl_1 nnl_1 refs +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +0 0 1 NULL 1 0 [1 NULL NULL NULL] +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +EXECUTE IMMEDIATE 'SELECT + COALESCE(?) AS c_c0, + COALESCE(?) IS NULL AS nl_0, + COALESCE(?) IS NOT NULL AS nnl_0, + COALESCE(?) AS c_c1, + COALESCE(?) IS NULL AS nl_1, + COALESCE(?) IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5' USING c0, c0, c0, c1, c1, c1; +END; +/ +CALL p1; +c_c0 nl_0 nnl_0 c_c1 nl_1 nnl_1 refs +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +0 0 1 NULL 1 0 [4 NULL NULL NULL] +DROP PROCEDURE p1; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE f1c0 SYS_REFCURSOR; +OPEN f1c0 FOR SELECT 1; +RETURN f1c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE f2c0 SYS_REFCURSOR; +RETURN f2c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +f1() IS NULL AS nl_0, +f1() IS NOT NULL AS nnl_0, +f2() IS NULL AS nl_1, +f2() IS NOT NULL AS nnl_1, +refs(0,3) AS refs +FROM seq_1_to_5; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +COALESCE(f1()) IS NULL AS nl_0, +COALESCE(f1()) IS NOT NULL AS nnl_0, +COALESCE(f2()) IS NULL AS nl_1, +COALESCE(f2()) IS NOT NULL AS nnl_1, +refs(0,3) AS refs +FROM seq_1_to_5; +END; +/ +CALL p1; +nl_0 nnl_0 nl_1 nnl_1 refs +0 1 1 0 [1 1 1 NULL] +0 1 1 0 [1 1 1 NULL] +0 1 1 0 [1 1 1 NULL] +0 1 1 0 [1 1 1 NULL] +0 1 1 0 [1 1 1 NULL] +CALL p2; +nl_0 nnl_0 nl_1 nnl_1 refs +0 1 1 0 [1 0 NULL NULL] +0 1 1 0 [1 0 NULL NULL] +0 1 1 0 [1 0 NULL NULL] +0 1 1 0 [1 0 NULL NULL] +0 1 1 0 [1 0 NULL NULL] +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP FUNCTION f1; +DROP FUNCTION f2; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.test new file mode 100644 index 00000000000..30b5306bdb5 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates-debug.test @@ -0,0 +1,163 @@ +--source include/have_debug.inc +--source include/have_sequence.inc + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--enable_result_log +--enable_query_log + + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # IS [NOT] NULL +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + c0, + c0 IS NULL AS nl_0, + c0 IS NOT NULL AS nnl_0, + c1, + c1 IS NULL AS nl_1, + c1 IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + EXECUTE IMMEDIATE 'SELECT + ? AS c0, + ? IS NULL AS nl_0, + ? IS NOT NULL AS nnl_0, + ? AS c1, + ? IS NULL AS nl_1, + ? IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5' USING c0, c0, c0, c1, c1, c1; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + COALESCE(c0) AS c_c0, + COALESCE(c0) IS NULL AS nl_0, + COALESCE(c0) IS NOT NULL AS nnl_0, + COALESCE(c1) AS c_c1, + COALESCE(c1) IS NULL AS nl_1, + COALESCE(c1) IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + EXECUTE IMMEDIATE 'SELECT + COALESCE(?) AS c_c0, + COALESCE(?) IS NULL AS nl_0, + COALESCE(?) IS NOT NULL AS nnl_0, + COALESCE(?) AS c_c1, + COALESCE(?) IS NULL AS nl_1, + COALESCE(?) IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5' USING c0, c0, c0, c1, c1, c1; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE f1c0 SYS_REFCURSOR; + OPEN f1c0 FOR SELECT 1; + RETURN f1c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE f2c0 SYS_REFCURSOR; + RETURN f2c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + f1() IS NULL AS nl_0, + f1() IS NOT NULL AS nnl_0, + f2() IS NULL AS nl_1, + f2() IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + COALESCE(f1()) IS NULL AS nl_0, + COALESCE(f1()) IS NOT NULL AS nnl_0, + COALESCE(f2()) IS NULL AS nl_1, + COALESCE(f2()) IS NOT NULL AS nnl_1, + refs(0,3) AS refs + FROM seq_1_to_5; +END; +/ + +DELIMITER ;/ +CALL p1; +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP FUNCTION f1; +DROP FUNCTION f2; + + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +--enable_result_log +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.result new file mode 100644 index 00000000000..48db2658020 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.result @@ -0,0 +1,146 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE FUNCTION f1(task VARCHAR(32)) RETURNS BOOLEAN +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +DECLARE c2 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +OPEN c1 FOR SELECT 2; +SET c2= c0; +CASE task +WHEN 'in' THEN RETURN c0 IN (c1, c2); +WHEN 'between' THEN RETURN c0 BETWEEN c1 AND c2; +WHEN 'is true' THEN RETURN c0 IS TRUE; +WHEN 'is false' THEN RETURN c0 IS FALSE; +WHEN 'is null' THEN RETURN c0 IS NULL; +WHEN 'is not true' THEN RETURN c0 IS NOT TRUE; +WHEN 'is not false' THEN RETURN c0 IS NOT FALSE; +WHEN 'is not null' THEN RETURN c0 IS NOT NULL; +WHEN 'like' THEN RETURN c0 LIKE c1; +WHEN 'rlike' THEN RETURN c0 RLIKE c1; +WHEN '=' THEN RETURN c0 = c1; +WHEN '<=>' THEN RETURN c0 = c1; +WHEN 'row =' THEN RETURN (c0,c1) = (c1,c2); +WHEN 'row <=>' THEN RETURN (c0,c1) = (c1,c2); +WHEN 'row in' THEN RETURN (c0,c1) IN ((c1,c2),(c0,c2)); +END CASE; +RETURN 'unknown'; +END; +/ +SELECT f1('in'); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'in' +SELECT f1('between'); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'between' +SELECT f1('is true'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'istrue' +SELECT f1('is false'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'isfalse' +SELECT f1('is not true'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'isnottrue' +SELECT f1('is not false'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'isnotfalse' +SELECT f1('like'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'like' +SELECT f1('rlike'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'regexp' +SELECT f1('='); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation '=' +SELECT f1('<=>'); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation '=' +SELECT f1('row ='); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation '=' +SELECT f1('row <=>'); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation '=' +SELECT f1('row in'); +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation 'in' +SELECT f1('is null'); +f1('is null') +0 +SELECT f1('is not null'); +f1('is not null') +1 +DROP FUNCTION f1; +# +# IS [NOT] NULL +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +c0, +c0 IS NULL AS nl_0, +c0 IS NOT NULL AS nnl_0, +c1, +c1 IS NULL AS nl_1, +c1 IS NOT NULL AS nnl_1 +FROM seq_1_to_5; +END; +/ +CALL p1; +c0 nl_0 nnl_0 c1 nl_1 nnl_1 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +SELECT +c0, +COALESCE(c0) IS NULL AS nl_0, +COALESCE(c0) IS NOT NULL AS nnl_0, +c1, +COALESCE(c1) IS NULL AS nl_1, +COALESCE(c1) IS NOT NULL AS nnl_1 +FROM seq_1_to_5; +END; +/ +CALL p1; +c0 nl_0 nnl_0 c1 nl_1 nnl_1 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +0 0 1 NULL 1 0 +DROP PROCEDURE p1; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE f1c0 SYS_REFCURSOR; +OPEN f1c0 FOR SELECT 1; +RETURN f1c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE f2c0 SYS_REFCURSOR; +RETURN f2c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN +SELECT +f1() IS NULL AS nl_0, +f1() IS NOT NULL AS nnl_0, +f2() IS NULL AS nl_1, +f2() IS NOT NULL AS nnl_1 +FROM seq_1_to_5; +END; +/ +CALL p1; +nl_0 nnl_0 nl_1 nnl_1 +0 1 1 0 +0 1 1 0 +0 1 1 0 +0 1 1 0 +0 1 1 0 +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP FUNCTION f2; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.test new file mode 100644 index 00000000000..ab253f59fce --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-predicates.test @@ -0,0 +1,145 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +DELIMITER /; +CREATE FUNCTION f1(task VARCHAR(32)) RETURNS BOOLEAN +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + DECLARE c2 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + OPEN c1 FOR SELECT 2; + SET c2= c0; + CASE task + WHEN 'in' THEN RETURN c0 IN (c1, c2); + WHEN 'between' THEN RETURN c0 BETWEEN c1 AND c2; + WHEN 'is true' THEN RETURN c0 IS TRUE; + WHEN 'is false' THEN RETURN c0 IS FALSE; + WHEN 'is null' THEN RETURN c0 IS NULL; + WHEN 'is not true' THEN RETURN c0 IS NOT TRUE; + WHEN 'is not false' THEN RETURN c0 IS NOT FALSE; + WHEN 'is not null' THEN RETURN c0 IS NOT NULL; + WHEN 'like' THEN RETURN c0 LIKE c1; + WHEN 'rlike' THEN RETURN c0 RLIKE c1; + WHEN '=' THEN RETURN c0 = c1; + WHEN '<=>' THEN RETURN c0 = c1; + WHEN 'row =' THEN RETURN (c0,c1) = (c1,c2); + WHEN 'row <=>' THEN RETURN (c0,c1) = (c1,c2); + WHEN 'row in' THEN RETURN (c0,c1) IN ((c1,c2),(c0,c2)); + END CASE; + RETURN 'unknown'; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('in'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('between'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('is true'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('is false'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('is not true'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('is not false'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('like'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT f1('rlike'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('='); +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('<=>'); + +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('row ='); +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('row <=>'); +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +SELECT f1('row in'); + +SELECT f1('is null'); +SELECT f1('is not null'); + +DROP FUNCTION f1; + + +--echo # +--echo # IS [NOT] NULL +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + c0, + c0 IS NULL AS nl_0, + c0 IS NOT NULL AS nnl_0, + c1, + c1 IS NULL AS nl_1, + c1 IS NOT NULL AS nnl_1 + FROM seq_1_to_5; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT + c0, + COALESCE(c0) IS NULL AS nl_0, + COALESCE(c0) IS NOT NULL AS nnl_0, + c1, + COALESCE(c1) IS NULL AS nl_1, + COALESCE(c1) IS NOT NULL AS nnl_1 + FROM seq_1_to_5; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE f1c0 SYS_REFCURSOR; + OPEN f1c0 FOR SELECT 1; + RETURN f1c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE f2c0 SYS_REFCURSOR; + RETURN f2c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN + SELECT + f1() IS NULL AS nl_0, + f1() IS NOT NULL AS nnl_0, + f2() IS NULL AS nl_1, + f2() IS NOT NULL AS nnl_1 + FROM seq_1_to_5; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION f1; +DROP FUNCTION f2; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.result new file mode 100644 index 00000000000..931f95dc8da --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.result @@ -0,0 +1,88 @@ +# +# Helper routines +# +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Testing a case when Item_param is the only Item with +# a complex data type (with a side effec) inside EXECUTE IMMEDIATE. +# It makes sure that the item_with_t::COMPLEX_DATA_TYPE flag +# gets properly added from Item_param +# to Query_arena::with_flags_bit_or_for_complex_data_types +# +CREATE PROCEDURE p1() +BEGIN +DECLARE p1c0 SYS_REFCURSOR; +OPEN p1c0 FOR SELECT 1; +SELECT 'p1-1' AS stage, refs(0,3) AS refs; +EXECUTE IMMEDIATE 'SELECT + ''stmt1'' AS stage, refs(0,3) AS refs + FROM seq_1_to_5 + WHERE ? IS NOT NULL' USING p1c0; +SELECT 'p1-2' AS stage, refs(0,3); +END; +/ +CALL p1; +p1-1 [1 NULL NULL NULL] +stmt1 [2 NULL NULL NULL] +stmt1 [2 NULL NULL NULL] +stmt1 [2 NULL NULL NULL] +stmt1 [2 NULL NULL NULL] +stmt1 [2 NULL NULL NULL] +p1-2 [1 NULL NULL NULL] +DROP PROCEDURE p1; +# +# Passing a variable as an IN and an OUT parameter at the same time +# +CREATE PROCEDURE p1(c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, refs(0,3) AS refs; +SET c1=c0; +SELECT 'p1-2' AS stage, refs(0,3) AS refs; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +PREPARE stmt FROM 'CALL p1(?,?)'; +SELECT 'p2-1' AS stage, refs(0,3) AS refs; +FOR i IN 1..4 DO +EXECUTE IMMEDIATE 'CALL p1(?,?)' USING c0,c0; +SELECT 'p2-i1' AS stage, refs(0,3) AS refs; +EXECUTE stmt USING c0,c0; +SELECT 'p2-i2' AS stage, refs(0,3) AS refs; +END FOR; +END; +/ +CALL p2; +p2-1 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i1 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i2 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i1 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i2 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i1 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i2 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i1 [1 NULL NULL NULL] +p1-1 [4 NULL NULL NULL] +p1-2 [5 NULL NULL NULL] +p2-i2 [1 NULL NULL NULL] +SELECT '/p2' AS stage, refs(0,3) AS refs; +/p2 [NULL NULL NULL NULL] +DROP PROCEDURE p2; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.test new file mode 100644 index 00000000000..6d9bf61aea4 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps-debug.test @@ -0,0 +1,84 @@ +--source include/have_debug.inc +--source include/have_sequence.inc + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--enable_result_log +--enable_query_log + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Testing a case when Item_param is the only Item with +--echo # a complex data type (with a side effec) inside EXECUTE IMMEDIATE. +--echo # It makes sure that the item_with_t::COMPLEX_DATA_TYPE flag +--echo # gets properly added from Item_param +--echo # to Query_arena::with_flags_bit_or_for_complex_data_types +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE p1c0 SYS_REFCURSOR; + OPEN p1c0 FOR SELECT 1; + + SELECT 'p1-1' AS stage, refs(0,3) AS refs; + + EXECUTE IMMEDIATE 'SELECT + ''stmt1'' AS stage, refs(0,3) AS refs + FROM seq_1_to_5 + WHERE ? IS NOT NULL' USING p1c0; + + SELECT 'p1-2' AS stage, refs(0,3); +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p1; +--enable_column_names +DROP PROCEDURE p1; + + +--echo # +--echo # Passing a variable as an IN and an OUT parameter at the same time +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, refs(0,3) AS refs; + SET c1=c0; + SELECT 'p1-2' AS stage, refs(0,3) AS refs; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + PREPARE stmt FROM 'CALL p1(?,?)'; + SELECT 'p2-1' AS stage, refs(0,3) AS refs; + FOR i IN 1..4 DO + EXECUTE IMMEDIATE 'CALL p1(?,?)' USING c0,c0; + SELECT 'p2-i1' AS stage, refs(0,3) AS refs; + EXECUTE stmt USING c0,c0; + SELECT 'p2-i2' AS stage, refs(0,3) AS refs; + END FOR; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2; +SELECT '/p2' AS stage, refs(0,3) AS refs; +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +--enable_result_log +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.result new file mode 100644 index 00000000000..fe17d53d349 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.result @@ -0,0 +1,430 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Sending a cursor via an SP IN parameter using EXECUTE IMMEDIATE +# +CREATE PROCEDURE p1(IN c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +FETCH c0 INTO v; +SELECT 'p1' AS stage, c0, v; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; -- c0 takes m_statement_cursors.at(0) +OPEN c1 FOR VALUES (10),(20),(30); -- c1 takes m_statement_cursors.at(1) +EXECUTE IMMEDIATE 'CALL p1(?)' USING c1; -- should fetch 10 from c1 +FETCH c1 INTO v; -- should fetch 20 from c1 +SELECT 'p2-c1' AS stage, c1, v; +FETCH c0 INTO v; -- should fetch 1 from c0 +SELECT 'p2-c0' AS stage, c0, v; +END; +/ +CALL p2; +stage c0 v +p1 1 10 +stage c1 v +p2-c1 1 20 +stage c0 v +p2-c0 0 1 +CALL p2; +stage c0 v +p1 1 10 +stage c1 v +p2-c1 1 20 +stage c0 v +p2-c0 0 1 +/* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# SYS_REFCURSOR dynamic parameters with PREPARE..EXECUTE +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +OPEN c1 FOR SELECT 1; +PREPARE stmt FROM 'SELECT ?,?'; +EXECUTE stmt USING c0,c1; +EXECUTE stmt USING c0,c1; +END; +/ +CALL p1; +? ? +0 1 +? ? +0 1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +OPEN c1 FOR SELECT 1; +PREPARE stmt FROM 'SELECT ?,?'; +EXECUTE stmt USING COALESCE(c0), COALESCE(c1); +EXECUTE stmt USING COALESCE(c0), COALESCE(c1); +END; +/ +CALL p1; +? ? +0 1 +? ? +0 1 +DROP PROCEDURE p1; +# +# Make sure various combinations of +# Item_param, Item_func_coalesce, Item_func_sp +# do not leak m_statement_cursors elements: +# c0 is expected to be 0 in all SELECT statements below. +# +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN +DECLARE ff0c0 SYS_REFCURSOR; +OPEN ff0c0 FOR SELECT 10; +RETURN ff0c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; +SELECT 'p1-0', c0; +EXECUTE IMMEDIATE 'SELECT COALESCE(?) FROM seq_1_to_5' USING c0; +SELECT 'p1-1', c0; +EXECUTE IMMEDIATE 'SELECT COALESCE(?) FROM seq_1_to_5' USING c0; +SELECT 'p1-2', c0; +EXECUTE IMMEDIATE 'SELECT LAST_VALUE(NULL,COALESCE(?)) FROM seq_1_to_5' USING c0; +SELECT 'p1-3', c0; +EXECUTE IMMEDIATE 'SELECT LAST_VALUE(COALESCE(?), COALESCE(?)) FROM seq_1_to_5' USING c0, c0; +SELECT 'p1-4', c0; +EXECUTE IMMEDIATE 'SELECT LAST_VALUE(COALESCE(?),NULL) FROM seq_1_to_5' USING c0; +SELECT 'p1-5', c0; +END; +/ +CALL p1; +p1-0 c0 +p1-0 0 +COALESCE(?) +0 +0 +0 +0 +0 +p1-1 c0 +p1-1 0 +COALESCE(?) +0 +0 +0 +0 +0 +p1-2 c0 +p1-2 0 +LAST_VALUE(NULL,COALESCE(?)) +0 +0 +0 +0 +0 +p1-3 c0 +p1-3 0 +LAST_VALUE(COALESCE(?), COALESCE(?)) +0 +0 +0 +0 +0 +p1-4 c0 +p1-4 0 +LAST_VALUE(COALESCE(?),NULL) +NULL +NULL +NULL +NULL +NULL +p1-5 c0 +p1-5 0 +DROP PROCEDURE p1; +DROP FUNCTION ff0; +# +# Similar tests using PREPARE..EXECUTE +# +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN +DECLARE ff0c0 SYS_REFCURSOR; +OPEN ff0c0 FOR SELECT 10; +RETURN ff0c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; +SELECT 'p1-0', c0; +PREPARE stmt FROM 'SELECT COALESCE(?) FROM seq_1_to_5'; +EXECUTE stmt USING c0; +DEALLOCATE PREPARE stmt; +SELECT 'p1-1', c0; +PREPARE stmt FROM 'SELECT COALESCE(?) FROM seq_1_to_5'; +EXECUTE stmt USING c0; +DEALLOCATE PREPARE stmt; +SELECT 'p1-2', c0; +PREPARE stmt FROM 'SELECT LAST_VALUE(NULL,COALESCE(?)) FROM seq_1_to_5'; +EXECUTE stmt USING c0; +DEALLOCATE PREPARE stmt; +SELECT 'p1-3', c0; +PREPARE stmt FROM 'SELECT LAST_VALUE(COALESCE(?), COALESCE(?)) FROM seq_1_to_5'; +EXECUTE stmt USING c0, c0; +DEALLOCATE PREPARE stmt; +SELECT 'p1-4', c0; +PREPARE stmt FROM 'SELECT LAST_VALUE(COALESCE(?),NULL) FROM seq_1_to_5'; +EXECUTE stmt USING c0; +DEALLOCATE PREPARE stmt; +SELECT 'p1-5', c0; +END; +/ +CALL p1; +p1-0 c0 +p1-0 0 +COALESCE(?) +0 +0 +0 +0 +0 +p1-1 c0 +p1-1 0 +COALESCE(?) +0 +0 +0 +0 +0 +p1-2 c0 +p1-2 0 +LAST_VALUE(NULL,COALESCE(?)) +0 +0 +0 +0 +0 +p1-3 c0 +p1-3 0 +LAST_VALUE(COALESCE(?), COALESCE(?)) +0 +0 +0 +0 +0 +p1-4 c0 +p1-4 0 +LAST_VALUE(COALESCE(?),NULL) +NULL +NULL +NULL +NULL +NULL +p1-5 c0 +p1-5 0 +DROP PROCEDURE p1; +DROP FUNCTION ff0; +# +# Getting a cursor via an SP OUT parameter using EXECUTE IMMEDIATE +# +CREATE PROCEDURE p1(OUT c1 SYS_REFCURSOR) +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; -- will be closed on return +OPEN c1 FOR SELECT 11; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +EXECUTE IMMEDIATE 'CALL p1(?)' USING c0; +FETCH c0 INTO v; +SELECT c0, v; +END; +/ +CALL p2; +c0 v +1 11 +CALL p2; +c0 v +1 11 +/* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Getting a cursor via an SP OUT parameter using EXECUTE IMMEDIATE +# This script demonstrates that two open cursors co-exist. +# +CREATE PROCEDURE p1(OUT f1c0 SYS_REFCURSOR) +BEGIN +OPEN f1c0 FOR SELECT 10; +END; +/ +CREATE PROCEDURE p2(count INT) +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +DECLARE v INT; +FOR i IN 1..count DO +EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; +SELECT CONCAT('p2c0=', CAST(p2c0 AS CHAR)) AS p2c0; +END FOR; +FETCH p2c0 INTO v; +SELECT CONCAT('v=', v) AS v; +END; +/ +SET @@max_open_cursors=2; +CALL p2(10); +p2c0=0 +p2c0=1 +p2c0=0 +p2c0=1 +p2c0=0 +p2c0=1 +p2c0=0 +p2c0=1 +p2c0=0 +p2c0=1 +v=10 +SET @@max_open_cursors=1; +CALL p2(10); +p2c0=0 +ERROR HY000: Too many open cursors; max 1 cursors allowed +SET @@max_open_cursors=DEFAULT; +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Getting a cursor via an SP INOUT parameter using EXECUTE IMMEDIATE +# +CREATE PROCEDURE p1(INOUT c1 SYS_REFCURSOR) +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; -- will be closed on return +OPEN c1 FOR SELECT 11; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +EXECUTE IMMEDIATE 'CALL p1(?)' USING c0; +FETCH c0 INTO v; +SELECT c0, v; +END; +/ +CALL p2; +c0 v +1 11 +CALL p2; +c0 v +1 11 +/* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Getting a cursor via an SP INOUT parameter using EXECUTE IMMEDIATE +# This script demonstates that only one cursor is needed. +# +CREATE PROCEDURE p1(INOUT f1c0 SYS_REFCURSOR) +BEGIN +OPEN f1c0 FOR SELECT 10; +END; +/ +CREATE PROCEDURE p2(count INT) +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +DECLARE v INT; +FOR i IN 1..count DO +EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; +SELECT CONCAT('p2c0=', CAST(p2c0 AS CHAR)) AS p2c0; +END FOR; +FETCH p2c0 INTO v; +SELECT CONCAT('v=', v) AS v; +END; +/ +SET @@max_open_cursors=1; +CALL p2(10); +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +p2c0=0 +v=10 +SET @@max_open_cursors=DEFAULT; +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Setting Item_param from not-NULL to NULL via EXECUTE IMMEDIATE +# OUT param +# +CREATE PROCEDURE p1(OUT p1c0 SYS_REFCURSOR) +BEGIN +SELECT 'p1-0' AS stage, p1c0; +SET p1c0= NULL; +SELECT 'p1-1' AS stage, p1c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +OPEN p2c0 FOR SELECT 10; +EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; +SELECT 'p2-1' AS stage, p2c0; +OPEN p2c0 FOR SELECT 20; +SELECT 'p2-2' AS stage, p2c0; -- Should return 0 if cursor array elements do not leak. +END; +/ +CALL p2; +p1-0 NULL +p1-1 NULL +p2-1 NULL +p2-2 0 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Setting Item_param from not-NULL to NULL via EXECUTE IMMEDIATE +# INOUT param +# +CREATE PROCEDURE p1(INOUT p1c0 SYS_REFCURSOR) +BEGIN +SELECT 'p1-0' AS stage, p1c0; +SET p1c0= NULL; +SELECT 'p1-1' AS stage, p1c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +OPEN p2c0 FOR SELECT 10; +EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; +SELECT 'p2-1' AS stage, p2c0; +OPEN p2c0 FOR SELECT 20; +SELECT 'pc-2' AS stage, p2c0; -- Should return 0 if cursor array elements do not leak. +END; +/ +CALL p2; +p1-0 0 +p1-1 NULL +p2-1 NULL +pc-2 0 +DROP PROCEDURE p2; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.test new file mode 100644 index 00000000000..57b17a1c30c --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-ps.test @@ -0,0 +1,371 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + + +--echo # +--echo # Sending a cursor via an SP IN parameter using EXECUTE IMMEDIATE +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(IN c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + FETCH c0 INTO v; + SELECT 'p1' AS stage, c0, v; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; -- c0 takes m_statement_cursors.at(0) + OPEN c1 FOR VALUES (10),(20),(30); -- c1 takes m_statement_cursors.at(1) + EXECUTE IMMEDIATE 'CALL p1(?)' USING c1; -- should fetch 10 from c1 + FETCH c1 INTO v; -- should fetch 20 from c1 + SELECT 'p2-c1' AS stage, c1, v; + FETCH c0 INTO v; -- should fetch 1 from c0 + SELECT 'p2-c0' AS stage, c0, v; +END; +/ +DELIMITER ;/ +CALL p2; +CALL p2; /* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; + +--echo # +--echo # SYS_REFCURSOR dynamic parameters with PREPARE..EXECUTE +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + OPEN c1 FOR SELECT 1; + PREPARE stmt FROM 'SELECT ?,?'; + EXECUTE stmt USING c0,c1; + EXECUTE stmt USING c0,c1; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + OPEN c1 FOR SELECT 1; + PREPARE stmt FROM 'SELECT ?,?'; + EXECUTE stmt USING COALESCE(c0), COALESCE(c1); + EXECUTE stmt USING COALESCE(c0), COALESCE(c1); +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Make sure various combinations of +--echo # Item_param, Item_func_coalesce, Item_func_sp +--echo # do not leak m_statement_cursors elements: +--echo # c0 is expected to be 0 in all SELECT statements below. +--echo # + +DELIMITER /; +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN + DECLARE ff0c0 SYS_REFCURSOR; + OPEN ff0c0 FOR SELECT 10; + RETURN ff0c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; + SELECT 'p1-0', c0; + + EXECUTE IMMEDIATE 'SELECT COALESCE(?) FROM seq_1_to_5' USING c0; + SELECT 'p1-1', c0; + + EXECUTE IMMEDIATE 'SELECT COALESCE(?) FROM seq_1_to_5' USING c0; + SELECT 'p1-2', c0; + + EXECUTE IMMEDIATE 'SELECT LAST_VALUE(NULL,COALESCE(?)) FROM seq_1_to_5' USING c0; + SELECT 'p1-3', c0; + + EXECUTE IMMEDIATE 'SELECT LAST_VALUE(COALESCE(?), COALESCE(?)) FROM seq_1_to_5' USING c0, c0; + SELECT 'p1-4', c0; + + EXECUTE IMMEDIATE 'SELECT LAST_VALUE(COALESCE(?),NULL) FROM seq_1_to_5' USING c0; + SELECT 'p1-5', c0; + +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION ff0; + + +--echo # +--echo # Similar tests using PREPARE..EXECUTE +--echo # + +DELIMITER /; +CREATE FUNCTION ff0() RETURNS SYS_REFCURSOR +BEGIN + DECLARE ff0c0 SYS_REFCURSOR; + OPEN ff0c0 FOR SELECT 10; + RETURN ff0c0; +END; +/ +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; + SELECT 'p1-0', c0; + + PREPARE stmt FROM 'SELECT COALESCE(?) FROM seq_1_to_5'; + EXECUTE stmt USING c0; + DEALLOCATE PREPARE stmt; + SELECT 'p1-1', c0; + + PREPARE stmt FROM 'SELECT COALESCE(?) FROM seq_1_to_5'; + EXECUTE stmt USING c0; + DEALLOCATE PREPARE stmt; + SELECT 'p1-2', c0; + + PREPARE stmt FROM 'SELECT LAST_VALUE(NULL,COALESCE(?)) FROM seq_1_to_5'; + EXECUTE stmt USING c0; + DEALLOCATE PREPARE stmt; + SELECT 'p1-3', c0; + + PREPARE stmt FROM 'SELECT LAST_VALUE(COALESCE(?), COALESCE(?)) FROM seq_1_to_5'; + EXECUTE stmt USING c0, c0; + DEALLOCATE PREPARE stmt; + SELECT 'p1-4', c0; + + PREPARE stmt FROM 'SELECT LAST_VALUE(COALESCE(?),NULL) FROM seq_1_to_5'; + EXECUTE stmt USING c0; + DEALLOCATE PREPARE stmt; + SELECT 'p1-5', c0; + +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP FUNCTION ff0; + + +--echo # +--echo # Getting a cursor via an SP OUT parameter using EXECUTE IMMEDIATE +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT c1 SYS_REFCURSOR) +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; -- will be closed on return + OPEN c1 FOR SELECT 11; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + EXECUTE IMMEDIATE 'CALL p1(?)' USING c0; + FETCH c0 INTO v; + SELECT c0, v; +END; +/ +DELIMITER ;/ +CALL p2; +CALL p2; /* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Getting a cursor via an SP OUT parameter using EXECUTE IMMEDIATE +--echo # This script demonstrates that two open cursors co-exist. +--echo # + +# - On the first loop iteration p1 opens m_statement_cursors.at(0). +# - On the second iteration p1 opens m_statement_cursors.at(1), +# because p2c0 still refers to m_statement_cursors.at(0). +# In the end of 'CALL p1(?)' f1c0 gets copied to p2c0, +# p2c0 detaches from m_statement_cursors.at(0) +# p2c0 now refers to m_statement_cursors.at(1) +# - On the third iteration p1 opens m_statement_cursors.at(0) +# And so on: +# - on the odd iteration p1 opens m_statement_cursors.at(0) +# - on the even iteration p1 opens m_statement_cursors.at(1) + + +DELIMITER /; +CREATE PROCEDURE p1(OUT f1c0 SYS_REFCURSOR) +BEGIN + OPEN f1c0 FOR SELECT 10; +END; +/ +CREATE PROCEDURE p2(count INT) +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + DECLARE v INT; + FOR i IN 1..count DO + EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; + SELECT CONCAT('p2c0=', CAST(p2c0 AS CHAR)) AS p2c0; + END FOR; + FETCH p2c0 INTO v; + SELECT CONCAT('v=', v) AS v; +END; +/ +DELIMITER ;/ +SET @@max_open_cursors=2; +--disable_column_names +CALL p2(10); +SET @@max_open_cursors=1; +--error ER_TOO_MANY_OPEN_CURSORS +CALL p2(10); +--enable_column_names +SET @@max_open_cursors=DEFAULT; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Getting a cursor via an SP INOUT parameter using EXECUTE IMMEDIATE +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(INOUT c1 SYS_REFCURSOR) +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; -- will be closed on return + OPEN c1 FOR SELECT 11; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + EXECUTE IMMEDIATE 'CALL p1(?)' USING c0; + FETCH c0 INTO v; + SELECT c0, v; +END; +/ +DELIMITER ;/ +CALL p2; +CALL p2; /* make sure it starts from m_statement_cursors(0) on the second execution */ +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Getting a cursor via an SP INOUT parameter using EXECUTE IMMEDIATE +--echo # This script demonstates that only one cursor is needed. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(INOUT f1c0 SYS_REFCURSOR) +BEGIN + OPEN f1c0 FOR SELECT 10; +END; +/ +CREATE PROCEDURE p2(count INT) +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + DECLARE v INT; + FOR i IN 1..count DO + EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; + SELECT CONCAT('p2c0=', CAST(p2c0 AS CHAR)) AS p2c0; + END FOR; + FETCH p2c0 INTO v; + SELECT CONCAT('v=', v) AS v; +END; +/ +DELIMITER ;/ +SET @@max_open_cursors=1; +--disable_column_names +CALL p2(10); +--enable_column_names +SET @@max_open_cursors=DEFAULT; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Setting Item_param from not-NULL to NULL via EXECUTE IMMEDIATE +--echo # OUT param +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT p1c0 SYS_REFCURSOR) +BEGIN + SELECT 'p1-0' AS stage, p1c0; + SET p1c0= NULL; + SELECT 'p1-1' AS stage, p1c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + OPEN p2c0 FOR SELECT 10; + EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; + SELECT 'p2-1' AS stage, p2c0; + OPEN p2c0 FOR SELECT 20; + SELECT 'p2-2' AS stage, p2c0; -- Should return 0 if cursor array elements do not leak. +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2; +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Setting Item_param from not-NULL to NULL via EXECUTE IMMEDIATE +--echo # INOUT param +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(INOUT p1c0 SYS_REFCURSOR) +BEGIN + SELECT 'p1-0' AS stage, p1c0; + SET p1c0= NULL; + SELECT 'p1-1' AS stage, p1c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + OPEN p2c0 FOR SELECT 10; + EXECUTE IMMEDIATE 'CALL p1(?)' USING p2c0; + SELECT 'p2-1' AS stage, p2c0; + OPEN p2c0 FOR SELECT 20; + SELECT 'pc-2' AS stage, p2c0; -- Should return 0 if cursor array elements do not leak. +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2; +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.result new file mode 100644 index 00000000000..411361cde45 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.result @@ -0,0 +1,130 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +SET @@max_open_cursors=3; +SET @@max_sp_recursion_depth=10; +# +# OUT param +# +CREATE PROCEDURE p1(counter INT UNSIGNED, OUT p1c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +IF counter=0 THEN +SELECT 'p1-0' AS stage, p1c0, v; +OPEN p1c0 FOR SELECT seq FROM seq_10_to_15; +SELECT 'p1-1' AS stage, p1c0, v; +FETCH p1c0 INTO v; +SELECT 'p1-3' AS stage, p1c0, v; +ELSE +OPEN p1c0 FOR SELECT seq FROM seq_20_to_20; +FETCH p1c0 INTO v; +SELECT 'p1-req' AS stage, p1c0, v, counter; +CALL p1(counter-1, p1c0); +END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN +DECLARE v INT; +DECLARE p2c0 SYS_REFCURSOR; +SELECT 'p2c0' AS stage, p2c0, v; +CALL p1(counter, p2c0); +FETCH p2c0 INTO v; +SELECT 'p2c1' AS stage, p2c0, v; +END; +/ +CALL p2(4); +p2c0 NULL NULL +p1-req 0 20 4 +p1-req 0 20 3 +p1-req 0 20 2 +p1-req 0 20 1 +p1-0 NULL NULL +p1-1 0 NULL +p1-3 0 10 +p2c1 0 11 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# INOUT param +# +CREATE PROCEDURE p1(counter INT UNSIGNED, INOUT p1c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +IF counter=0 THEN +FETCH p1c0 INTO v; +SELECT 'p1-0' AS stage, p1c0, v; +OPEN p1c0 FOR SELECT seq FROM seq_10_to_15; +FETCH p1c0 INTO v; +SELECT 'p1-1' AS stage, p1c0, v; +ELSE +OPEN p1c0 FOR SELECT seq FROM seq_20_to_25; +FETCH p1c0 INTO v; +SELECT 'p1-req' AS stage, p1c0, v, counter; +CALL p1(counter-1, p1c0); +END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN +DECLARE v INT; +DECLARE p2c0 SYS_REFCURSOR; +SELECT 'p2-0', p2c0, v; +CALL p1(counter, p2c0); +FETCH p2c0 INTO v; +SELECT 'p2-1', p2c0, v; +END; +/ +CALL p2(4); +p2-0 NULL NULL +p1-req 0 20 4 +p1-req 0 20 3 +p1-req 0 20 2 +p1-req 0 20 1 +p1-0 0 21 +p1-1 0 10 +p2-1 0 11 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# IN param +# +CREATE PROCEDURE p1(counter INT UNSIGNED, IN p1c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +IF counter=0 THEN +FETCH p1c0 INTO v; +SELECT 'p1-0' AS stage, p1c0, v; +ELSE +FETCH p1c0 INTO v; +SELECT 'p1-req' AS stage, p1c0, v, counter; +CALL p1(counter-1, p1c0); +END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN +DECLARE v INT; +DECLARE p2c0 SYS_REFCURSOR; +SELECT 'p2-0', p2c0, v; +OPEN p2c0 FOR SELECT seq FROM seq_20_to_26; +FETCH p2c0 INTO v; +SELECT 'p2-1', p2c0, v; +CALL p1(counter, p2c0); +FETCH p2c0 INTO v; +SELECT 'p2-2', p2c0, v; +END; +/ +CALL p2(4); +p2-0 NULL NULL +p2-1 0 20 +p1-req 0 21 4 +p1-req 0 22 3 +p1-req 0 23 2 +p1-req 0 24 1 +p1-0 0 25 +p2-2 0 26 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +SET @@max_open_cursors=DEFAULT; +SET @@max_sp_recursion_depth=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.test new file mode 100644 index 00000000000..80a48a36d07 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-recursion.test @@ -0,0 +1,130 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +SET @@max_open_cursors=3; +SET @@max_sp_recursion_depth=10; + +--echo # +--echo # OUT param +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(counter INT UNSIGNED, OUT p1c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + IF counter=0 THEN + SELECT 'p1-0' AS stage, p1c0, v; + OPEN p1c0 FOR SELECT seq FROM seq_10_to_15; + SELECT 'p1-1' AS stage, p1c0, v; + FETCH p1c0 INTO v; + SELECT 'p1-3' AS stage, p1c0, v; + ELSE + OPEN p1c0 FOR SELECT seq FROM seq_20_to_20; + FETCH p1c0 INTO v; + SELECT 'p1-req' AS stage, p1c0, v, counter; + CALL p1(counter-1, p1c0); + END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN + DECLARE v INT; + DECLARE p2c0 SYS_REFCURSOR; + SELECT 'p2c0' AS stage, p2c0, v; + CALL p1(counter, p2c0); + FETCH p2c0 INTO v; + SELECT 'p2c1' AS stage, p2c0, v; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2(4); +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # INOUT param +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(counter INT UNSIGNED, INOUT p1c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + IF counter=0 THEN + FETCH p1c0 INTO v; + SELECT 'p1-0' AS stage, p1c0, v; + OPEN p1c0 FOR SELECT seq FROM seq_10_to_15; + FETCH p1c0 INTO v; + SELECT 'p1-1' AS stage, p1c0, v; + ELSE + OPEN p1c0 FOR SELECT seq FROM seq_20_to_25; + FETCH p1c0 INTO v; + SELECT 'p1-req' AS stage, p1c0, v, counter; + CALL p1(counter-1, p1c0); + END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN + DECLARE v INT; + DECLARE p2c0 SYS_REFCURSOR; + SELECT 'p2-0', p2c0, v; + CALL p1(counter, p2c0); + FETCH p2c0 INTO v; + SELECT 'p2-1', p2c0, v; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2(4); +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # IN param +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(counter INT UNSIGNED, IN p1c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + IF counter=0 THEN + FETCH p1c0 INTO v; + SELECT 'p1-0' AS stage, p1c0, v; + ELSE + FETCH p1c0 INTO v; + SELECT 'p1-req' AS stage, p1c0, v, counter; + CALL p1(counter-1, p1c0); + END IF; +END; +/ +CREATE PROCEDURE p2(counter INT UNSIGNED) +BEGIN + DECLARE v INT; + DECLARE p2c0 SYS_REFCURSOR; + SELECT 'p2-0', p2c0, v; + OPEN p2c0 FOR SELECT seq FROM seq_20_to_26; + FETCH p2c0 INTO v; + SELECT 'p2-1', p2c0, v; + CALL p1(counter, p2c0); + FETCH p2c0 INTO v; + SELECT 'p2-2', p2c0, v; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2(4); +--enable_column_names +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +SET @@max_open_cursors=DEFAULT; +SET @@max_sp_recursion_depth=DEFAULT; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-select_for_update.inc b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-select_for_update.inc new file mode 100644 index 00000000000..de736c2bd34 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-select_for_update.inc @@ -0,0 +1,123 @@ +--echo # +--echo # OPEN sys_ref_cursor FOR SELECT ... FOR UPDATE +--echo # + +SELECT @@autocommit; +SELECT @@transaction_isolation; +SELECT @@default_storage_engine; + +CREATE TABLE t1 ( + id INT PRIMARY KEY, + worker VARCHAR(32) DEFAULT '', + ts TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +); +SHOW CREATE TABLE t1; + + +CREATE TABLE t2 (a VARCHAR(128)) ENGINE=MEMORY; + +DELIMITER /; +CREATE PROCEDURE p2(for_update BOOL, do_fetch BOOL) +BEGIN + DECLARE c SYS_REFCURSOR; + DECLARE v INT; + START TRANSACTION; + IF for_update THEN + OPEN c FOR SELECT id FROM t1 WHERE id=0 FOR UPDATE; + ELSE + OPEN c FOR SELECT id FROM t1 WHERE id=0; + END IF; + IF do_fetch THEN + FETCH c INTO v; + END IF; + + -- signal to the other thread that OPEN happened + INSERT INTO t2 VALUES + ('The exact value does not matter in t2. Only COUNT(*) matters'); + + IF NOT for_update THEN + -- If FOR UPDATE is not specified then other thread is not locked + -- Let the other thread finish INSERT. + DO SLEEP(30); -- This query will be killed by the other thread + END IF; + INSERT INTO t1 VALUES (12, 'p2', SYSDATE(6)); + CLOSE c; + COMMIT; +END; +/ +CREATE PROCEDURE p1(for_update BOOL) +BEGIN + DECLARE v INT; + DECLARE session_id INT; + START TRANSACTION; + IF for_update THEN + SET v=(SELECT id FROM t1 WHERE id=0 FOR UPDATE); + ELSE + SET v=(SELECT id FROM t1 WHERE id=0); + END IF; + INSERT INTO t1 VALUES (11, 'p1', SYSDATE(6)); + COMMIT; + + -- Check if the other thread is executing the SLEEP + -- statement and kill it to avoid waiting + SET session_id= (SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE INFO LIKE '%SLEEP(%)'); + SELECT CONCAT('p1: session_id IS NOT NULL:', session_id IS NOT NULL) AS msg; + IF session_id IS NOT NULL + THEN + KILL QUERY session_id; + END IF; +END; +/ +DELIMITER ;/ + + +let $for_update=0; + +while ($for_update < 2) +{ + let $do_fetch=0; + while ($do_fetch < 2) + { + echo ------------ for_update=$for_update do_fetch=$do_fetch; + + TRUNCATE TABLE t1; + TRUNCATE TABLE t2; + + # Let's insert in a transaction to be independent from @@autocommit. + BEGIN; + INSERT INTO t1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7); + COMMIT; + + connect(con2,localhost,root); + connection con2; + send_eval CALL p2($for_update, $do_fetch); + connection default; + # wait for the thread con2 to finish the OPEN statement in p2() + let $wait_condition=SELECT COUNT(*) FROM t2; + source include/wait_condition.inc; + eval CALL p1($for_update); + connection con2; + reap; + disconnect con2; + connection default; + + if (!$for_update) + { + echo # Without FOR UPDATE: p1 inserted first; + } + if ($for_update) + { + echo # With FOR UPDATE: p2 inserted first; + } + + SELECT id, worker FROM t1 WHERE worker<>'' ORDER BY ts; + inc $do_fetch; + } + inc $for_update; +} + +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +DROP TABLE t2; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.result new file mode 100644 index 00000000000..2ad52d3889a --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.result @@ -0,0 +1,488 @@ +SET NAMES utf8mb4; +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Helper routines +# +# +# LAST_VALUE() cleans ref counters +# +CREATE PROCEDURE p2() +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); +OPEN p2c0 FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); +SET @log= ''; +SELECT last_value(ff1(p2c0), ff1(p2c0), ff1(p2c0)) AS lv; +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +CALL p2; +stage curs refs +p2-0 NULL [NULL NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL NULL] +lv +0 +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 5); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# A cursor on a SELECT returning a local SP variable works in the caller +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +CALL show_cursor_and_refs('p2-0', CAST(c0 AS CHAR), 0, 5); +SET @log= ''; +SET c0= ff0(); +SELECT @log; +FETCH c0 INTO v; +SELECT v; +CALL show_cursor_and_refs('p2-1', CAST(c0 AS CHAR), 0, 5); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL NULL NULL NULL] +@log +ff0-0 NULL [NULL NULL NULL NULL NULL NULL] +ff0-1 0 [1 NULL NULL NULL NULL NULL] +v +10 +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# A function returning on error still clears ref counters +# +# RETURN +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +OPEN c0 FOR SELECT 1; +FETCH c0 INTO v; +FETCH c0 INTO v; -- This raises 'No data' error +RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + SET c0= f1(); +RETURN c0; +END; +/ +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +stage curs refs +/f2 NULL [0 NULL NULL NULL NULL] +DROP FUNCTION f1; +DROP FUNCTION f2; +# INOUT parameter +CREATE FUNCTION f1(INOUT c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN +DECLARE v INT; +OPEN c0 FOR SELECT 1; +FETCH c0 INTO v; +FETCH c0 INTO v; -- This raises 'No data' error +RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + DO f1(c0); +RETURN c0; +END; +/ +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +stage curs refs +/f2 NULL [0 NULL NULL NULL NULL] +DROP FUNCTION f1; +DROP FUNCTION f2; +# OUT parameter +CREATE FUNCTION f1(OUT c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN +DECLARE v INT; +OPEN c0 FOR SELECT 1; +FETCH c0 INTO v; +FETCH c0 INTO v; -- This raises 'No data' error +RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + DO f1(c0); +RETURN c0; +END; +/ +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +stage curs refs +/f2 NULL [0 NULL NULL NULL NULL] +DROP FUNCTION f1; +DROP FUNCTION f2; +# +# IN param + OPEN in p2 + multuple assignments +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 1); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 1); +SET @log=''; +SET c= ff1(c); +SELECT @log; +CALL show_cursor_and_refs('p2-3', CAST(c AS CHAR), 0, 1); +SET @log=''; +SET c= ff1(c); +SELECT @log; +CALL show_cursor_and_refs('p2-4', CAST(c AS CHAR), 0, 1); +END; +/ +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 88 "CALL show_cursor_and_refs('p2-1', CAS..." +2 copen STMT.cursor[c@0] +3 stmt 88 "CALL show_cursor_and_refs('p2-2', CAS..." +4 stmt 31 "SET @log=''" +5 set c@0 `ff1`(c@0) +6 stmt 0 "SELECT @log" +7 stmt 88 "CALL show_cursor_and_refs('p2-3', CAS..." +8 stmt 31 "SET @log=''" +9 set c@0 `ff1`(c@0) +10 stmt 0 "SELECT @log" +11 stmt 88 "CALL show_cursor_and_refs('p2-4', CAS..." +12 destruct sys_refcursor c@0 +CALL p2; +stage curs refs +p2-1 NULL [NULL NULL] +stage curs refs +p2-2 0 [1 NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-3 0 [1 NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-4 0 [1 NULL] +CALL show_cursor_and_refs('/p2','-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# SF + no-param + OPEN in f0 +# +CREATE PROCEDURE p2(num INT) +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 1); +FOR i IN 1..num +DO +SET @log= ''; +SET c= ff0(); +SELECT @log; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 4); +END FOR; +CALL show_cursor_and_refs('p2-e', CAST(c AS CHAR), 0, 4); +END; +/ +CALL p2(1); +stage curs refs +p2-0 NULL [NULL NULL] +@log +ff0-0 NULL [NULL NULL NULL NULL NULL NULL] +ff0-1 0 [1 NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL] +stage curs refs +p2-e 0 [1 NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +CALL p2(4); +stage curs refs +p2-0 NULL [NULL NULL] +@log +ff0-0 NULL [NULL NULL NULL NULL NULL NULL] +ff0-1 0 [1 NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL] +@log +ff0-0 NULL [1 NULL NULL NULL NULL NULL] +ff0-1 1 [1 1 NULL NULL NULL NULL] +stage curs refs +p2-1 1 [0 1 NULL NULL NULL] +@log +ff0-0 NULL [0 1 NULL NULL NULL NULL] +ff0-1 0 [1 1 NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 0 NULL NULL NULL] +@log +ff0-0 NULL [1 0 NULL NULL NULL NULL] +ff0-1 1 [1 1 NULL NULL NULL NULL] +stage curs refs +p2-1 1 [0 1 NULL NULL NULL] +stage curs refs +p2-e 1 [0 1 NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# SF + no-param + OPEN in p2 + OPEN in f0 +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); +SET @log= ''; +SET c= ff0(); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL] +@log +ff0-0 NULL [1 NULL NULL NULL NULL NULL] +ff0-1 1 [1 1 NULL NULL NULL NULL] +stage curs refs +p2-2 1 [0 1 NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# SF + IN param + OPEN in p2 +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); +SET @log= ''; +SET c= ff1(c); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); +SET @log= ''; +SET c= ff1(ff1(c)); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); +SET @log= ''; +SET c= ff1(ff1(ff1(ff1(ff1(c))))); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# COALESCE + OPEN in p2 +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); +SET @log= ''; +SET c= COALESCE(COALESCE(COALESCE(COALESCE(COALESCE(c))))); +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +CALL p2(); +stage curs refs +p2-0 NULL [NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; +# +# SF-COALESCE-MIX + IN param + OPEN in p2 +# +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 5); +OPEN c FOR SELECT 1; +CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 5); +SET @log= ''; +SET c= COALESCE(ff1(NULL), ff1(NULL), ff1(NULL), ff1(c)); +SELECT @log; +CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 5); +SET @log= ''; +SET c= ff1(COALESCE(NULL, ff1(NULL), ff1(NULL), ff1(NULL), ff1(c))); +SELECT @log; +CALL show_cursor_and_refs('p2-3', CAST(c AS CHAR), 0, 5); +SET @log= ''; +SET c= ff1(COALESCE(COALESCE(COALESCE(NULL, ff1(c))))); +SELECT @log; +CALL show_cursor_and_refs('p2-4', CAST(c AS CHAR), 0, 5); +SET @log= ''; +SET c= ff1(ff1(ff1(ff1(COALESCE(NULL, ff1(c)))))); +SELECT @log; +CALL show_cursor_and_refs('p2-5', CAST(c AS CHAR), 0, 5); +SET @log= ''; +SET c= ff1(COALESCE(ff1(COALESCE(ff1(COALESCE(NULL, ff1(c))))))); +SELECT @log; +CALL show_cursor_and_refs('p2-5', CAST(c AS CHAR), 0, 5); +END; +/ +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 88 "CALL show_cursor_and_refs('p2-0', CAS..." +2 copen STMT.cursor[c@0] +3 stmt 88 "CALL show_cursor_and_refs('p2-1', CAS..." +4 stmt 31 "SET @log= ''" +5 set c@0 coalesce(`ff1`(NULL),`ff1`(NULL),`ff1`(NULL),`ff1`(c@0)) +6 stmt 0 "SELECT @log" +7 stmt 88 "CALL show_cursor_and_refs('p2-2', CAS..." +8 stmt 31 "SET @log= ''" +9 set c@0 `ff1`(coalesce(NULL,`ff1`(NULL),`ff1`(NULL),`ff1`(NULL),`ff1`(c@0))) +10 stmt 0 "SELECT @log" +11 stmt 88 "CALL show_cursor_and_refs('p2-3', CAS..." +12 stmt 31 "SET @log= ''" +13 set c@0 `ff1`(coalesce(coalesce(coalesce(NULL,`ff1`(c@0))))) +14 stmt 0 "SELECT @log" +15 stmt 88 "CALL show_cursor_and_refs('p2-4', CAS..." +16 stmt 31 "SET @log= ''" +17 set c@0 `ff1`(`ff1`(`ff1`(`ff1`(coalesce(NULL,`ff1`(c@0)))))) +18 stmt 0 "SELECT @log" +19 stmt 88 "CALL show_cursor_and_refs('p2-5', CAS..." +20 stmt 31 "SET @log= ''" +21 set c@0 `ff1`(coalesce(`ff1`(coalesce(`ff1`(coalesce(NULL,`ff1`(c@0))))))) +22 stmt 0 "SELECT @log" +23 stmt 88 "CALL show_cursor_and_refs('p2-5', CAS..." +24 destruct sys_refcursor c@0 +CALL p2; +stage curs refs +p2-0 NULL [NULL NULL NULL NULL NULL NULL] +stage curs refs +p2-1 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-2 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 NULL [1 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-3 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-4 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-5 0 [1 NULL NULL NULL NULL NULL] +@log +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +ff1-0 0 [2 NULL NULL NULL NULL NULL] +stage curs refs +p2-5 0 [1 NULL NULL NULL NULL NULL] +CALL show_cursor_and_refs('/p2', '-', 0, 4); +stage curs refs +/p2 - [NULL NULL NULL NULL NULL] +DROP PROCEDURE p2; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.test new file mode 100644 index 00000000000..5f559f4b9ee --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sf-debug.test @@ -0,0 +1,373 @@ +--source include/have_debug.inc + +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--enable_result_log +--enable_query_log + + +--echo # +--echo # LAST_VALUE() cleans ref counters +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(p2c0 AS CHAR), 0, 5); + OPEN p2c0 FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(p2c0 AS CHAR), 0, 5); + SET @log= ''; + SELECT last_value(ff1(p2c0), ff1(p2c0), ff1(p2c0)) AS lv; + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(p2c0 AS CHAR), 0, 5); +END; +/ +DELIMITER ;/ +CALL p2; +CALL show_cursor_and_refs('/p2', '-', 0, 5); +DROP PROCEDURE p2; + + +--echo # +--echo # A cursor on a SELECT returning a local SP variable works in the caller +--echo # + +# +# Although ff0's sp_rcontext is already destroyed when FETCH is executed in p2, +# the cursor opened by `SELECT a` in ff0 still works +# because the cursor was materialized into a temporary table. +# + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + CALL show_cursor_and_refs('p2-0', CAST(c0 AS CHAR), 0, 5); + SET @log= ''; + SET c0= ff0(); + SELECT @log; + FETCH c0 INTO v; + SELECT v; + CALL show_cursor_and_refs('p2-1', CAST(c0 AS CHAR), 0, 5); +END; +/ +DELIMITER ;/ +CALL p2(); +DROP PROCEDURE p2; + + +--echo # +--echo # A function returning on error still clears ref counters +--echo # + +--echo # RETURN + +DELIMITER /; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + OPEN c0 FOR SELECT 1; + FETCH c0 INTO v; + FETCH c0 INTO v; -- This raises 'No data' error + RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + SET c0= f1(); + RETURN c0; +END; +/ +DELIMITER ;/ +# CURSOR_REF_COUNT(0) is expected to be 0 +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +DROP FUNCTION f1; +DROP FUNCTION f2; + + +--echo # INOUT parameter +DELIMITER /; +CREATE FUNCTION f1(INOUT c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN + DECLARE v INT; + OPEN c0 FOR SELECT 1; + FETCH c0 INTO v; + FETCH c0 INTO v; -- This raises 'No data' error + RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + DO f1(c0); + RETURN c0; +END; +/ +DELIMITER ;/ +# CURSOR_REF_COUNT(0) is expected to be 0 +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +DROP FUNCTION f1; +DROP FUNCTION f2; + + +--echo # OUT parameter +DELIMITER /; +CREATE FUNCTION f1(OUT c0 SYS_REFCURSOR) RETURNS SYS_REFCURSOR +BEGIN + DECLARE v INT; + OPEN c0 FOR SELECT 1; + FETCH c0 INTO v; + FETCH c0 INTO v; -- This raises 'No data' error + RETURN c0; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' RETURN NULL; -- Handle 'No data' + DO f1(c0); + RETURN c0; +END; +/ +DELIMITER ;/ +# CURSOR_REF_COUNT(0) is expected to be 0 +CALL show_cursor_and_refs('/f2', CAST(f2() AS CHAR), 0, 4); +DROP FUNCTION f1; +DROP FUNCTION f2; + + +--echo # +--echo # IN param + OPEN in p2 + multuple assignments +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 1); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 1); + + SET @log=''; + SET c= ff1(c); + SELECT @log; + CALL show_cursor_and_refs('p2-3', CAST(c AS CHAR), 0, 1); + + SET @log=''; + SET c= ff1(c); + SELECT @log; + CALL show_cursor_and_refs('p2-4', CAST(c AS CHAR), 0, 1); + +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p2; +CALL p2; +CALL show_cursor_and_refs('/p2','-', 0, 4); +DROP PROCEDURE p2; + + +--echo # +--echo # SF + no-param + OPEN in f0 +--echo # + +DELIMITER /; +CREATE PROCEDURE p2(num INT) +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 1); + + FOR i IN 1..num + DO + SET @log= ''; + SET c= ff0(); + SELECT @log; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 4); + + END FOR; + + CALL show_cursor_and_refs('p2-e', CAST(c AS CHAR), 0, 4); +END; +/ +DELIMITER ;/ +CALL p2(1); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +CALL p2(4); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +--echo # +--echo # SF + no-param + OPEN in p2 + OPEN in f0 +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); + + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); + + SET @log= ''; + SET c= ff0(); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +DELIMITER ;/ +CALL p2(); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +--echo # +--echo # SF + IN param + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); + + SET @log= ''; + SET c= ff1(c); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +DELIMITER ;/ +CALL p2(); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); + + SET @log= ''; + SET c= ff1(ff1(c)); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +DELIMITER ;/ +CALL p2(); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); + + SET @log= ''; + SET c= ff1(ff1(ff1(ff1(ff1(c))))); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +DELIMITER ;/ +CALL p2(); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +--echo # +--echo # COALESCE + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 2); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 2); + + SET @log= ''; + SET c= COALESCE(COALESCE(COALESCE(COALESCE(COALESCE(c))))); + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 2); +END; +/ +DELIMITER ;/ +CALL p2(); +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +--echo # +--echo # SF-COALESCE-MIX + IN param + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + CALL show_cursor_and_refs('p2-0', CAST(c AS CHAR), 0, 5); + OPEN c FOR SELECT 1; + CALL show_cursor_and_refs('p2-1', CAST(c AS CHAR), 0, 5); + + SET @log= ''; + SET c= COALESCE(ff1(NULL), ff1(NULL), ff1(NULL), ff1(c)); + SELECT @log; + CALL show_cursor_and_refs('p2-2', CAST(c AS CHAR), 0, 5); + + SET @log= ''; + SET c= ff1(COALESCE(NULL, ff1(NULL), ff1(NULL), ff1(NULL), ff1(c))); + SELECT @log; + CALL show_cursor_and_refs('p2-3', CAST(c AS CHAR), 0, 5); + + SET @log= ''; + SET c= ff1(COALESCE(COALESCE(COALESCE(NULL, ff1(c))))); + SELECT @log; + CALL show_cursor_and_refs('p2-4', CAST(c AS CHAR), 0, 5); + + SET @log= ''; + SET c= ff1(ff1(ff1(ff1(COALESCE(NULL, ff1(c)))))); + SELECT @log; + CALL show_cursor_and_refs('p2-5', CAST(c AS CHAR), 0, 5); + + SET @log= ''; + SET c= ff1(COALESCE(ff1(COALESCE(ff1(COALESCE(NULL, ff1(c))))))); + SELECT @log; + CALL show_cursor_and_refs('p2-5', CAST(c AS CHAR), 0, 5); + +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p2; +CALL p2; +CALL show_cursor_and_refs('/p2', '-', 0, 4); +DROP PROCEDURE p2; + + +--disable_query_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.result new file mode 100644 index 00000000000..118314058e9 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.result @@ -0,0 +1,687 @@ +SET @sql_mode=@@sql_mode; +SET sql_mode=ORACLE; +CREATE FUNCTION fetch_one_value(c SYS_REFCURSOR) RETURN VARCHAR AS +v VARCHAR(128) :='none'; +BEGIN +IF c%ISOPEN THEN +FETCH c INTO v; +END IF; +RETURN v; +END; +/ +SET sql_mode=@sql_mode; +# +# Helper routines +# +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# A cursor on a SELECT returning a local SP variable works in the caller +# +CREATE PROCEDURE p1(a INT, OUT c0 SYS_REFCURSOR) +BEGIN +OPEN c0 FOR SELECT a; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v INT; +CALL p1(10, c0); +FETCH c0 INTO v; +SELECT v; +END; +/ +CALL p2(); +v +10 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# A procedure failing on error still clears ref counters +# +# INOUT parameter +CREATE PROCEDURE p1(INOUT c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +OPEN c0 FOR SELECT 1; +FETCH c0 INTO v; +FETCH c0 INTO v; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' + BEGIN +RETURN NULL; +END; +CALL p1(c0); +RETURN c0; +END; +/ +SELECT f2(), CURSOR_REF_COUNT(0), CURSOR_REF_COUNT(1); +f2() CURSOR_REF_COUNT(0) CURSOR_REF_COUNT(1) +NULL 0 NULL +DROP PROCEDURE p1; +DROP FUNCTION f2; +# OUT parameter +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR) +BEGIN +DECLARE v INT; +OPEN c0 FOR SELECT 1; +FETCH c0 INTO v; +FETCH c0 INTO v; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' + BEGIN +RETURN NULL; +END; +CALL p1(c0); +RETURN c0; +END; +/ +SELECT f2(), CURSOR_REF_COUNT(0), CURSOR_REF_COUNT(1); +f2() CURSOR_REF_COUNT(0) CURSOR_REF_COUNT(1) +NULL 0 NULL +DROP PROCEDURE p1; +DROP FUNCTION f2; +# +# PROCEDURE + IN param + OPEN in p2 +# +CREATE PROCEDURE p1(c SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +OPEN c FOR SELECT 1; +SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +CALL p1(c); +SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 0 "SELECT 'p2-1' AS stage, c, CURSOR_REF..." +2 copen STMT.cursor[c@0] +3 stmt 0 "SELECT 'p2-2' AS stage, c, CURSOR_REF..." +4 stmt 88 "CALL p1(c)" +5 stmt 0 "SELECT 'p2-3' AS stage, c, CURSOR_REF..." +6 destruct sys_refcursor c@0 +CALL p2; +stage c cnt +p2-1 NULL NULL +stage c cnt +p2-2 0 1 +stage c cnt +p1-1 0 2 +stage c cnt +p2-3 0 1 +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +stage c cnt +/p2 - NULL +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# PROCEDURE + INOUT param + OPEN in p2 +# +CREATE PROCEDURE p1(INOUT c SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +OPEN c FOR SELECT 1; +SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +CALL p1(c); +SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 0 "SELECT 'p2-1' AS stage, c, CURSOR_REF..." +2 copen STMT.cursor[c@0] +3 stmt 0 "SELECT 'p2-2' AS stage, c, CURSOR_REF..." +4 stmt 88 "CALL p1(c)" +5 stmt 0 "SELECT 'p2-3' AS stage, c, CURSOR_REF..." +6 destruct sys_refcursor c@0 +CALL p2; +stage c cnt +p2-1 NULL NULL +stage c cnt +p2-2 0 1 +stage c cnt +p1-1 0 2 +stage c cnt +p2-3 0 1 +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +stage c cnt +/p2 - NULL +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# PROCEDURE + INOUT param + OPEN in p1 +# +CREATE PROCEDURE p1(INOUT c SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +OPEN c FOR SELECT 1; +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +CALL p1(c); +SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +1 copen STMT.cursor[c@0] +2 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 0 "SELECT 'p2-1' AS stage, c, CURSOR_REF..." +2 stmt 88 "CALL p1(c)" +3 stmt 0 "SELECT 'p2-2' AS stage, c, CURSOR_REF..." +4 destruct sys_refcursor c@0 +CALL p2; +stage c cnt +p2-1 NULL NULL +stage c cnt +p1-1 NULL NULL +stage c cnt +p1-1 0 1 +stage c cnt +p2-2 0 1 +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +stage c cnt +/p2 - NULL +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# PROCEDURE + OUT param + OPEN in p2 +# +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +OPEN c FOR SELECT 1; +SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +CALL p1(c); +SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 0 "SELECT 'p2-1' AS stage, c, CURSOR_REF..." +2 copen STMT.cursor[c@0] +3 stmt 0 "SELECT 'p2-2' AS stage, c, CURSOR_REF..." +4 stmt 88 "CALL p1(c)" +5 stmt 0 "SELECT 'p2-3' AS stage, c, CURSOR_REF..." +6 destruct sys_refcursor c@0 +CALL p2; +stage c cnt +p2-1 NULL NULL +stage c cnt +p2-2 0 1 +stage c cnt +p1-1 NULL NULL +stage c cnt +p2-3 NULL NULL +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +stage c cnt +/p2 - NULL +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# PROCEDURE + OUT param + OPEN in p1 +# +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN +SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +OPEN c FOR SELECT 1; +SELECT 'p1-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c SYS_REFCURSOR; +SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +CALL p1(c); +SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 0 "SELECT 'p1-1' AS stage, c, CURSOR_REF..." +1 copen STMT.cursor[c@0] +2 stmt 0 "SELECT 'p1-2' AS stage, c, CURSOR_REF..." +SHOW PROCEDURE CODE p2; +Pos Instruction +0 set c@0 NULL +1 stmt 0 "SELECT 'p2-1' AS stage, c, CURSOR_REF..." +2 stmt 88 "CALL p1(c)" +3 stmt 0 "SELECT 'p2-2' AS stage, c, CURSOR_REF..." +4 destruct sys_refcursor c@0 +CALL p2; +stage c cnt +p2-1 NULL NULL +stage c cnt +p1-1 NULL NULL +stage c cnt +p1-2 0 1 +stage c cnt +p2-2 0 1 +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +stage c cnt +/p2 - NULL +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Returning the same cursor into multiple formal OUT parameters +# +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR, OUT c2 SYS_REFCURSOR) +BEGIN +OPEN c0 FOR VALUES (0),(1),(2); +SET c1= c0; +SET c2= c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0, c1, c2 SYS_REFCURSOR; +DECLARE v0, v1, v2 INT; +SELECT 'p2-0' AS stage, refs(0,3) AS refs; +CALL p1(c0, c1, c2); +SELECT 'p2-1' AS stage, refs(0,3) AS refs; +FETCH c0 INTO v0; +FETCH c1 INTO v1; +FETCH c2 INTO v2; +SELECT v0, v1, v2; +SELECT 'p2-3' AS stage, refs(0,3) AS refs; +SET c0= NULL; +SELECT 'p2-40' AS stage, refs(0,3) AS refs; +SET c1= NULL; +SELECT 'p2-41' AS stage, refs(0,3) AS refs; +SET c2= NULL; +SELECT 'p2-42' AS stage, refs(0,3) AS refs; +END; +/ +CALL p2; +p2-0 [NULL NULL NULL NULL] +p2-1 [3 NULL NULL NULL] +0 1 2 +p2-3 [3 NULL NULL NULL] +p2-40 [2 NULL NULL NULL] +p2-41 [1 NULL NULL NULL] +p2-42 [0 NULL NULL NULL] +DROP PROCEDURE p1; +DROP PROCEDURE p2; +# +# Returning the same cursor into multiple formal OUT parameters. +# Passing the same variable as all actual parameters. +# +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR, OUT c2 SYS_REFCURSOR) +BEGIN +OPEN c0 FOR VALUES (0),(1),(2); +SET c1= c0; +SET c2= c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE v0, v1, v2 INT; +SELECT 'p2-0' AS stage, refs(0,3) AS refs; +CALL p1(c0, c0, c0); +SELECT 'p2-1' AS stage, refs(0,3) AS refs; +FETCH c0 INTO v0; +FETCH c0 INTO v1; +FETCH c0 INTO v2; +SELECT v0, v1, v2; +SELECT 'p2-3' AS stage, refs(0,3) AS refs; +SET c0= NULL; +SELECT 'p2-40' AS stage, refs(0,3) AS refs; +END; +/ +CALL p2; +p2-0 [NULL NULL NULL NULL] +p2-1 [1 NULL NULL NULL] +0 1 2 +p2-3 [1 NULL NULL NULL] +p2-40 [0 NULL NULL NULL] +DROP PROCEDURE p1; +DROP PROCEDURE p2; +# +# PROCEDURE + OUT param + OPEN in p1 + multiple calls +# reuse the cursor at the same position +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +CREATE PROCEDURE p1(OUT p1c0 SYS_REFCURSOR, task VARCHAR(64)) +BEGIN +SELECT 'p1-0' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +IF task LIKE '%open_p1c0%' THEN +OPEN p1c0 FOR SELECT a+100 FROM t1; +END IF; +SELECT 'p1-1' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(64)) +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +SELECT 'state', 'curs', 'c_cur', 'c_0', 'val'; +SELECT 'p2-0' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-1' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-2' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +IF task LIKE '%open_p2c0%' THEN +OPEN p2c0 FOR SELECT a+200 FROM t1; +SELECT 'p2-op' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END IF; +CALL p1(p2c0, task); +SELECT 'p2-3' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-4' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-5' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END; +/ +CALL p2(''); +state curs c_cur c_0 val +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-1 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-2 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-3 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-4 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-5 NULL NULL NULL none +CALL p2('open_p1c0'); +state curs c_cur c_0 val +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 0 1 1 100 +p2-1 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-2 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-3 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-4 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-5 0 1 1 101 +CALL p2('open_p2c0'); +state curs c_cur c_0 val +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-1 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-2 NULL NULL NULL none +p2-op 0 1 1 200 +p1-0 NULL NULL 0 none +p1-1 NULL NULL 0 none +p2-3 NULL NULL 0 none +p1-0 NULL NULL 0 none +p1-1 NULL NULL 0 none +p2-4 NULL NULL 0 none +p1-0 NULL NULL 0 none +p1-1 NULL NULL 0 none +p2-5 NULL NULL 0 none +CALL p2('open_p2c0 open_p1c0'); +state curs c_cur c_0 val +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 0 1 1 100 +p2-1 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-2 0 1 1 101 +p2-op 0 1 1 200 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-3 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-4 0 1 1 101 +p1-0 NULL NULL 0 none +p1-1 0 1 1 100 +p2-5 0 1 1 101 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +# +# PROCEDURE + INOUT param + OPEN in p1 + OPEN in p2 + multiple calls +# reuse the cursor at the same position +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +CREATE PROCEDURE p1(INOUT p1c0 SYS_REFCURSOR, task VARCHAR(64)) +BEGIN +SELECT 'p1-0' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +IF task LIKE '%open_p1c0%' THEN +OPEN p1c0 FOR SELECT a+100 FROM t1; +END IF; +SELECT 'p1-1' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(64)) +BEGIN +DECLARE p2c0 SYS_REFCURSOR; +SELECT 'state', 'curs', 'c_cur', 'c_0'; +SELECT 'p2-0' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-1' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-2' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +IF task LIKE '%open_p2c0%' THEN +OPEN p2c0 FOR SELECT a+200 FROM t1; +SELECT 'p2-op' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END IF; +CALL p1(p2c0, task); +SELECT 'p2-3' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-4' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +CALL p1(p2c0, task); +SELECT 'p2-5' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END; +/ +CALL p2(''); +state curs c_cur c_0 +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-1 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-2 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-3 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-4 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-5 NULL NULL NULL none +CALL p2('open_p1c0'); +state curs c_cur c_0 +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 0 1 1 100 +p2-1 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-2 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-3 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-4 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-5 0 1 1 101 +CALL p2('open_p2c0'); +state curs c_cur c_0 +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-1 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 NULL NULL NULL none +p2-2 NULL NULL NULL none +p2-op 0 1 1 200 +p1-0 0 2 2 201 +p1-1 0 2 2 202 +p2-3 0 1 1 203 +p1-0 0 2 2 204 +p1-1 0 2 2 205 +p2-4 0 1 1 206 +p1-0 0 2 2 207 +p1-1 0 2 2 208 +p2-5 0 1 1 209 +CALL p2('open_p2c0 open_p1c0'); +state curs c_cur c_0 +p2-0 NULL NULL NULL none +p1-0 NULL NULL NULL none +p1-1 0 1 1 100 +p2-1 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-2 0 1 1 101 +p2-op 0 1 1 200 +p1-0 0 2 2 201 +p1-1 0 2 2 100 +p2-3 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-4 0 1 1 101 +p1-0 0 2 2 102 +p1-1 0 2 2 100 +p2-5 0 1 1 101 +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; +# +# PROCEDURE + OPEN in p1 + RETURN in the middle +# +SET sql_mode=ORACLE; +CREATE PROCEDURE p1(ret_after_open_c1 BOOLEAN) AS +c0 SYS_REFCURSOR; +BEGIN +SELECT 'Enter p1' AS ``; +SELECT 'p1-0' AS stage, refs(0,3) AS refs; +OPEN c0 FOR SELECT 'c0'; +SELECT 'p1-1' AS stage, refs(0,3) AS refs; +DECLARE +c1 SYS_REFCURSOR; +BEGIN +OPEN c1 FOR SELECT 'c1'; +SELECT 'p1-2' AS stage, refs(0,3) AS refs; +IF ret_after_open_c1 THEN +RETURN; +END IF; +END; +CLOSE c0; +SELECT 'p1-3' AS stage, refs(0,3) AS refs; +END; +/ +CREATE PROCEDURE p2() AS +BEGIN +SELECT 'p2-0' AS stage, refs(0,3) AS refs; +CALL p1(FALSE); +SELECT 'p2-1' AS stage, refs(0,3) AS refs; +CALL p1(TRUE); +SELECT 'p2-2' AS stage, refs(0,3) AS refs; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set c0@1 NULL +1 stmt 0 "SELECT 'Enter p1' AS ``" +2 stmt 0 "SELECT 'p1-0' AS stage, refs(0,3) AS ..." +3 copen STMT.cursor[c0@1] +4 stmt 0 "SELECT 'p1-1' AS stage, refs(0,3) AS ..." +5 set c1@2 NULL +6 copen STMT.cursor[c1@2] +7 stmt 0 "SELECT 'p1-2' AS stage, refs(0,3) AS ..." +8 jump_if_not 10(10) ret_after_open_c1@0 +9 preturn +10 destruct sys_refcursor c1@2 +11 cclose STMT.cursor[c0@1] +12 stmt 0 "SELECT 'p1-3' AS stage, refs(0,3) AS ..." +13 destruct sys_refcursor c0@1 +SHOW PROCEDURE CODE p2; +Pos Instruction +0 stmt 0 "SELECT 'p2-0' AS stage, refs(0,3) AS ..." +1 stmt 88 "CALL p1(FALSE)" +2 stmt 0 "SELECT 'p2-1' AS stage, refs(0,3) AS ..." +3 stmt 88 "CALL p1(TRUE)" +4 stmt 0 "SELECT 'p2-2' AS stage, refs(0,3) AS ..." +CALL p2; +p2-0 [NULL NULL NULL NULL] +Enter p1 +p1-0 [NULL NULL NULL NULL] +p1-1 [1 NULL NULL NULL] +p1-2 [1 1 NULL NULL] +p1-3 [1 0 NULL NULL] +p2-1 [0 0 NULL NULL] +Enter p1 +p1-0 [0 0 NULL NULL] +p1-1 [1 0 NULL NULL] +p1-2 [1 1 NULL NULL] +p2-2 [0 0 NULL NULL] +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET sql_mode=DEFAULT; +DROP FUNCTION fetch_one_value; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.test new file mode 100644 index 00000000000..b93d2cdec8c --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-sp-debug.test @@ -0,0 +1,485 @@ +--source include/have_debug.inc +--source include/fetch_one_value.inc + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--enable_result_log +--enable_query_log + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # A cursor on a SELECT returning a local SP variable works in the caller +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(a INT, OUT c0 SYS_REFCURSOR) +BEGIN + OPEN c0 FOR SELECT a; +END; +/ +# +# Although p1's sp_rcontext is already destroyed when FETCH is executed in p2, +# the cursor opened by `SELECT a` in p1 still works +# because the cursor was materialized into a temporary table. +# +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v INT; + CALL p1(10, c0); + FETCH c0 INTO v; + SELECT v; +END; +/ +DELIMITER ;/ +CALL p2(); +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # A procedure failing on error still clears ref counters +--echo # + +--echo # INOUT parameter +DELIMITER /; +CREATE PROCEDURE p1(INOUT c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + OPEN c0 FOR SELECT 1; + FETCH c0 INTO v; + FETCH c0 INTO v; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' + BEGIN + RETURN NULL; + END; + CALL p1(c0); + RETURN c0; +END; +/ +DELIMITER ;/ +# CURSOR_REF_COUNT(0) is expected to be 0 +# Let's also check CURSOR_REF_COUNT(1) just in case. NULL is expected. +SELECT f2(), CURSOR_REF_COUNT(0), CURSOR_REF_COUNT(1); +DROP PROCEDURE p1; +DROP FUNCTION f2; + + +--echo # OUT parameter +DELIMITER /; +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR) +BEGIN + DECLARE v INT; + OPEN c0 FOR SELECT 1; + FETCH c0 INTO v; + FETCH c0 INTO v; +END; +/ +CREATE FUNCTION f2() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' + BEGIN + RETURN NULL; + END; + CALL p1(c0); + RETURN c0; +END; +/ +DELIMITER ;/ +# CURSOR_REF_COUNT(0) is expected to be 0 +# Let's also check CURSOR_REF_COUNT(1) just in case. NULL is expected. +SELECT f2(), CURSOR_REF_COUNT(0), CURSOR_REF_COUNT(1); +DROP PROCEDURE p1; +DROP FUNCTION f2; + + +--echo # +--echo # PROCEDURE + IN param + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(c SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + OPEN c FOR SELECT 1; + SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + CALL p1(c); + SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +CALL p2; +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # PROCEDURE + INOUT param + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(INOUT c SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + OPEN c FOR SELECT 1; + SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + CALL p1(c); + SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +CALL p2; +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # PROCEDURE + INOUT param + OPEN in p1 +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(INOUT c SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + OPEN c FOR SELECT 1; + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + CALL p1(c); + SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +CALL p2; +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # PROCEDURE + OUT param + OPEN in p2 +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + OPEN c FOR SELECT 1; + SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + CALL p1(c); + SELECT 'p2-3' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +CALL p2; +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # PROCEDURE + OUT param + OPEN in p1 +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT c SYS_REFCURSOR) +BEGIN + SELECT 'p1-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + OPEN c FOR SELECT 1; + SELECT 'p1-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c SYS_REFCURSOR; + SELECT 'p2-1' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; + CALL p1(c); + SELECT 'p2-2' AS stage, c, CURSOR_REF_COUNT(c) AS cnt; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +CALL p2; +SELECT '/p2' AS stage, '-' AS c, CURSOR_REF_COUNT(0) AS cnt; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Returning the same cursor into multiple formal OUT parameters +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR, OUT c2 SYS_REFCURSOR) +BEGIN + OPEN c0 FOR VALUES (0),(1),(2); + SET c1= c0; + SET c2= c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0, c1, c2 SYS_REFCURSOR; + DECLARE v0, v1, v2 INT; + SELECT 'p2-0' AS stage, refs(0,3) AS refs; + CALL p1(c0, c1, c2); + SELECT 'p2-1' AS stage, refs(0,3) AS refs; + FETCH c0 INTO v0; + FETCH c1 INTO v1; + FETCH c2 INTO v2; + SELECT v0, v1, v2; + SELECT 'p2-3' AS stage, refs(0,3) AS refs; + SET c0= NULL; + SELECT 'p2-40' AS stage, refs(0,3) AS refs; + SET c1= NULL; + SELECT 'p2-41' AS stage, refs(0,3) AS refs; + SET c2= NULL; + SELECT 'p2-42' AS stage, refs(0,3) AS refs; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2; +--enable_column_names +DROP PROCEDURE p1; +DROP PROCEDURE p2; + + +--echo # +--echo # Returning the same cursor into multiple formal OUT parameters. +--echo # Passing the same variable as all actual parameters. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(OUT c0 SYS_REFCURSOR, OUT c1 SYS_REFCURSOR, OUT c2 SYS_REFCURSOR) +BEGIN + OPEN c0 FOR VALUES (0),(1),(2); + SET c1= c0; + SET c2= c0; +END; +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE v0, v1, v2 INT; + SELECT 'p2-0' AS stage, refs(0,3) AS refs; + CALL p1(c0, c0, c0); + SELECT 'p2-1' AS stage, refs(0,3) AS refs; + FETCH c0 INTO v0; + FETCH c0 INTO v1; + FETCH c0 INTO v2; + SELECT v0, v1, v2; + SELECT 'p2-3' AS stage, refs(0,3) AS refs; + SET c0= NULL; + SELECT 'p2-40' AS stage, refs(0,3) AS refs; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2; +--enable_column_names +DROP PROCEDURE p1; +DROP PROCEDURE p2; + + +--echo # +--echo # PROCEDURE + OUT param + OPEN in p1 + multiple calls +--echo # reuse the cursor at the same position +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +DELIMITER /; +CREATE PROCEDURE p1(OUT p1c0 SYS_REFCURSOR, task VARCHAR(64)) +BEGIN + SELECT 'p1-0' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; + IF task LIKE '%open_p1c0%' THEN + OPEN p1c0 FOR SELECT a+100 FROM t1; + END IF; + SELECT 'p1-1' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(64)) +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + SELECT 'state', 'curs', 'c_cur', 'c_0', 'val'; + SELECT 'p2-0' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-1' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-2' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + + IF task LIKE '%open_p2c0%' THEN + OPEN p2c0 FOR SELECT a+200 FROM t1; + SELECT 'p2-op' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + END IF; + + CALL p1(p2c0, task); + SELECT 'p2-3' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-4' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-5' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2(''); +CALL p2('open_p1c0'); +CALL p2('open_p2c0'); +CALL p2('open_p2c0 open_p1c0'); +--enable_column_names +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; + + +--echo # +--echo # PROCEDURE + INOUT param + OPEN in p1 + OPEN in p2 + multiple calls +--echo # reuse the cursor at the same position +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +DELIMITER /; +CREATE PROCEDURE p1(INOUT p1c0 SYS_REFCURSOR, task VARCHAR(64)) +BEGIN + SELECT 'p1-0' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; + IF task LIKE '%open_p1c0%' THEN + OPEN p1c0 FOR SELECT a+100 FROM t1; + END IF; + SELECT 'p1-1' AS stage, p1c0, CURSOR_REF_COUNT(p1c0) AS c_p1c0, CURSOR_REF_COUNT(0) AS c_0, FETCH_ONE_VALUE(p1c0) AS val; +END; +/ +CREATE PROCEDURE p2(task VARCHAR(64)) +BEGIN + DECLARE p2c0 SYS_REFCURSOR; + SELECT 'state', 'curs', 'c_cur', 'c_0'; + SELECT 'p2-0' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-1' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-2' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + + IF task LIKE '%open_p2c0%' THEN + OPEN p2c0 FOR SELECT a+200 FROM t1; + SELECT 'p2-op' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + END IF; + + CALL p1(p2c0, task); + SELECT 'p2-3' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-4' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; + CALL p1(p2c0, task); + SELECT 'p2-5' AS stage, p2c0, CURSOR_REF_COUNT(p2c0) AS cnt_p2c0, CURSOR_REF_COUNT(0) AS cnt_0, FETCH_ONE_VALUE(p2c0) AS val; +END; +/ +DELIMITER ;/ +--disable_column_names +CALL p2(''); +CALL p2('open_p1c0'); +CALL p2('open_p2c0'); +CALL p2('open_p2c0 open_p1c0'); +--enable_column_names +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; + +--echo # +--echo # PROCEDURE + OPEN in p1 + RETURN in the middle +--echo # + +# sql_mode=DEFAULT does not support RETURN in procedures. + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1(ret_after_open_c1 BOOLEAN) AS + c0 SYS_REFCURSOR; +BEGIN + SELECT 'Enter p1' AS ``; + SELECT 'p1-0' AS stage, refs(0,3) AS refs; + OPEN c0 FOR SELECT 'c0'; + SELECT 'p1-1' AS stage, refs(0,3) AS refs; + DECLARE + c1 SYS_REFCURSOR; + BEGIN + OPEN c1 FOR SELECT 'c1'; + SELECT 'p1-2' AS stage, refs(0,3) AS refs; + IF ret_after_open_c1 THEN + RETURN; + END IF; + END; + CLOSE c0; + SELECT 'p1-3' AS stage, refs(0,3) AS refs; +END; +/ +CREATE PROCEDURE p2() AS +BEGIN + SELECT 'p2-0' AS stage, refs(0,3) AS refs; + CALL p1(FALSE); + SELECT 'p2-1' AS stage, refs(0,3) AS refs; + CALL p1(TRUE); + SELECT 'p2-2' AS stage, refs(0,3) AS refs; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +SHOW PROCEDURE CODE p2; +--disable_column_names +CALL p2; +--enable_column_names +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET sql_mode=DEFAULT; + +DROP FUNCTION fetch_one_value; + + +--disable_query_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.result new file mode 100644 index 00000000000..7b4e56cca23 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.result @@ -0,0 +1,108 @@ +SET NAMES utf8mb4; +# +# Helper routines +# +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Make sure a ROW variable with SYS_REFCURSOR members destructs +# its members properly when leaving a BEGIN..END block. +# +CREATE PROCEDURE p1() +BEGIN +BEGIN +DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; +OPEN c1 FOR SELECT 11; +SET r0= ROW(c0, c1); +END; -- c0, c1 should be freed by a "destruct sys_refcursor" instruction +SELECT 'p1-1' AS stage, r0.m0, r0.m1, refs(0,4) AS refs; +END; -- r0.m0, r0.m1 should be freed here by a "destruct row" instruction +BEGIN +DECLARE v0 INT; +DECLARE v1 INT; +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 20; -- Expect to reuse m_statement_cursors.at(0) +OPEN c1 FOR SELECT 21; -- Expect to reuse m_statement_cursors.at(1) +FETCH c0 INTO v0; +FETCH c1 INTO v1; +SELECT 'p1-2' AS stage, c0, c1, refs(0,4) AS refs, v0, v1; +END; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set r0@0 NULL +1 set c0@1 NULL +2 set c1@2 NULL +3 copen STMT.cursor[c0@1] +4 copen STMT.cursor[c1@2] +5 set r0@0 (c0@1,c1@2) +6 destruct sys_refcursor c1@2 +7 destruct sys_refcursor c0@1 +8 stmt 0 "SELECT 'p1-1' AS stage, r0.m0, r0.m1,..." +9 destruct row r0@0 +10 set v0@3 NULL +11 set v1@4 NULL +12 set c0@5 NULL +13 set c1@6 NULL +14 copen STMT.cursor[c0@5] +15 copen STMT.cursor[c1@6] +16 cfetch STMT.cursor[c0@5] v0@3 +17 cfetch STMT.cursor[c1@6] v1@4 +18 stmt 0 "SELECT 'p1-2' AS stage, c0, c1, refs(..." +19 destruct sys_refcursor c1@6 +20 destruct sys_refcursor c0@5 +CALL p1; +p1-1 0 1 [1 1 NULL NULL NULL] +p1-2 0 1 [1 1 NULL NULL NULL] 20 21 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 0; +OPEN c1 FOR SELECT 1; +BEGIN +DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); +DECLARE r1 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); +SELECT 'p1-1' AS stage, refs(0,5) AS refs; +SET r0=ROW(c0, c1); +SELECT 'p1-2' AS stage, refs(0,5) AS refs; +SET r1=ROW(ff0(), ff0()); +SELECT 'p1-3' AS stage, refs(0,5) AS refs; +END; +SELECT 'p1-4' AS stage, refs(0,5) AS refs; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 set c0@0 NULL +1 set c1@1 NULL +2 copen STMT.cursor[c0@0] +3 copen STMT.cursor[c1@1] +4 set r0@2 NULL +5 set r1@3 NULL +6 stmt 0 "SELECT 'p1-1' AS stage, refs(0,5) AS ..." +7 set r0@2 (c0@0,c1@1) +8 stmt 0 "SELECT 'p1-2' AS stage, refs(0,5) AS ..." +9 set r1@3 (`ff0`(),`ff0`()) +10 stmt 0 "SELECT 'p1-3' AS stage, refs(0,5) AS ..." +11 destruct row r1@3 +12 destruct row r0@2 +13 stmt 0 "SELECT 'p1-4' AS stage, refs(0,5) AS ..." +14 destruct sys_refcursor c1@1 +15 destruct sys_refcursor c0@0 +CALL p1; +p1-1 [1 1 NULL NULL NULL NULL] +p1-2 [2 2 NULL NULL NULL NULL] +p1-3 [2 2 1 1 NULL NULL] +p1-4 [1 1 0 0 NULL NULL] +SELECT 'p1-e' AS stage, refs(0,5) AS refs; +p1-e [NULL NULL NULL NULL NULL NULL] +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.test new file mode 100644 index 00000000000..db0d73aa484 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row-debug.test @@ -0,0 +1,85 @@ +--source include/have_debug.inc + +SET NAMES utf8mb4; + +--disable_query_log +--disable_result_log +--source type_sys_refcursor-helper_routines-debug-create.inc +--enable_result_log +--enable_query_log + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Make sure a ROW variable with SYS_REFCURSOR members destructs +--echo # its members properly when leaving a BEGIN..END block. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + BEGIN + DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); + BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; + OPEN c1 FOR SELECT 11; + SET r0= ROW(c0, c1); + END; -- c0, c1 should be freed by a "destruct sys_refcursor" instruction + SELECT 'p1-1' AS stage, r0.m0, r0.m1, refs(0,4) AS refs; + END; -- r0.m0, r0.m1 should be freed here by a "destruct row" instruction + BEGIN + DECLARE v0 INT; + DECLARE v1 INT; + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 20; -- Expect to reuse m_statement_cursors.at(0) + OPEN c1 FOR SELECT 21; -- Expect to reuse m_statement_cursors.at(1) + FETCH c0 INTO v0; + FETCH c1 INTO v1; + SELECT 'p1-2' AS stage, c0, c1, refs(0,4) AS refs, v0, v1; + END; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +--disable_column_names +CALL p1; +--enable_column_names +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 0; + OPEN c1 FOR SELECT 1; + BEGIN + DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); + DECLARE r1 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); + SELECT 'p1-1' AS stage, refs(0,5) AS refs; + SET r0=ROW(c0, c1); + SELECT 'p1-2' AS stage, refs(0,5) AS refs; + SET r1=ROW(ff0(), ff0()); + SELECT 'p1-3' AS stage, refs(0,5) AS refs; + END; + SELECT 'p1-4' AS stage, refs(0,5) AS refs; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +--disable_column_names +CALL p1; +SELECT 'p1-e' AS stage, refs(0,5) AS refs; +--enable_column_names +DROP PROCEDURE p1; + + +--disable_query_log +--source type_sys_refcursor-helper_routines-debug-drop.inc +--enable_query_log diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.result new file mode 100644 index 00000000000..37b80861aa2 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.result @@ -0,0 +1,102 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# ROWs with SYS_REFCURSOR fields +# +CREATE PROCEDURE p1(p1r0 ROW(c0 SYS_REFCURSOR, c1 SYS_REFCURSOR)) +BEGIN +DECLARE c0 SYS_REFCURSOR DEFAULT p1r0.c0; +DECLARE c1 SYS_REFCURSOR DEFAULT p1r0.c1; +DECLARE v0 INT; +DECLARE v1 INT; +FETCH c0 INTO v0; +FETCH c1 INTO v1; +SELECT v0, v1; +END +/ +CREATE PROCEDURE p2() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; +OPEN c1 FOR SELECT 11; +CALL p1(ROW(c0, c1)); +END; +/ +CALL p2; +v0 v1 +10 11 +DROP PROCEDURE p2; +DROP PROCEDURE p1; +# +# Fetching from a SYS_REFCURSOR variable to an explicit ROW +# +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE r0 ROW (a INT, b VARCHAR(10)); +OPEN c0 FOR SELECT 1, 'b1'; +FETCH c0 INTO r0; +SELECT r0.a, r0.b; +END; +/ +CALL p1; +r0.a r0.b +1 b1 +DROP PROCEDURE p1; +# +# Fetching from a SYS_REFCURSOR variable to an anchored ROW +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (1,'b1'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE r0 ROW TYPE OF t1; +OPEN c0 FOR SELECT * FROM t1; +FETCH c0 INTO r0; +SELECT r0.a, r0.b; +END; +/ +CALL p1; +r0.a r0.b +1 b1 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# Make sure a ROW variable with SYS_REFCURSOR members destructs +# its members properly when leaving a BEGIN..END block. +# +CREATE PROCEDURE p1() +BEGIN +BEGIN +DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); +BEGIN +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 10; +OPEN c1 FOR SELECT 11; +SET r0= ROW(c0, c1); +END; +SELECT r0.m0, r0.m1; -- Expect 0, 1 +END; -- All SYS_REFCURSORs should be freed here: c0, c1, r0.m0, r0.m1 +BEGIN +DECLARE v0 INT; +DECLARE v1 INT; +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +OPEN c0 FOR SELECT 20; -- Expect to reuse m_statement_cursors.at(0) +OPEN c1 FOR SELECT 21; -- Expect to reuse m_statement_cursors.at(1) +FETCH c0 INTO v0; +FETCH c1 INTO v1; +SELECT c0, c1, v0, v1; -- Expect 0, 1, 20, 21 +END; +END; +/ +CALL p1; +r0.m0 r0.m1 +0 1 +c0 c1 v0 v1 +0 1 20 21 +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.test new file mode 100644 index 00000000000..4348d09bc52 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-type_row.test @@ -0,0 +1,117 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +# +# Cursor operations on a ROW field are not supported yet: +# OPEN row_name.field_name; +# FETCH row_name.field_name INTO v; +# FETCH cur INTO row_name.field_name; +# CLOSE row_name.field_name; +# + +--echo # +--echo # ROWs with SYS_REFCURSOR fields +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(p1r0 ROW(c0 SYS_REFCURSOR, c1 SYS_REFCURSOR)) +BEGIN + DECLARE c0 SYS_REFCURSOR DEFAULT p1r0.c0; + DECLARE c1 SYS_REFCURSOR DEFAULT p1r0.c1; + DECLARE v0 INT; + DECLARE v1 INT; + FETCH c0 INTO v0; + FETCH c1 INTO v1; + SELECT v0, v1; +END +/ +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; + OPEN c1 FOR SELECT 11; + CALL p1(ROW(c0, c1)); +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + +--echo # +--echo # Fetching from a SYS_REFCURSOR variable to an explicit ROW +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE r0 ROW (a INT, b VARCHAR(10)); + OPEN c0 FOR SELECT 1, 'b1'; + FETCH c0 INTO r0; + SELECT r0.a, r0.b; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Fetching from a SYS_REFCURSOR variable to an anchored ROW +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (1,'b1'); +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE r0 ROW TYPE OF t1; + OPEN c0 FOR SELECT * FROM t1; + FETCH c0 INTO r0; + SELECT r0.a, r0.b; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # +--echo # Make sure a ROW variable with SYS_REFCURSOR members destructs +--echo # its members properly when leaving a BEGIN..END block. +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + BEGIN + DECLARE r0 ROW(m0 SYS_REFCURSOR, m1 SYS_REFCURSOR); + BEGIN + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 10; + OPEN c1 FOR SELECT 11; + SET r0= ROW(c0, c1); + END; + SELECT r0.m0, r0.m1; -- Expect 0, 1 + END; -- All SYS_REFCURSORs should be freed here: c0, c1, r0.m0, r0.m1 + BEGIN + DECLARE v0 INT; + DECLARE v1 INT; + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 20; -- Expect to reuse m_statement_cursors.at(0) + OPEN c1 FOR SELECT 21; -- Expect to reuse m_statement_cursors.at(1) + FETCH c0 INTO v0; + FETCH c1 INTO v1; + SELECT c0, c1, v0, v1; -- Expect 0, 1, 20, 21 + END; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.result new file mode 100644 index 00000000000..f89072125bb --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.result @@ -0,0 +1,127 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Unary operations +# +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT c0 IS NULL AS c0n1; +OPEN c0 FOR SELECT 1; +SELECT c0 IS NULL AS c0n2; +CLOSE c0; +SELECT c0 IS NULL AS c0n3; +SET c0=NULL; +SELECT c0 IS NULL AS c0n3; +END; +$$ +c0n1 +1 +c0n2 +0 +c0n3 +0 +c0n3 +1 +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT ROUND(c0); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'round' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT FLOOR(c0); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'floor' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT ABS(c0); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'abs' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT -c0; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation '-' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT NOT c0; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'not' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +SELECT c0 COLLATE latin1_bin; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'collate' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT IF (c, 'TRUE', 'FALSE'); -- can_return_bool() +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'if' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT CASE WHEN c THEN 'TRUE' ELSE 'FALSE' END; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'case' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT BIT_COUNT(c); -- can_return_int() +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'bit_count' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT FROM_UNIXTIME(c); -- can_return_decimal +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'from_unixtime' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT SEC_TO_TIME(c); -- can_return_decimal +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'sec_to_time' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT LN(c); -- can_return_real +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'ln' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT OCTET_LENGTH(c); -- can_return_str +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'octet_length' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT COERCIBILITY(c); -- can_return_str +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'coercibility' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT LAST_DAY(c); -- can_return_date +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'last_day' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT SECOND(c); -- can_return_time +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'second' +BEGIN NOT ATOMIC +DECLARE c SYS_REFCURSOR; +SELECT EXTRACT(SECOND FROM c); -- can_return_extract_source +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'extract(second)' diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.test new file mode 100644 index 00000000000..114e38950e6 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-unary_op.test @@ -0,0 +1,183 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--echo # +--echo # Unary operations +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT c0 IS NULL AS c0n1; + OPEN c0 FOR SELECT 1; + SELECT c0 IS NULL AS c0n2; + CLOSE c0; + SELECT c0 IS NULL AS c0n3; + SET c0=NULL; + SELECT c0 IS NULL AS c0n3; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT ROUND(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT FLOOR(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT ABS(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT -c0; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT NOT c0; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT c0 COLLATE latin1_bin; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT IF (c, 'TRUE', 'FALSE'); -- can_return_bool() +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT CASE WHEN c THEN 'TRUE' ELSE 'FALSE' END; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT BIT_COUNT(c); -- can_return_int() +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT FROM_UNIXTIME(c); -- can_return_decimal +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT SEC_TO_TIME(c); -- can_return_decimal +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT LN(c); -- can_return_real +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT OCTET_LENGTH(c); -- can_return_str +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT COERCIBILITY(c); -- can_return_str +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT LAST_DAY(c); -- can_return_date +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT SECOND(c); -- can_return_time +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SELECT EXTRACT(SECOND FROM c); -- can_return_extract_source +END; +$$ +DELIMITER ;$$ diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.result new file mode 100644 index 00000000000..96ea89402ab --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.result @@ -0,0 +1,52 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE TABLE t1 +( +a INT, +b INT AS (CURSOR_REF_COUNT(a)) STORED +); +ERROR HY000: Function or expression 'cursor_ref_count()' cannot be used in the GENERATED ALWAYS AS clause of `b` +CREATE TABLE t1 +( +a INT, +b BOOLEAN AS (CURSOR_REF_COUNT(a) IS NULL) STORED +); +ERROR HY000: Function or expression 'cursor_ref_count()' cannot be used in the GENERATED ALWAYS AS clause of `b` +CREATE TABLE t1 +( +a INT, +b INT AS (CURSOR_REF_COUNT(a)) VIRTUAL +); +INSERT INTO t1 (a) VALUES (0),(1),(2),(3),(4); +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; -- uses m_statement_cursors.at(0) after OPEN c0 +DECLARE c1 SYS_REFCURSOR; -- stays NULL +DECLARE c2 SYS_REFCURSOR; -- uses m_statement_cursors.at(1) after OPEN c2 +DECLARE c3 SYS_REFCURSOR; -- uses m_statement_cursors.at(1) after c3=c2 +OPEN c0 FOR SELECT 10; +OPEN c2 FOR SELECT 20; +SET c3= c2; +SELECT a, b FROM t1; +-- Make sure direct calls of CURSOR_REF_COUNT(n) return the same values +-- with the virtual column t1.b values in the previous SELECT. +SELECT +CURSOR_REF_COUNT(0) AS cnt0, +CURSOR_REF_COUNT(1) AS cnt1, +CURSOR_REF_COUNT(2) AS cnt2, +CURSOR_REF_COUNT(3) AS cnt3, +CURSOR_REF_COUNT(4) AS cnt4; +END; +/ +CALL p1; +a b +0 1 +1 2 +2 NULL +3 NULL +4 NULL +cnt0 cnt1 cnt2 cnt3 cnt4 +1 2 NULL NULL NULL +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.test new file mode 100644 index 00000000000..7343a3ed1ad --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-vcol-debug.test @@ -0,0 +1,54 @@ +--source include/have_debug.inc + +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +CREATE TABLE t1 +( + a INT, + b INT AS (CURSOR_REF_COUNT(a)) STORED +); + + +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +CREATE TABLE t1 +( + a INT, + b BOOLEAN AS (CURSOR_REF_COUNT(a) IS NULL) STORED +); + + +CREATE TABLE t1 +( + a INT, + b INT AS (CURSOR_REF_COUNT(a)) VIRTUAL +); +INSERT INTO t1 (a) VALUES (0),(1),(2),(3),(4); + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; -- uses m_statement_cursors.at(0) after OPEN c0 + DECLARE c1 SYS_REFCURSOR; -- stays NULL + DECLARE c2 SYS_REFCURSOR; -- uses m_statement_cursors.at(1) after OPEN c2 + DECLARE c3 SYS_REFCURSOR; -- uses m_statement_cursors.at(1) after c3=c2 + OPEN c0 FOR SELECT 10; + OPEN c2 FOR SELECT 20; + SET c3= c2; + SELECT a, b FROM t1; + -- Make sure direct calls of CURSOR_REF_COUNT(n) return the same values + -- with the virtual column t1.b values in the previous SELECT. + SELECT + CURSOR_REF_COUNT(0) AS cnt0, + CURSOR_REF_COUNT(1) AS cnt1, + CURSOR_REF_COUNT(2) AS cnt2, + CURSOR_REF_COUNT(3) AS cnt3, + CURSOR_REF_COUNT(4) AS cnt4; +END; +/ +DELIMITER ;/ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.result new file mode 100644 index 00000000000..c9e0f8220e8 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.result @@ -0,0 +1,39 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1; +RETURN c0; +END; +/ +CREATE VIEW v1 AS SELECT f1() AS c1; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +CREATE VIEW v1 AS SELECT f1() IS NULL AS c1; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +CREATE VIEW v1 AS SELECT 1 AS c1 WHERE f1() IS NOT NULL; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +CREATE VIEW v1 AS SELECT f1() AS c1 WHERE f1() IS NOT NULL; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +CREATE VIEW v1 AS SELECT ROW(f1(),1)=ROW(f1(),1) AS c1; +ERROR HY000: Illegal parameter data types sys_refcursor and sys_refcursor for operation '=' +CREATE PROCEDURE p1() +BEGIN +DECLARE c0 SYS_REFCURSOR; +CREATE VIEW v1 AS SELECT f1() AS c1; +END +/ +CALL p1; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +DROP PROCEDURE p1; +CREATE PROCEDURE p1(stmt TEXT) +BEGIN +DECLARE c0 SYS_REFCURSOR; +EXECUTE IMMEDIATE stmt; +END +/ +CALL p1('CREATE VIEW v1 AS SELECT 1 AS c1 WHERE f1() IS NOT NULL'); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE VIEW' +DROP PROCEDURE p1; +DROP FUNCTION f1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.test b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.test new file mode 100644 index 00000000000..1c9428a3bc1 --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor-view.test @@ -0,0 +1,57 @@ +--echo # +--echo # MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +--echo # + +DELIMITER /; + +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + RETURN c0; +END; +/ +DELIMITER ;/ + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE VIEW v1 AS SELECT f1() AS c1; + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE VIEW v1 AS SELECT f1() IS NULL AS c1; + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE VIEW v1 AS SELECT 1 AS c1 WHERE f1() IS NOT NULL; + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE VIEW v1 AS SELECT f1() AS c1 WHERE f1() IS NOT NULL; + +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +CREATE VIEW v1 AS SELECT ROW(f1(),1)=ROW(f1(),1) AS c1; + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE c0 SYS_REFCURSOR; + CREATE VIEW v1 AS SELECT f1() AS c1; +END +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1(stmt TEXT) +BEGIN + DECLARE c0 SYS_REFCURSOR; + EXECUTE IMMEDIATE stmt; +END +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p1('CREATE VIEW v1 AS SELECT 1 AS c1 WHERE f1() IS NOT NULL'); +DROP PROCEDURE p1; + + +DROP FUNCTION f1; diff --git a/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor.result b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor.result new file mode 100644 index 00000000000..80fa40627ca --- /dev/null +++ b/plugin/type_cursor/mysql-test/type_cursor/type_sys_refcursor.result @@ -0,0 +1,479 @@ +# +# MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR +# +# +# Error: SYS_REFCURSOR is not allowed in a table column +# +CREATE TABLE t1 (a SYS_REFCURSOR); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +CREATE TABLE t1 AS SELECT c0; +SHOW CREATE TABLE t1; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +SELECT c0 UNION SELECT c1; +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +BEGIN NOT ATOMIC +DECLARE c0 SYS_REFCURSOR; +DECLARE c1 SYS_REFCURSOR; +VALUES (c0), (c1); +END; +$$ +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1 AS c; +RETURN c0; +END; +$$ +SELECT * FROM (SELECT COALESCE(f1()) FROM seq_1_to_5) dt1; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +DROP FUNCTION f1; +# +# ORDER BY, GROUP BY, DISTINCT +# +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN +DECLARE c0 SYS_REFCURSOR; +OPEN c0 FOR SELECT 1 AS c; +RETURN c0; +END; +$$ +SELECT f1() FROM seq_1_to_5 ORDER BY f1(); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT f1() FROM seq_1_to_5 ORDER BY COALESCE(f1()); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT COALESCE(f1()) FROM seq_1_to_5 ORDER BY f1(); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT COALESCE(f1()) FROM seq_1_to_5 ORDER BY COALESCE(f1()); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT f1() FROM seq_1_to_5 GROUP BY f1(); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT f1() FROM seq_1_to_5 GROUP BY COALESCE(f1()); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT COALESCE(f1()) FROM seq_1_to_5 GROUP BY f1(); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT COALESCE(f1()) FROM seq_1_to_5 GROUP BY COALESCE(f1()); +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +CREATE TABLE t1 (a INT, b TIMESTAMP); +INSERT INTO t1 VALUES (1,'2018-06-19 00:00:00'); +SELECT f1() AS f, MAX(a) FROM t1 GROUP BY f; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'GROUP BY' +DROP TABLE t1; +SELECT DISTINCT f1() FROM seq_1_to_5; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +SELECT DISTINCT COALESCE(f1()) FROM seq_1_to_5; +ERROR HY000: Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' +DROP FUNCTION f1; +# +# Although SYS_REFCURSOR in a is not allowed in Oracle, +--echo # let's allow it in MariaDB. It's convenient for debug purposes. +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT c0; + OPEN c0 FOR SELECT 1; + SELECT c0; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Storing SYS_REFCURSOR expressions into user variables is not allowed +--echo # + +DELIMITER /; +CREATE FUNCTION f0() RETURNS SYS_REFCURSOR +BEGIN + DECLARE f0c0 SYS_REFCURSOR; + OPEN f0c0 FOR SELECT 1; + RETURN f0c0; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SET @a=f0(); +DROP FUNCTION f0; + + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE f0c0 SYS_REFCURSOR; + SET @a= f0c0; +END; +/ +DELIMITER ;/ + +DELIMITER /; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE f0c0 SYS_REFCURSOR; + SELECT @a:= f0c0; +END; +/ +DELIMITER ;/ + + +--echo # +--echo # OUTFILE +--echo # + +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + DECLARE c1 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + OPEN c1 FOR SELECT 2; + SELECT c0, c1 INTO OUTFILE "../../tmp/file1.txt"; +END; +$$ +DELIMITER ;$$ +--cat_file $MYSQLTEST_VARDIR/tmp/file1.txt +--remove_file $MYSQLTEST_VARDIR/tmp/file1.txt + + +--echo # +--echo # SYS_REFCURSOR is not allowed when Item_cache is needed. +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + + SELECT seq, c0 FROM seq_0_to_3 + WHERE (SELECT CASE WHEN c0 IS NOT NULL THEN c0 ELSE NULL END + FROM seq_0_to_3 WHERE c0 IS NOT NULL) IS NOT NULL; + SHOW WARNINGS; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p2; +DROP PROCEDURE p2; + + +--echo # +--echo # Error: Unknown cursors +--echo # + +DELIMITER $$; +--error ER_SP_UNDECLARED_VAR +BEGIN NOT ATOMIC + OPEN c FOR SELECT 1; +END; +$$ +DELIMITER ;$$ + + +DELIMITER $$; +--error ER_SP_CURSOR_MISMATCH +BEGIN NOT ATOMIC + CLOSE c; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_SP_CURSOR_MISMATCH +BEGIN NOT ATOMIC + DECLARE v INT; + CLOSE c INTO v; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Error: Closing a not open cursor +--echo # + +DELIMITER $$; +--error ER_SP_CURSOR_NOT_OPEN +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + CLOSE c; +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Error: Fetching from a not open cursor +--echo # + +DELIMITER $$; +--error ER_SP_CURSOR_NOT_OPEN +BEGIN NOT ATOMIC + DECLARE a INT; + DECLARE c SYS_REFCURSOR; + FETCH c INTO a; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Error: fetching beyond the available number of records +--echo # + +DELIMITER $$; +--error ER_SP_FETCH_NO_DATA +BEGIN NOT ATOMIC + DECLARE a INT; + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1; + FETCH c INTO a; + FETCH c INTO a; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # sql_mode=ORACLE: fetching beyond the available number of records +--echo # sets the %NOTFOUND attribute. +--echo # + +SET sql_mode=ORACLE; +DELIMITER $$; +DECLARE + a INT; + c SYS_REFCURSOR; +BEGIN + OPEN c FOR SELECT 1; + FETCH c INTO a; + SELECT a, c%NOTFOUND; + FETCH c INTO a; + SELECT a, c%NOTFOUND; +END; +$$ +DELIMITER ;$$ +SET sql_mode=DEFAULT; + + +--echo # +--echo # Store assignment +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + SET c=1; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SET c=i; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c SYS_REFCURSOR; + DECLARE i INT; + SET i=c; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # FETCH using a SYS_REFCURSOR as a *target* variable +--echo # + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE p1c0 SYS_REFCURSOR; + DECLARE p1c1 SYS_REFCURSOR; + OPEN p1c0 FOR SELECT 0; + OPEN p1c1 FOR SELECT 1; + FETCH p1c0 INTO p1c1; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE p1c0 SYS_REFCURSOR; + DECLARE p1c1 SYS_REFCURSOR; + OPEN p1c0 FOR SELECT 1; + FETCH p1c0 INTO p1c1; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; + + +DELIMITER /; +CREATE PROCEDURE p1() +BEGIN + DECLARE p1c0 SYS_REFCURSOR; + DECLARE p1c1 SYS_REFCURSOR; + DECLARE p1c2 SYS_REFCURSOR; + OPEN p1c0 FOR SELECT 1; + OPEN p1c1 FOR SELECT p1c0; + FETCH p1c1 INTO p1c2; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # Aggregate functions +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT MAX(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT MIN(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT SUM(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT AVG(c0); +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + SELECT VARIANCE(c0); +END; +$$ +DELIMITER ;$$ + +--echo # +--echo # Window functions +--echo # + +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +BEGIN NOT ATOMIC + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + SELECT seq, FIRST_VALUE(c0) OVER (ORDER BY seq) AS fl FROM seq_1_to_5; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # OPEN is not allowed for IN SYS_REFCURSOR parameters +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +--error ER_NOT_SUPPORTED_YET +CREATE PROCEDURE p1(p1c0 IN SYS_REFCURSOR) AS +BEGIN + OPEN p1c0 FOR SELECT 11 FROM DUAL UNION SELECT 12 FROM DUAL; +END; +/ +DELIMITER ;/ +SET sql_mode=DEFAULT; + + +--echo # +--echo # FETCH and CLOSE are allowed for IN SYS_REFCURSOR parameters +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1(p1c0 IN SYS_REFCURSOR) AS + p1c1 SYS_REFCURSOR DEFAULT p1c0; + v0 INT; + v1 INT; +BEGIN + FETCH p1c1 INTO v0; + OPEN p1c1 FOR SELECT 11 FROM DUAL UNION SELECT 12 FROM DUAL; + FETCH p1c1 INTO v1; + DBMS_OUTPUT.PUT_LINE(v0 || ' ' || v1); + CLOSE p1c0; +END; +/ +CREATE PROCEDURE p2 AS + p2c0 SYS_REFCURSOR; +BEGIN + OPEN p2c0 FOR SELECT 21 FROM DUAL UNION SELECT 22 FROM DUAL; + p1(p2c0); + DBMS_OUTPUT.PUT_LINE('p2c0%ISOPEN' ||'='|| bool_to_char(p2c0%ISOPEN)); +END; +/ +DELIMITER ;/ +CALL p2; + +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET sql_mode=DEFAULT; + + +--echo # +--echo # OPEN is allowed for an IN parameter alias +--echo # + +SET sql_mode=ORACLE; +DELIMITER /; +CREATE PROCEDURE p1(p1c0 IN SYS_REFCURSOR) AS + p1c1 SYS_REFCURSOR DEFAULT p1c0; + v0 INT; + v1 INT; +BEGIN + FETCH p1c1 INTO v0; + OPEN p1c1 FOR SELECT 11 FROM DUAL UNION SELECT 12 FROM DUAL; + FETCH p1c1 INTO v1; + DBMS_OUTPUT.PUT_LINE(v0 || ' ' || v1); + CLOSE p1c0; +END; +/ +CREATE PROCEDURE p2 AS + p2c0 SYS_REFCURSOR; +BEGIN + OPEN p2c0 FOR SELECT 21 FROM DUAL UNION SELECT 22 FROM DUAL; + p1(p2c0); + DBMS_OUTPUT.PUT_LINE('p2c0%ISOPEN' ||'='|| bool_to_char(p2c0%ISOPEN)); +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p1; +DROP PROCEDURE p2; +SET sql_mode=DEFAULT; + + +--echo # +--echo # SYS_REFCURSOR SP variables are not constants. Expressions +--echo # with SYS_REFCURSOR variables in WHERE condition do not get cached. +--echo # 'WHERE c0 IS NOT NULL' is not replaced to 'WHERE 1' +--echo # + +DELIMITER /; +CREATE PROCEDURE p2() +BEGIN + DECLARE c0 SYS_REFCURSOR; + OPEN c0 FOR SELECT 1; + + EXPLAIN EXTENDED + SELECT seq, c0 FROM seq_0_to_3 WHERE c0 IS NOT NULL; + SHOW WARNINGS; + + SELECT seq, c0 FROM seq_0_to_3 WHERE c0 IS NOT NULL; +END; +/ +DELIMITER ;/ +CALL p2; +DROP PROCEDURE p2; + +--echo # +--echo # MDEV-36409 Server crashes when creating a table using function with a return type sys_refcursor +--echo # + +DELIMITER /; +CREATE FUNCTION f1() RETURNS SYS_REFCURSOR +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT 1; + RETURN c; +END; +/ +DELIMITER ;/ +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE TABLE t1 AS SELECT f1(); +DROP FUNCTION f1; + + +--echo # +--echo # MDEV-36377 Assertion `thd->lex == sp_instr_lex' failed in LEX *sp_lex_instr::parse_expr(THD *, sp_head *, LEX *) +--echo # + +CREATE TABLE t1 (a INT); +--delimiter $ +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 SYS_REFCURSOR; + OPEN c1 FOR SELECT * FROM t1; + END; +$ +--delimiter ; +CALL p1; +CREATE OR REPLACE TABLE t1(a INT); +CALL p1; +CALL p1; +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t (a INT); +--delimiter $ +CREATE PROCEDURE p1 (OUT c sys_refcursor) +BEGIN + OPEN c FOR SELECT a FROM t ; +END; +$ +CREATE PROCEDURE p2() +BEGIN + DECLARE c sys_refcursor; + DECLARE v INT; + EXECUTE IMMEDIATE 'CALL p1 (?)' USING c; + FETCH c INTO v; + SELECT c,v; +END; +$ +--delimiter ; +--error ER_SP_FETCH_NO_DATA +CALL p2; +CREATE OR REPLACE TABLE t (a INT); +--error ER_SP_FETCH_NO_DATA +CALL p2; +--error ER_SP_FETCH_NO_DATA +CALL p2; +--error ER_SP_WRONG_NO_OF_ARGS +CALL p1; +DROP PROCEDURE p2; +DROP PROCEDURE p1; +DROP TABLE t; + + +# +# Cleanup +# + +DROP PACKAGE dbms_output; +DROP FUNCTION bool_to_char; diff --git a/plugin/type_cursor/plugin.cc b/plugin/type_cursor/plugin.cc new file mode 100644 index 00000000000..c8d2c9bd855 --- /dev/null +++ b/plugin/type_cursor/plugin.cc @@ -0,0 +1,950 @@ +/* + Copyright (c) 2023-2025, MariaDB Corporation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define MYSQL_SERVER +#include "my_global.h" +#include "sql_class.h" // THD +#include "sp_head.h" +#include "mysql/plugin_data_type.h" +#include "mysql/plugin_function.h" +#include "sp_instr.h" +#include "sql_type.h" + + +static constexpr LEX_CSTRING sys_refcursor_str= +{STRING_WITH_LEN("sys_refcursor")}; + + +class Type_collection_cursor: public Type_collection +{ +protected: + const Type_handler *aggregate_common(const Type_handler *h1, + const Type_handler *h2) const; +public: + const Type_handler *aggregate_for_result(const Type_handler *h1, + const Type_handler *h2) + const override + { + return aggregate_common(h1, h2); + } + const Type_handler *aggregate_for_comparison(const Type_handler *h1, + const Type_handler *h2) + const override + { + DBUG_ASSERT(h1 == h1->type_handler_for_comparison()); + DBUG_ASSERT(h2 == h2->type_handler_for_comparison()); + return nullptr; + } + const Type_handler *aggregate_for_min_max(const Type_handler *h1, + const Type_handler *h2) + const override + { + return nullptr; + } + const Type_handler *aggregate_for_num_op(const Type_handler *h1, + const Type_handler *h2) + const override + { + return nullptr; + } +}; + + +static Type_collection_cursor type_collection_cursor; + + +/* + Basic SYS_REFCURSOR traits +*/ +class Sys_refcursor_traits +{ +public: + + static const Type_handler *storage_type_handler() + { + return &type_handler_ushort; + } + + static const Type_limits_int & type_limits_int() + { + static const Type_limits_uint16 limits; + return limits; + } + + static enum_field_types field_type() { return MYSQL_TYPE_SHORT; } + + static uint flags() { return UNSIGNED_FLAG; } + + static protocol_send_type_t protocol_send_type() + { + return PROTOCOL_SEND_SHORT; + } + + static uint32 calc_pack_length() + { + return 2; + } + + static uint32 max_display_length_for_field() + { + return 6; + } + + static uint32 max_display_length() + { + return type_limits_int().char_length(); + } + + static uint32 Item_decimal_notation_int_digits() + { + // Used in Item_func_format + return type_limits_int().precision(); + } + + static sp_cursor_array *cursor_array(THD *thd) + { + return thd->statement_cursors(); + } +}; + + + + +class Field_sys_refcursor final :public Field_short, + public Sys_refcursor_traits +{ + + int update_to_null(bool no_conversion) + { + DBUG_ENTER("Field_sys_refcursor::update_to_null"); + /* + As SP variables cannot be NOT NULL, it's not needed to call + set_field_to_null_with_conversions() on updating to NULL. + set_null() is enough. + */ + DBUG_ASSERT(real_maybe_null()); + if (!is_null()) + { + THD *thd= get_thd(); + cursor_array(thd)->ref_count_dec(thd, (ulonglong) val_int()); + set_null(); + reset(); + } + DBUG_RETURN(0); + } + + int update_to_not_null_ref(ulonglong ref) + { + DBUG_ENTER("Field_sys_refcursor::update_to_not_null"); + THD *thd= get_thd(); + const Type_ref_null old_value= val_ref(thd); + set_notnull(); + int rc= store((longlong) ref, true/*unsigned*/); + if (!rc) + cursor_array(thd)->ref_count_update(thd, old_value, val_ref(thd)); + DBUG_RETURN(rc); + } + +public: + Field_sys_refcursor(const LEX_CSTRING &name, const Record_addr &addr, + enum utype unireg_check_arg, uint32 len_arg) + :Field_short(addr.ptr(), len_arg, addr.null_ptr(), addr.null_bit(), + Field::NONE, &name, false/*zerofill*/, true/*unsigned*/) + {} + void sql_type(String &res) const override + { + res.set_ascii(sys_refcursor_str.str, sys_refcursor_str.length); + } + const Type_handler *type_handler() const override; + /* + Field_sys_refcursor has a side effect. + Cannot use memcpy when copying data from another field. + */ + bool memcpy_field_possible(const Field *from) const override + { + return false; + } + + /* + expr_event_handler() + + This method is called at various points in time, + (for example when an SP execution is leaving a BEGIN..END block), + when the Field value needs some additional handling other than just + changing or destructing its value. + See the definition of expr_event_t for all event types. + + Details about SYS_REFCURSOR implementation + ------------------------------------------ + Suppose m_statement_cursors.at(0..4) were opened earlier, + in the upper level BEGIN..END blocks. + + BEGIN + DECLARE ref1 SYS_RECURSOR; + -- The OPEN statement below attaches the reference ref1 to + -- the next available cursor thd->m_statement_cursors.at(5) + OPEN ref1 FOR SELECT 1; + BEGIN + DECLARE ref2 DEFAULT ref1; -- one more reference to the same cursor + BEGIN + DECLARE ref3 DEFAULT ref1; -- one more reference to the same cursor + -- Here we have these relationships between variables: + -- ref1==5 ---> +------------------------------------------+ + -- ref2==5 ---> | m_statement_cursors.at(5).m_ref_count==3 | + -- ref3==5 ---> +------------------------------------------+ + END; + END; + END; + END; + + The referenced object m_statement_cursors.at(5) is not necessarily + destructed/modified every time when a referece SP variable pointing to the + object is destructed, as every object can have more than one references + declared in different BEGIN..END blocks, like in the chart above. + + A call for `expr_event_handler(thd, DESTRUCT*, 5)` detaches the + reference SP variable (e.g. ref3) from the referenced object + (e.g. m_statement_cursors.at(5)) by decrementing the reference + counter in sp_cursor_array_element::m_ref_count. + When m_ref_count gets down to zero, the sp_cursor_array_element + instance m_statement_cursors.at(5) gets closed and re-initialized + for possible new OPEN statements. + */ + + void expr_event_handler(THD *thd, expr_event_t event) override + { + if ((bool) (event & expr_event_t::DESTRUCT_ANY)) + { + update_to_null(false); + return; + } + DBUG_ASSERT(0); + } + + Type_ref_null val_ref(THD *thd) override + { + return is_null() ? Type_ref_null() : Type_ref_null((ulonglong) val_int()); + } + + int store_ref(const Type_ref_null &ref, bool no_conversions) override + { + DBUG_ENTER("Field_sys_refcursor::store_ref"); + DBUG_RETURN(ref.is_null() ? update_to_null(no_conversions) : + update_to_not_null_ref(ref.value())); + } + + bool store_item(Item *item) + { + THD *thd= get_thd(); + bool rc= store_ref(item->val_ref(thd), true/*no_conversion*/); + item->expr_event_handler(thd, expr_event_t::DESTRUCT_ASSIGNMENT_RIGHT_HAND); + return rc; + } + + bool sp_prepare_and_store_item(THD *thd, Item **value) override + { + DBUG_ENTER("Field_sys_refcursor::sp_prepare_and_store_item"); + DBUG_ASSERT(value); + Item *expr_item; + bool rc= !(expr_item= thd->sp_fix_func_item_for_assignment(this, value)) || + expr_item->check_is_evaluable_expression_or_error() || + store_item(expr_item) || + thd->is_error(); + DBUG_RETURN(rc); + } + + Field *make_new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type) + override + { + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + sys_refcursor_str.str, "CREATE TABLE"); + return nullptr; + } + +}; + + +class Type_handler_sys_refcursor final: public Type_handler_int_result, + public Sys_refcursor_traits +{ +public: + static const Type_handler *singleton(); + + /* + Data type features determined by Sys_refcursor_traits. + If we ever want to change the reference value storage type + from uint16 to something bigger, nothing in the below code + should be changed. Only Sys_refcursor_traits should be changed. + */ + uint32 max_display_length(const Item *item) const override + { + return Sys_refcursor_traits::type_limits_int().char_length(); + } + uint32 Item_decimal_notation_int_digits(const Item *item) const override + { + // Used in Item_func_format + return Sys_refcursor_traits::type_limits_int().precision(); + } + + enum_field_types field_type() const override + { + return Sys_refcursor_traits::field_type(); + } + + uint flags() const override + { + return Sys_refcursor_traits::flags(); + } + + protocol_send_type_t protocol_send_type() const override + { + return Sys_refcursor_traits::protocol_send_type(); + } + + uint32 max_display_length_for_field(const Conv_source &src) const override + { + return Sys_refcursor_traits::max_display_length_for_field(); + } + + uint32 calc_pack_length(uint32 length) const override + { + return Sys_refcursor_traits::calc_pack_length(); + } + + bool Item_send(Item *item, Protocol *protocol, st_value *buf) const override + { + return storage_type_handler()->Item_send(item, protocol, buf); + } + + bool Column_definition_fix_attributes(Column_definition *def) const override + { + return storage_type_handler()->Column_definition_fix_attributes(def); + } + + bool Column_definition_prepare_stage2(Column_definition *c, + handler *file, + ulonglong table_flags) const override + { + return storage_type_handler()->Column_definition_prepare_stage2(c, file, + table_flags); + } + + /*** Basic data type feautures ***/ + + const Type_collection *type_collection() const override + { + return &type_collection_cursor; + } + + const Type_handler *type_handler_for_comparison() const override + { + return this; + } + + bool can_return_bool() const override { return false; } + bool can_return_int() const override { return false; } + bool can_return_decimal() const override { return false; } + bool can_return_real() const override { return false; } + bool can_return_str() const override { return false; } + bool can_return_text() const override { return false; } + bool can_return_date() const override { return false; } + bool can_return_time() const override { return false; } + bool can_return_extract_source(interval_type type) const override + { + return false; + } + + bool is_complex() const override + { + return true; + } + + void Item_update_null_value(Item *item) const override + { + // This method is used by IS NULL and IS NOT NULL predicates + item->null_value= item->val_ref(current_thd).is_null(); + item->expr_event_handler(current_thd, expr_event_t::DESTRUCT_ROUTINE_ARG); + } + + bool Item_eq_value(THD *thd, const Type_cmp_attributes *attr, + Item *a, Item *b) const override + { + /* + This method is used to simplify conditions like this: + WHERE field=const1 AND field=const2 + In case of SYS_REFCURSOR we should never get to here, because: + - the comparison predicate is disallowed by + Type_collection_cursor::aggregate_for_comparison() + - Also Item_sp_variable::const_item() returns false if the item + has the COMPLEX_DATA_TYPE flag (like SYS_REFCURSOR items). + */ + DBUG_ASSERT(0); + return false; + } + + /*** Field and SP variable related methods ***/ + + bool Spvar_definition_with_complex_data_types(Spvar_definition *def) + const override + { + return true; + } + + int Item_save_in_field(Item *item, Field *field, bool no_conversions) + const override + { + Field_sys_refcursor *fc= dynamic_cast(field); + return fc ? fc->store_item(item) : + item->save_int_in_field(field, no_conversions); + } + + Field *make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *root, + const LEX_CSTRING *name, + const Record_addr &rec, const Bit_addr &bit, + const Column_definition_attributes *attr, + uint32 flags) const override + { + /* + Create a Field as a storage for an SP variable. + Note, creating a field for a real table is prevented in these methods: + - make_table_field() + - Column_definition_set_attributes() + */ + return new (root) Field_sys_refcursor(*name, rec, attr->unireg_check, + (uint32) attr->length); + } + + Field *make_table_field(MEM_ROOT *root, + const LEX_CSTRING *name, + const Record_addr &addr, + const Type_all_attributes &attr, + TABLE_SHARE *share) const override + { + // Disallow "CREATE TABLE t1 AS SELECT sys_refcursor_var;" + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + sys_refcursor_str.str, "CREATE TABLE"); + return nullptr; + } + + bool Column_definition_set_attributes(THD *thd, + Column_definition *def, + const Lex_field_type_st &attr, + column_definition_type_t type) + const override + { + // Disallow "CREATE TABLE t1 (a SYS_REFCURSOR)" + if (type == COLUMN_DEFINITION_TABLE_FIELD) + { + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + sys_refcursor_str.str, "CREATE TABLE"); + return true; + } + + DBUG_ASSERT(thd->lex->sphead); + DBUG_ASSERT(thd->lex->spcont); + + /* + Oracle returns an error on SYS_REFCURSOR variable declarations + in the top level frame of a package or a package body, + i.e. in the frame following immediately after IS/AS. + For example, this script: + CREATE PACKAGE BODY pkg AS + cur SYS_REFCURSOR; + ... functions and procedures ... + END; + returns an error: + "Cursor Variables cannot be declared as part of a package" + SYS_REFCURSOR can only appear in a package as: + - a package routine parameter + - a package function return value + - in the package body initialization section + Let's return an error on the top level, like Oracle does. + + Let's also disasallow SYS_REFCURSOR in stored aggregate functions. + */ + if ((type == COLUMN_DEFINITION_ROUTINE_LOCAL && + thd->lex->spcont->scope() == sp_pcontext::PACKAGE_BODY_SCOPE) || + thd->lex->sphead->chistics().agg_type == GROUP_AGGREGATE) + { + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), sys_refcursor_str.str); + return true; + } + return def->fix_attributes_int(MAX_SMALLINT_WIDTH + def->sign_length()); + } + + + /*** Item_param related methods ***/ + + void Item_param_set_param_func(Item_param *param, + uchar **pos, ulong len) const override + { + storage_type_handler()->Item_param_set_param_func(param, pos, len); + } + + Type_ref_null Item_param_val_ref(THD *thd, const Item_param *param) + const override + { + return param->val_ref_from_int(); + } + + bool Item_param_set_from_value(THD *thd, Item_param *param, + const Type_all_attributes *attr, + const st_value *val) const override + { + const Type_ref_null old_value= param->val_ref_from_int(); + param->unsigned_flag= attr->unsigned_flag; + param->set_int(val->value.m_longlong, attr->max_length); + const Type_ref_null new_value= Type_ref_null((ulonglong) + val->value.m_longlong); + cursor_array(thd)->ref_count_update(thd, old_value, new_value); + param->with_flags|= item_with_t::COMPLEX_DATA_TYPE; + return false; + } + + void Item_param_expr_event_handler(THD *thd, Item_param *param, + expr_event_t event) const override + { + /* + A reference stored in Item_param detaches from the object it + refers to when at the end of a prepared statement the value of ? + gets copied to the routine actual OUT or INOUT parameter, e.g.: + EXECUTE IMMEDIATE 'CALL p1_with_out_or_inout_param(?)' USING spvar; + It does not change per row, in statements like this: + EXECUTE IMMEDIATE 'SELECT ? FROM t1' USING ref_value; + So it ignores most of the expr_event_t::DESTRUCT_XXX events. + */ + if ((bool) (event & expr_event_t::DESTRUCT_DYNAMIC_PARAM)) + { + const Type_ref_null ref= param->val_ref_from_int(); + if (!ref.is_null()) + { + cursor_array(thd)->ref_count_dec(thd, ref.value()); + param->set_null(); + } + } + } + + /*** Item_func_hybrid related methods ***/ + + bool Item_hybrid_func_fix_attributes(THD *thd, + const LEX_CSTRING &name, + Type_handler_hybrid_field_type *handler, + Type_all_attributes *func, + Item **items, uint nitems) const override + { + /* + Suppress the inherited behavior which converts the data type + from *INT to NEWDECIMAL if arguments have different signess. + */ + return false; + } + + Type_ref_null Item_func_hybrid_field_type_val_ref(THD *thd, + Item_func_hybrid_field_type *item) + const override + { + DBUG_ASSERT(item->type_handler() == this); + const Type_ref_null ref= item->ref_op(thd); + item->expr_event_handler_args(thd, expr_event_t::DESTRUCT_ROUTINE_ARG); + return ref; + } + + + /*** Unary Item_func related methods ***/ + bool Item_func_round_fix_length_and_dec(Item_func_round *item) const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_func_abs_fix_length_and_dec(Item_func_abs *item) const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_func_neg_fix_length_and_dec(Item_func_neg *item) const override + { + return Item_func_or_sum_illegal_param(item); + } + + + /*** Item_sum related methods ***/ + + bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const override + { + // Convert "max(" to "max" and "min(" to "min" + const LEX_CSTRING name= func->func_name_cstring(); + DBUG_ASSERT(name.length > 0); + size_t lp= name.str[name.length - 1] == '(' ? 1 : 0; + ErrConvString str(name.str, name.length - lp, system_charset_info); + return Item_func_or_sum_illegal_param(str.lex_cstring()); + } + + bool Item_sum_sum_fix_length_and_dec(Item_sum_sum *item) const override + { + static const LEX_CSTRING name= {STRING_WITH_LEN("sum") }; + return Item_func_or_sum_illegal_param(name); + } + + bool Item_sum_avg_fix_length_and_dec(Item_sum_avg *item) const override + { + static const LEX_CSTRING name= {STRING_WITH_LEN("avg") }; + return Item_func_or_sum_illegal_param(name); + } + + bool Item_sum_variance_fix_length_and_dec(Item_sum_variance *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + Field *make_num_distinct_aggregator_field(MEM_ROOT *, const Item *) + const override + { + /* + A call for this method should never happen because + Item_sum_*_fix_length_and_dec() was called earlier and raised an error. + */ + DBUG_ASSERT(0); + return nullptr; + } + + + /*** CAST related classes and methods ***/ + + class Item_cast_sys_refcursor_to_varchar_func_handler: + public Item_handled_func::Handler_str + { + public: + static const Item_cast_sys_refcursor_to_varchar_func_handler *singleton() + { + static Item_cast_sys_refcursor_to_varchar_func_handler fh; + return &fh; + } + const Type_handler *return_type_handler(const Item_handled_func *item) + const override + { + return &type_handler_varchar; + } + bool fix_length_and_dec(Item_handled_func *item) const override + { + return false; + } + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(dynamic_cast(item)); + THD *thd= current_thd; + Item *arg= item->arguments()[0]; + const Type_ref_null ref= arg->val_ref(thd); + if ((item->null_value= ref.is_null())) + return 0; + DBUG_ASSERT(arg->with_complex_data_types()); + arg->expr_event_handler(thd, expr_event_t::DESTRUCT_ROUTINE_ARG); + to->set(ref.value(), &my_charset_latin1); + return static_cast(item)-> + val_str_generic_finalize(to, to); + } + }; + + bool Item_char_typecast_fix_length_and_dec(Item_char_typecast *item) + const override + { + item->fix_length_and_dec_numeric(); + item->set_func_handler(Item_cast_sys_refcursor_to_varchar_func_handler:: + singleton()); + return false; + } + + bool Item_func_signed_fix_length_and_dec(Item_func_signed *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_double_typecast_fix_length_and_dec(Item_double_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_float_typecast_fix_length_and_dec(Item_float_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_decimal_typecast_fix_length_and_dec(Item_decimal_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_time_typecast_fix_length_and_dec(Item_time_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_date_typecast_fix_length_and_dec(Item_date_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + bool Item_datetime_typecast_fix_length_and_dec(Item_datetime_typecast *item) + const override + { + return Item_func_or_sum_illegal_param(item); + } + + Item *create_typecast_item(THD *thd, Item *item, + const Type_cast_attributes &attr) const override + { + return NULL; + } + + + /*** Methods related to other Item types ***/ + + Item_cache *Item_get_cache(THD *thd, const Item *item) const override + { + /* + It's not clear how to maintain cursor reference counters in Item_cache. + Let's disallow all operations that need caching. + */ + static const LEX_CSTRING name= + {STRING_WITH_LEN("EXPRESSION CACHE (e.g. SUBSELECT)") }; + Item_func_or_sum_illegal_param(name); + return nullptr; + } + + Item_copy *create_item_copy(THD *thd, Item *item) const override + { + // Let's also disallow GROUP BY + static const LEX_CSTRING name= {STRING_WITH_LEN("GROUP BY") }; + Item_func_or_sum_illegal_param(name); + return nullptr; + } + + /* + Methods used by table columns. + They should not be called, as SYS_REFCURSOR is only an SP data type. + */ + bool type_can_have_auto_increment_attribute() const override + { + DBUG_ASSERT(0); + return false; + } + + bool partition_field_check(const LEX_CSTRING &, Item *item_expr) + const override + { + DBUG_ASSERT(0); + return partition_field_check_result_type(item_expr, INT_RESULT); + } + + bool partition_field_append_value(String *str, + Item *item_expr, + CHARSET_INFO *field_cs, + partition_value_print_mode_t) + const override + { + DBUG_ASSERT(0); + return true; + } + const Vers_type_handler *vers() const override + { + DBUG_ASSERT(0); + return NULL; + } + + Field *make_conversion_table_field(MEM_ROOT *root, + TABLE *table, uint metadata, + const Field *target) const override + { + DBUG_ASSERT(0); + return nullptr; + } + + Field *make_schema_field(MEM_ROOT *root, + TABLE *table, + const Record_addr &addr, + const ST_FIELD_INFO &def) const override + { + DBUG_ASSERT(0); + return nullptr; + } + +}; + + +static Type_handler_sys_refcursor type_handler_sys_refcursor; + +const Type_handler *Type_handler_sys_refcursor::singleton() +{ + return &type_handler_sys_refcursor; +} + +const Type_handler *Field_sys_refcursor::type_handler() const +{ + return &type_handler_sys_refcursor; +} + + +const Type_handler * +Type_collection_cursor::aggregate_common(const Type_handler *h1, + const Type_handler *h2) const +{ + if (h1 == h2) + return h1; + + static const Type_aggregator::Pair agg[]= + { + { + &type_handler_sys_refcursor, + &type_handler_null, + &type_handler_sys_refcursor + }, + {NULL,NULL,NULL} + }; + return Type_aggregator::find_handler_in_array(agg, h1, h2, true); +} + + +static struct st_mariadb_data_type plugin_descriptor_type_sys_refcursor= +{ + MariaDB_DATA_TYPE_INTERFACE_VERSION, + &type_handler_sys_refcursor +}; + + +/*************************************************************************/ + +#ifndef DBUG_OFF +class Item_func_cursor_ref_count :public Item_long_func, + public Sys_refcursor_traits +{ +public: + Item_func_cursor_ref_count(THD *thd, Item *pos) + :Item_long_func(thd, pos) + { } + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("cursor_ref_count") }; + return name; + } + bool const_item() const override + { + return false; + } + bool check_vcol_func_processor(void *arg) override + { + return mark_unsupported_function(func_name(), "()", arg, VCOL_SESSION_FUNC); + } + bool fix_length_and_dec(THD *thd) override + { + bool rc= Item_long_func::fix_length_and_dec(thd); + set_maybe_null(true); + return rc; + } + longlong val_int() override + { + THD *thd= current_thd; + ulonglong offset= (ulonglong) args[0]->val_int(); + if ((null_value= args[0]->null_value)) + return 0; + const ULonglong_null count= cursor_array(thd)->ref_count(offset); + return (null_value= count.is_null()) ? 0LL : (longlong) count.value(); + } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + +class Create_func_cursor_ref_count :public Create_func_arg1 +{ +public: + Item *create_1_arg(THD *thd, Item *pos) override; + static Create_func_cursor_ref_count s_singleton; +protected: + Create_func_cursor_ref_count() {} +}; + + +Create_func_cursor_ref_count Create_func_cursor_ref_count::s_singleton; + +Item* Create_func_cursor_ref_count::create_1_arg(THD *thd, Item *pos) +{ + // Disallow query cache. This also disallows partitioning + thd->lex->safe_to_cache_query= false; + return new (thd->mem_root) Item_func_cursor_ref_count(thd, pos); +} + +#define BUILDER(F) & F::s_singleton + +static Plugin_function + plugin_descriptor_function_cursor_ref_count(BUILDER(Create_func_cursor_ref_count)); + +#endif + +/*************************************************************************/ + +maria_declare_plugin(type_cursor) +{ + MariaDB_DATA_TYPE_PLUGIN, // the plugin type (see include/mysql/plugin.h) + &plugin_descriptor_type_sys_refcursor, // a pointer to the plugin descriptor + sys_refcursor_str.str, // plugin name + "MariaDB Corporation", // plugin author + "Data type SYS_REFCURSOR", // the plugin description + PLUGIN_LICENSE_GPL, // the plugin license (see include/mysql/plugin.h) + 0, // Pointer to plugin initialization function + 0, // Pointer to plugin deinitialization function + 0x0100, // Numeric version 0xAABB means AA.BB version + NULL, // Status variables + NULL, // System variables + "1.0", // String version representation + MariaDB_PLUGIN_MATURITY_EXPERIMENTAL // Maturity(see include/mysql/plugin.h)*/ +} +#ifndef DBUG_OFF +, +{ + MariaDB_FUNCTION_PLUGIN, // the plugin type (see include/mysql/plugin.h) + &plugin_descriptor_function_cursor_ref_count, // ptr to the plugin descriptor + "cursor_ref_count", // plugin name + "MariaDB Corporation", // plugin author + "Function CURSOR_REF_COUNT()",// the plugin description + PLUGIN_LICENSE_GPL, // the plugin license (see include/mysql/plugin.h) + 0, // Pointer to plugin initialization function + 0, // Pointer to plugin deinitialization function + 0x0100, // Numeric version 0xAABB means AA.BB version + NULL, // Status variables + NULL, // System variables + "1.0", // String version representation + MariaDB_PLUGIN_MATURITY_EXPERIMENTAL // Maturity(see include/mysql/plugin.h)*/ +} +#endif +maria_declare_plugin_end; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 8738c7b70d1..0243bd6e2cc 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -125,7 +125,8 @@ SET (SQL_SOURCE session_tracker.cc set_var.cc slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc - sp_rcontext.cc spatial.cc sql_acl.cc sql_analyse.cc sql_base.cc + sp_rcontext.cc sp_cursor.cc + spatial.cc sql_acl.cc sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc sql_client.cc sql_crypt.cc sql_cursor.cc sql_db.cc sql_delete.cc sql_derived.cc sql_digest.cc sql_do.cc diff --git a/sql/field.cc b/sql/field.cc index 9aa816d0d79..f8b427ea54a 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2824,7 +2824,13 @@ bool Field_row::sp_prepare_and_store_item(THD *thd, Item **value) } src->bring_value(); - DBUG_RETURN(m_table->sp_set_all_fields_from_item(thd, src)); + if (m_table->sp_set_all_fields_from_item(thd, src)) + { + set_null(); + DBUG_RETURN(true); + } + set_notnull(); + DBUG_RETURN(false); } @@ -2860,6 +2866,13 @@ void Field_row::sql_type_for_sp_returns(String &res) const } +void Field_row::expr_event_handler(THD *thd, expr_event_t event) +{ + if (m_table) + m_table->expr_event_handler(thd, event); +} + + /**************************************************************************** Functions for the Field_decimal class This is an number stored as a pre-space (or pre-zero) string diff --git a/sql/field.h b/sql/field.h index cdab1e49f19..be8abec2989 100644 --- a/sql/field.h +++ b/sql/field.h @@ -997,6 +997,36 @@ public: reset(); return 0; } + + // Store a reference value (e.g. SYS_REFCURSOR) into the field. + virtual int store_ref(const Type_ref_null &ref, bool no_conversions) + { + return 0; + } + + /* + expr_event_handler() + + Handle an expression event, such as destruction, for the Field's value. + In case of SYS_REFCURSOR, the Field's value stores the offset + of the cursor in the array Statement_rcontext::m_statement_cursors. + + Unlike set_null(), expr_event_handler(DESTRUCT*) can have a side effect + and can check the previous Field value. For example, + if the previous value of a Field_sys_refcursor field was not NULL, + Field_sys_refcursor::expr_event_handler(DESTRUCT*) decrements the cursor + reference counter and closes the cursor if the counter decremented + down to zero. + + expr_event_handler() assumes that the Field is in a deterministic state, + e.g. set_null(), which has no side effects, was earlier called. + + For details see the implementation of Field_sys_refcursor + in /plugin/type_cursor/. + */ + virtual void expr_event_handler(THD *thd, expr_event_t event) + { } + int store_time(const MYSQL_TIME *ltime) { return store_time_dec(ltime, TIME_SECOND_PART_DIGITS); } int store(const char *to, size_t length, CHARSET_INFO *cs, @@ -1053,6 +1083,15 @@ public: void mark_unused_memory_as_defined() {} #endif + /* + Get a reference value of the Field. + Field_sys_refcursor in plugins/type_cursor overrides this. + */ + virtual Type_ref_null val_ref(THD *thd) + { + return Type_ref_null(); // Return a NULL reference by default. + } + virtual double val_real()=0; virtual longlong val_int()=0; /* @@ -2691,7 +2730,7 @@ public: }; -class Field_short final :public Field_int +class Field_short :public Field_int { const Type_handler_general_purpose_int *type_handler_priv() const { @@ -5257,6 +5296,7 @@ public: bool row_create_fields(THD *thd, List *list); bool row_create_fields(THD *thd, const Spvar_definition &def); bool sp_prepare_and_store_item(THD *thd, Item **value) override; + void expr_event_handler(THD *thd, expr_event_t event) override; }; diff --git a/sql/item.cc b/sql/item.cc index 81dc36c0a2d..49c28d5e72e 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1071,6 +1071,17 @@ bool Item::check_type_traditional_scalar(const LEX_CSTRING &opname) const } +bool Item::check_type_can_return_bool(const LEX_CSTRING &opname) const +{ + const Type_handler *handler= type_handler(); + if (handler->can_return_bool()) + return false; + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + handler->name().ptr(), opname.str); + return true; +} + + bool Item::check_type_can_return_int(const LEX_CSTRING &opname) const { const Type_handler *handler= type_handler(); @@ -1708,6 +1719,8 @@ bool Item_sp_variable::fix_fields_from_item(THD *thd, Item **, const Item *it) unsigned_flag= it->unsigned_flag; base_flags|= item_base_t::FIXED; with_flags|= item_with_t::SP_VAR; + if (type_handler()->is_complex()) + with_flags|= item_with_t::COMPLEX_DATA_TYPE; if (thd->lex->current_select && thd->lex->current_select->master_unit()->item) thd->lex->current_select->master_unit()->item->with_flags|= item_with_t::SP_VAR; collation.set(it->collation.collation, it->collation.derivation); @@ -1736,6 +1749,14 @@ longlong Item_sp_variable::val_int() } +Type_ref_null Item_sp_variable::val_ref(THD *thd) +{ + DBUG_ASSERT(fixed()); + DBUG_ASSERT(!is_cond()); + return this_item()->val_ref(thd); +} + + String *Item_sp_variable::val_str(String *sp) { DBUG_ASSERT(fixed()); @@ -3167,6 +3188,7 @@ Item_sp::init_result_field(THD *thd, uint max_length, uint maybe_null, sp_result_field->null_ptr= (uchar *) null_value; sp_result_field->null_bit= 1; + sp_result_field->set_null(); // SYS_REFCURSOR needs NULL as the initial value DBUG_RETURN(FALSE); } @@ -3518,6 +3540,14 @@ bool Item_field::val_bool() } +Type_ref_null Item_field::val_ref(THD *thd) +{ + DBUG_ASSERT(fixed()); + DBUG_ASSERT(!is_cond()); + return field->val_ref(thd); +} + + my_decimal *Item_field::val_decimal(my_decimal *decimal_value) { if ((null_value= field->is_null())) @@ -5203,7 +5233,19 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it) to make sure the next mysql_stmt_execute() correctly fetches the value from the client-server protocol, using set_param_func(). + + We're here in statements like: + EXECUTE IMMEDIATE 'CALL proc(?)' USING ps_param; + where proc() is a procedure with an OUT/INOUT parameter. + At the end of EXECUTE the formal routine parameter (Item_splocal) + is copied back to the actual routine parameter, which is "this" and which + is later copied back to the prepared statement parameter ps_param. */ + if (!is_null() && with_complex_data_types()) + { + // Destruct the previous value + expr_event_handler(thd, expr_event_t::DESTRUCT_DYNAMIC_PARAM); + } if (arg->save_in_value(thd, &tmp) || set_value(thd, arg, &tmp, arg->type_handler())) { diff --git a/sql/item.h b/sql/item.h index a111e58be49..2c545963019 100644 --- a/sql/item.h +++ b/sql/item.h @@ -103,6 +103,7 @@ const char *dbug_print_item(Item *item); class Virtual_tmp_table; class sp_head; +class sp_rcontext; class Protocol; struct TABLE_LIST; void item_init(void); /* Init item functions */ @@ -414,68 +415,6 @@ typedef enum monotonicity_info /*************************************************************************/ -class sp_rcontext; - -/** - A helper class to collect different behavior of various kinds of SP variables: - - local SP variables and SP parameters - - PACKAGE BODY routine variables - - (there will be more kinds in the future) -*/ - -class Sp_rcontext_handler -{ -public: - virtual ~Sp_rcontext_handler() = default; - /** - A prefix used for SP variable names in queries: - - EXPLAIN EXTENDED - - SHOW PROCEDURE CODE - Local variables and SP parameters have empty prefixes. - Package body variables are marked with a special prefix. - This improves readability of the output of these queries, - especially when a local variable or a parameter has the same - name with a package body variable. - */ - virtual const LEX_CSTRING *get_name_prefix() const= 0; - /** - At execution time THD->spcont points to the run-time context (sp_rcontext) - of the currently executed routine. - Local variables store their data in the sp_rcontext pointed by thd->spcont. - Package body variables store data in separate sp_rcontext that belongs - to the package. - This method provides access to the proper sp_rcontext structure, - depending on the SP variable kind. - */ - virtual sp_rcontext *get_rcontext(sp_rcontext *ctx) const= 0; -}; - - -class Sp_rcontext_handler_local: public Sp_rcontext_handler -{ -public: - const LEX_CSTRING *get_name_prefix() const override; - sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; -}; - - -class Sp_rcontext_handler_package_body: public Sp_rcontext_handler -{ -public: - const LEX_CSTRING *get_name_prefix() const override; - sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; -}; - - -extern MYSQL_PLUGIN_IMPORT - Sp_rcontext_handler_local sp_rcontext_handler_local; - - -extern MYSQL_PLUGIN_IMPORT - Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; - - - class Item_equal; struct st_join_table* const NO_PARTICULAR_TAB= (struct st_join_table*)0x1; @@ -791,7 +730,9 @@ enum class item_with_t : item_flags_t SUM_FUNC= (1<<3), // If item contains a sum func SUBQUERY= (1<<4), // If item contains a subquery ROWNUM_FUNC= (1<<5), // If ROWNUM function was used - PARAM= (1<<6) // If user parameter was used + PARAM= (1<<6), // If user parameter was used + COMPLEX_DATA_TYPE= (1<<7) // If the expression is of a complex data type which + // requires special handling on destruction }; @@ -1108,6 +1049,8 @@ public: { return (bool) (with_flags & item_with_t::ROWNUM_FUNC); } inline bool with_param() const { return (bool) (with_flags & item_with_t::PARAM); } + inline bool with_complex_data_types() const + { return (bool) (with_flags & item_with_t::COMPLEX_DATA_TYPE); } inline void copy_flags(const Item *org, item_base_t mask) { base_flags= (item_base_t) (((item_flags_t) base_flags & @@ -1724,6 +1667,23 @@ public: return type_handler()->Item_val_bool(this); } + virtual Type_ref_null val_ref(THD *thd) + { + return Type_ref_null(); + } + + /* + expr_event_handler() + Performs extra handling on an Item, e.g. destruction + of the Item's value when the value is not needed any more. + See also: + - comments near expr_event_handler() in fields.h + - the definition of expr_event_t in sql_type.h + - Field_sys_refcursor::expr_event_handler() in /plugin/type_cursor/ + */ + virtual void expr_event_handler(THD *thd, expr_event_t event) + { } + bool eval_const_cond() { DBUG_ASSERT(const_item()); @@ -2521,6 +2481,7 @@ public: bool check_type_or_binary(const LEX_CSTRING &opname, const Type_handler *handler) const; bool check_type_general_purpose_string(const LEX_CSTRING &opname) const; + bool check_type_can_return_bool(const LEX_CSTRING &opname) const; bool check_type_can_return_int(const LEX_CSTRING &opname) const; bool check_type_can_return_decimal(const LEX_CSTRING &opname) const; bool check_type_can_return_real(const LEX_CSTRING &opname) const; @@ -3005,6 +2966,18 @@ public: inline uint argument_count() const { return arg_count; } inline void remove_arguments() { arg_count=0; } Sql_mode_dependency value_depends_on_sql_mode_bit_or() const; + void expr_event_handler_args(THD * thd, expr_event_t event, + uint start, uint end) + { + DBUG_ASSERT(start <= end); + DBUG_ASSERT(end <= arg_count); + for (uint i= start; i < end; i++) + args[i]->expr_event_handler(thd, event); + } + void expr_event_handler_args(THD *thd, expr_event_t event) + { + expr_event_handler_args(thd, event, 0, arg_count); + } }; @@ -3194,11 +3167,20 @@ public: my_decimal *val_decimal(my_decimal *decimal_value) override; bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override; bool val_native(THD *thd, Native *to) override; + Type_ref_null val_ref(THD *thd) override; bool is_null() override; public: void make_send_field(THD *thd, Send_field *field) override; - bool const_item() const override { return true; } + bool const_item() const override + { + /* + SP variables of tricky data types with side effects, e.g. SYS_REFCURSOR, + are not constants to avoid various item tree transformations + (e.g. by the optimizer). + */ + return !type_handler()->is_complex(); + } Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src, const Tmp_field_param *param) override @@ -3276,6 +3258,10 @@ public: { return this_item()->element_index(i); } Item** addr(uint i) override { return this_item()->addr(i); } bool check_cols(uint c) override; + const Sp_rcontext_handler *rcontext_handler() const + { + return m_rcontext_handler; + } private: bool set_value(THD *thd, sp_rcontext *ctx, Item **it) override; @@ -3752,6 +3738,7 @@ public: my_decimal *val_decimal_result(my_decimal *) override; bool val_bool_result() override; bool is_null_result() override; + Type_ref_null val_ref(THD *thd) override; bool send(Protocol *protocol, st_value *buffer) override; Load_data_outvar *get_load_data_outvar() override { return this; } bool load_data_set_null(THD *thd, const Load_data_param *param) override @@ -4087,10 +4074,10 @@ public: - Item_param::set_from_item(), for EXECUTE and EXECUTE IMMEDIATE. */ -class Item_param :public Item_basic_value, - private Settable_routine_parameter, - public Rewritable_query_parameter, - private Type_handler_hybrid_field_type +class Item_param final :public Item_basic_value, + private Settable_routine_parameter, + public Rewritable_query_parameter, + private Type_handler_hybrid_field_type { /* NO_VALUE is a special value meaning that the parameter has not been @@ -4275,6 +4262,25 @@ public: Item::cleanup(); } + Type_ref_null val_ref_from_int() const + { + // Item_param uses value.integer as a storage for not-NULL references + const longlong *addr; + if (has_no_value() || !(addr= const_ptr_longlong())) + return Type_ref_null(); + return Type_ref_null((ulonglong) *addr); + } + + Type_ref_null val_ref(THD *thd) override + { + return type_handler()->Item_param_val_ref(thd, this); + } + + void expr_event_handler(THD *thd, expr_event_t event) override + { + type_handler()->Item_param_expr_event_handler(thd, this, event); + } + Type type() const override { // Don't pretend to be a constant unless value for this item is set. diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 460fb020d5e..6e65e9c771f 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2586,6 +2586,14 @@ bool Item_func_ifnull::time_op(THD *thd, MYSQL_TIME *ltime) } +Type_ref_null Item_func_ifnull::ref_op(THD *thd) +{ + DBUG_ASSERT(fixed()); + const Type_ref_null res= args[0]->val_ref(thd); + return !res.is_null() ? res : args[1]->val_ref(thd); +} + + /** Perform context analysis of an IF item tree. @@ -3283,6 +3291,13 @@ bool Item_func_case::native_op(THD *thd, Native *to) } +Type_ref_null Item_func_case::ref_op(THD *thd) +{ + DBUG_ASSERT(fixed()); + Item *item= find_item(); + return item ? item->val_ref(thd) : Type_ref_null(); +} + bool Item_func_case::fix_fields(THD *thd, Item **ref) { bool res= Item_func::fix_fields(thd, ref); @@ -3674,6 +3689,20 @@ my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value) } +Type_ref_null Item_func_coalesce::ref_op(THD *thd) +{ + DBUG_ASSERT(fixed()); + for (uint i=0 ; i < arg_count ; i++) + { + const Type_ref_null res= args[i]->val_ref(thd); + if (!res.is_null()) + return res; + } + return Type_ref_null(); +} + + + /**************************************************************************** Classes and function for the IN operator ****************************************************************************/ diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index e0233a8f085..5a001c2041b 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -259,6 +259,8 @@ public: class Item_func_truth : public Item_bool_func { + bool check_arguments() const override + { return check_argument_types_can_return_bool(0, 1); } public: bool val_bool() override; bool fix_length_and_dec(THD *thd) override; @@ -686,6 +688,8 @@ public: class Item_func_not :public Item_bool_func { + bool check_arguments() const override + { return check_argument_types_can_return_bool(0, 1); } public: Item_func_not(THD *thd, Item *a): Item_bool_func(thd, a) {} bool val_bool() override; @@ -1186,6 +1190,7 @@ public: bool date_op(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override; bool time_op(THD *thd, MYSQL_TIME *ltime) override; bool native_op(THD *thd, Native *to) override; + Type_ref_null ref_op(THD *thd) override; bool fix_length_and_dec(THD *thd) override { if (aggregate_for_result(func_name_cstring(), args, arg_count, true)) @@ -1270,6 +1275,7 @@ public: bool date_op(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override; bool time_op(THD *thd, MYSQL_TIME *ltime) override; bool native_op(THD *thd, Native *to) override; + Type_ref_null ref_op(THD *thd) override; bool fix_length_and_dec(THD *thd) override { /* @@ -1347,11 +1353,17 @@ public: return val_native_with_conversion_from_item(thd, find_item(), to, type_handler()); } + Type_ref_null ref_op(THD *thd) override + { + return find_item()->val_ref(thd); + } }; class Item_func_if :public Item_func_case_abbreviation2_switch { + bool check_arguments() const override + { return check_argument_types_can_return_bool(0, 1); } protected: Item *find_item() const override { return args[0]->val_bool() ? args[1] : args[2]; } @@ -1458,6 +1470,15 @@ public: String *str_op(String *str) override; my_decimal *decimal_op(my_decimal *) override; bool native_op(THD *thd, Native *to) override; + Type_ref_null ref_op(THD *thd) override + { + /* + At fix_fields() type this error is raised: + Illegal parameter data type for operation 'nullif' + */ + DBUG_ASSERT(0); + return Type_ref_null(); + } bool fix_length_and_dec(THD *thd) override; bool walk(Item_processor processor, bool walk_subquery, void *arg) override; LEX_CSTRING func_name_cstring() const override @@ -2401,6 +2422,7 @@ public: bool date_op(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override; bool time_op(THD *thd, MYSQL_TIME *ltime) override; bool native_op(THD *thd, Native *to) override; + Type_ref_null ref_op(THD *thd) override; bool fix_fields(THD *thd, Item **ref) override; table_map not_null_tables() const override { return 0; } LEX_CSTRING func_name_cstring() const override @@ -2422,6 +2444,8 @@ public: */ class Item_func_case_searched: public Item_func_case { + bool check_arguments() const override + { return check_argument_types_can_return_bool(0, when_count()); } uint when_count() const { return arg_count / 2; } bool with_else() const { return arg_count % 2; } Item **else_expr_addr() const override @@ -2912,6 +2936,8 @@ public: class Item_func_like :public Item_bool_func2 { + bool check_arguments() const override + { return check_argument_types_can_return_str(0, arg_count); } // Turbo Boyer-Moore data bool canDoTurboBM; // pattern is '%abcd%' case const char* pattern; @@ -3146,6 +3172,8 @@ public: class Item_func_regex :public Item_bool_func { + bool check_arguments() const override + { return check_argument_types_can_return_str(0, arg_count); } Regexp_processor_pcre re; DTCollation cmp_collation; public: @@ -3735,10 +3763,19 @@ public: class Item_func_cursor_bool_attr: public Item_bool_func, public Cursor_ref { +protected: + THD *m_thd; public: - Item_func_cursor_bool_attr(THD *thd, const LEX_CSTRING *name, uint offset) - :Item_bool_func(thd), Cursor_ref(name, offset) + Item_func_cursor_bool_attr(THD *thd, const Cursor_ref &ref) + :Item_bool_func(thd), Cursor_ref(ref), m_thd(nullptr) { } + bool fix_fields(THD *thd, Item **ref) override + { + if (Item_bool_func::fix_fields(thd, ref)) + return true; + m_thd= thd; + return false; + } bool check_vcol_func_processor(void *arg) override { return mark_unsupported_function(func_name(), arg, VCOL_SESSION_FUNC); @@ -3753,8 +3790,8 @@ public: class Item_func_cursor_isopen: public Item_func_cursor_bool_attr { public: - Item_func_cursor_isopen(THD *thd, const LEX_CSTRING *name, uint offset) - :Item_func_cursor_bool_attr(thd, name, offset) { } + Item_func_cursor_isopen(THD *thd, const Cursor_ref &ref) + :Item_func_cursor_bool_attr(thd, ref) { } LEX_CSTRING func_name_cstring() const override { static LEX_CSTRING name= {STRING_WITH_LEN("%ISOPEN") }; @@ -3769,8 +3806,8 @@ public: class Item_func_cursor_found: public Item_func_cursor_bool_attr { public: - Item_func_cursor_found(THD *thd, const LEX_CSTRING *name, uint offset) - :Item_func_cursor_bool_attr(thd, name, offset) + Item_func_cursor_found(THD *thd, const Cursor_ref &ref) + :Item_func_cursor_bool_attr(thd, ref) { set_maybe_null(); } @@ -3788,8 +3825,8 @@ public: class Item_func_cursor_notfound: public Item_func_cursor_bool_attr { public: - Item_func_cursor_notfound(THD *thd, const LEX_CSTRING *name, uint offset) - :Item_func_cursor_bool_attr(thd, name, offset) + Item_func_cursor_notfound(THD *thd, const Cursor_ref &ref) + :Item_func_cursor_bool_attr(thd, ref) { set_maybe_null(); } diff --git a/sql/item_create.cc b/sql/item_create.cc index 05f9c65bfb3..1e8ae9b187a 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -6846,6 +6846,15 @@ Item *create_func_dyncol_get(THD *thd, Item *str, Item *num, if (likely(!(res= new (thd->mem_root) Item_dyncol_get(thd, str, num)))) return res; // Return NULL - return handler->create_typecast_item(thd, res, - Type_cast_attributes(length_dec, cs)); + if (likely((res= handler->create_typecast_item(thd, res, + Type_cast_attributes(length_dec, cs))))) + return res; + // Type cast to handler's data type does not exist + const Name name= handler->name(); + char buf[128]; + size_t length= my_snprintf(buf, sizeof(buf), "CAST(expr AS %.*s)", + (int) name.length(), name.ptr()); + my_error(ER_UNKNOWN_OPERATOR, MYF(0), + ErrConvString(buf, length, system_charset_info).ptr()); + return nullptr; } diff --git a/sql/item_func.cc b/sql/item_func.cc index f11b78af191..ebbf55efeee 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -187,6 +187,20 @@ bool Item_func::check_argument_types_traditional_scalar(uint start, } +bool Item_func::check_argument_types_can_return_bool(uint start, + uint end) const +{ + const LEX_CSTRING fname= func_name_cstring(); + for (uint i= start; i < end ; i++) + { + DBUG_ASSERT(i < arg_count); + if (args[i]->check_type_can_return_bool(fname)) + return true; + } + return false; +} + + bool Item_func::check_argument_types_can_return_int(uint start, uint end) const { @@ -369,6 +383,11 @@ Item_func::fix_fields(THD *thd, Item **ref) return TRUE; } base_flags|= item_base_t::FIXED; + if (type_handler()->is_complex()) + { + with_flags|= item_with_t::COMPLEX_DATA_TYPE; + thd->stmt_arena->with_flags_bit_or_for_complex_data_types|= with_flags; + } return FALSE; } @@ -5238,6 +5257,21 @@ Item_func_set_user_var::update() unsigned_flag ? (Type_handler *) &type_handler_ulonglong : (Type_handler *) &type_handler_slonglong, &my_charset_numeric); + if (with_complex_data_types()) + { + /* + There are no user variable methods in Type_handler yet. + Also all INT-alike expresions do not preserve the exact data type on + a user variable assignment: they all get converted into BIGINT. + Let's disallow complex data types with side effects for now, + to avoid side effect resources leaking, e.g. m_statement_cursors + elements in case of SYS_REFCURSOR. + */ + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + args[0]->type_handler()->name().lex_cstring().str, + "SET user_variable"); + DBUG_RETURN(true); + } break; } case STRING_RESULT: @@ -6936,17 +6970,22 @@ longlong Item_func_uuid_short::val_int() Last_value - return last argument. */ -void Item_func_last_value::evaluate_sideeffects() +void Item_func_last_value::evaluate_sideeffects(THD *thd) { DBUG_ASSERT(fixed() && arg_count > 0); for (uint i= 0; i < arg_count-1 ; i++) + { args[i]->val_int(); + if (with_complex_data_types()) + args[i]->expr_event_handler(thd ? thd : current_thd, + expr_event_t::DESTRUCT_ROUTINE_ARG); + } } String *Item_func_last_value::val_str(String *str) { String *tmp; - evaluate_sideeffects(); + evaluate_sideeffects(nullptr); tmp= last_value->val_str(str); null_value= last_value->null_value; return tmp; @@ -6955,7 +6994,7 @@ String *Item_func_last_value::val_str(String *str) bool Item_func_last_value::val_native(THD *thd, Native *to) { - evaluate_sideeffects(); + evaluate_sideeffects(thd); return val_native_from_item(thd, last_value, to); } @@ -6963,7 +7002,7 @@ bool Item_func_last_value::val_native(THD *thd, Native *to) longlong Item_func_last_value::val_int() { longlong tmp; - evaluate_sideeffects(); + evaluate_sideeffects(nullptr); tmp= last_value->val_int(); null_value= last_value->null_value; return tmp; @@ -6972,16 +7011,24 @@ longlong Item_func_last_value::val_int() double Item_func_last_value::val_real() { double tmp; - evaluate_sideeffects(); + evaluate_sideeffects(nullptr); tmp= last_value->val_real(); null_value= last_value->null_value; return tmp; } + +Type_ref_null Item_func_last_value::val_ref(THD *thd) +{ + evaluate_sideeffects(thd); + return last_value->val_ref(thd); +} + + my_decimal *Item_func_last_value::val_decimal(my_decimal *decimal_value) { my_decimal *tmp; - evaluate_sideeffects(); + evaluate_sideeffects(nullptr); tmp= last_value->val_decimal(decimal_value); null_value= last_value->null_value; return tmp; @@ -6990,7 +7037,7 @@ my_decimal *Item_func_last_value::val_decimal(my_decimal *decimal_value) bool Item_func_last_value::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) { - evaluate_sideeffects(); + evaluate_sideeffects(thd); bool tmp= last_value->get_date(thd, ltime, fuzzydate); null_value= last_value->null_value; return tmp; @@ -7013,46 +7060,30 @@ void Cursor_ref::print_func(String *str, const LEX_CSTRING &func_name) } -sp_cursor *Cursor_ref::get_open_cursor_or_error() -{ - THD *thd= current_thd; - sp_cursor *c= thd->spcont->get_cursor(m_cursor_offset); - DBUG_ASSERT(c); - if (!c/*safety*/ || !c->is_open()) - { - my_message(ER_SP_CURSOR_NOT_OPEN, ER_THD(thd, ER_SP_CURSOR_NOT_OPEN), - MYF(0)); - return NULL; - } - return c; -} - - bool Item_func_cursor_isopen::val_bool() { - sp_cursor *c= current_thd->spcont->get_cursor(m_cursor_offset); - DBUG_ASSERT(c != NULL); + sp_cursor *c= Sp_rcontext_handler::get_cursor(m_thd, *this); return c ? c->is_open() : 0; } bool Item_func_cursor_found::val_bool() { - sp_cursor *c= get_open_cursor_or_error(); + sp_cursor *c= Sp_rcontext_handler::get_open_cursor_or_error(m_thd, *this); return !(null_value= (!c || c->fetch_count() == 0)) && c->found(); } bool Item_func_cursor_notfound::val_bool() { - sp_cursor *c= get_open_cursor_or_error(); + sp_cursor *c= Sp_rcontext_handler::get_open_cursor_or_error(m_thd, *this); return !(null_value= (!c || c->fetch_count() == 0)) && !c->found(); } longlong Item_func_cursor_rowcount::val_int() { - sp_cursor *c= get_open_cursor_or_error(); + sp_cursor *c= Sp_rcontext_handler::get_open_cursor_or_error(m_thd, *this); return !(null_value= !c) ? c->row_count() : 0; } diff --git a/sql/item_func.h b/sql/item_func.h index e372f5212b1..3783797f142 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -46,6 +46,7 @@ protected: bool check_argument_types_traditional_scalar(uint start, uint end) const; bool check_argument_types_or_binary(const Type_handler *handler, uint start, uint end) const; + bool check_argument_types_can_return_bool(uint start, uint end) const; bool check_argument_types_can_return_int(uint start, uint end) const; bool check_argument_types_can_return_real(uint start, uint end) const; bool check_argument_types_can_return_str(uint start, uint end) const; @@ -985,6 +986,12 @@ public: return Item_func_hybrid_field_type::type_handler()-> Item_func_hybrid_field_type_val_int(this); } + Type_ref_null val_ref(THD *thd) override + { + DBUG_ASSERT(fixed()); + return Item_func_hybrid_field_type::type_handler()-> + Item_func_hybrid_field_type_val_ref(thd, this); + } my_decimal *val_decimal(my_decimal *dec) override { DBUG_ASSERT(fixed()); @@ -1083,6 +1090,8 @@ public: virtual bool time_op(THD *thd, MYSQL_TIME *res)= 0; virtual bool native_op(THD *thd, Native *native)= 0; + + virtual Type_ref_null ref_op(THD *thd)= 0; }; @@ -1156,6 +1165,11 @@ public: DBUG_ASSERT(0); return true; } + Type_ref_null ref_op(THD *thd) override + { + DBUG_ASSERT(0); + return Type_ref_null(); + } }; @@ -1344,14 +1358,16 @@ public: }; -class Cursor_ref +class Cursor_ref: public sp_rcontext_ref { protected: LEX_CSTRING m_cursor_name; - uint m_cursor_offset; - class sp_cursor *get_open_cursor_or_error(); - Cursor_ref(const LEX_CSTRING *name, uint offset) - :m_cursor_name(*name), m_cursor_offset(offset) +public: + Cursor_ref(const LEX_CSTRING *name, + const Sp_rcontext_handler *h, uint offset, + const Sp_rcontext_handler *deref_rcontext_handler) + :sp_rcontext_ref(sp_rcontext_addr(h, offset), deref_rcontext_handler), + m_cursor_name(*name) { } void print_func(String *str, const LEX_CSTRING &func_name); }; @@ -1361,12 +1377,21 @@ protected: class Item_func_cursor_rowcount: public Item_longlong_func, public Cursor_ref { +protected: + THD *m_thd; public: - Item_func_cursor_rowcount(THD *thd, const LEX_CSTRING *name, uint offset) - :Item_longlong_func(thd), Cursor_ref(name, offset) + Item_func_cursor_rowcount(THD *thd, const Cursor_ref &ref) + :Item_longlong_func(thd), Cursor_ref(ref), m_thd(nullptr) { set_maybe_null(); } + bool fix_fields(THD *thd, Item **ref) override + { + if (Item_longlong_func::fix_fields(thd, ref)) + return true; + m_thd= thd; + return false; + } LEX_CSTRING func_name_cstring() const override { static LEX_CSTRING name= {STRING_WITH_LEN("%ROWCOUNT") }; @@ -2116,6 +2141,11 @@ public: } bool fix_length_and_dec(THD *thd) override; String *str_op(String *str) override { DBUG_ASSERT(0); return 0; } + Type_ref_null ref_op(THD *thd) override + { + DBUG_ASSERT(0); + return Type_ref_null(); + } bool native_op(THD *thd, Native *to) override; }; @@ -2187,6 +2217,11 @@ public: DBUG_ASSERT(0); return NULL; } + Type_ref_null ref_op(THD *thd) override + { + DBUG_ASSERT(0); + return Type_ref_null(); + } void fix_arg_decimal(); void fix_arg_int(const Type_handler *preferred, const Type_std_attributes *preferred_attributes, @@ -4102,9 +4137,14 @@ public: const Tmp_field_param *param) override; Field *create_field_for_create_select(MEM_ROOT *root, TABLE *table) override { - return result_type() != STRING_RESULT ? - sp_result_field : - create_table_field_from_handler(root, table); + /* + The below call makes execution go through + type_handler()->make_table_field() which in case of SYS_REFCURSOR: + CREATE TABLE t1 AS SELECT stored_function_returning_sys_refcursor(); + produces an error: + Illegal parameter data type sys_refcursor for operation 'CREATE TABLE' + */ + return create_table_field_from_handler(root, table); } void make_send_field(THD *thd, Send_field *tmp_field) override; @@ -4162,6 +4202,20 @@ public: return (null_value= sp_result_field->val_native(to)); } + Type_ref_null val_ref(THD *thd) override + { + const Type_ref_null ref= execute() ? Type_ref_null() : + sp_result_field->val_ref(thd); + if (with_complex_data_types()) + expr_event_handler_args(thd, expr_event_t::DESTRUCT_ROUTINE_ARG); + return ref; + } + void expr_event_handler(THD *thd, expr_event_t event) override + { + if (with_complex_data_types()) + sp_result_field->expr_event_handler(thd, event); + } + void update_null_value() override { execute(); @@ -4322,6 +4376,7 @@ public: my_decimal *val_decimal(my_decimal *) override; bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate) override; bool val_native(THD *thd, Native *) override; + Type_ref_null val_ref(THD *thd) override; bool fix_length_and_dec(THD *thd) override; LEX_CSTRING func_name_cstring() const override { @@ -4340,7 +4395,7 @@ public: return false; } bool const_item() const override { return 0; } - void evaluate_sideeffects(); + void evaluate_sideeffects(THD *thd); void update_used_tables() override { Item_func::update_used_tables(); diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 4f9c00466af..495a387ed8b 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -344,6 +344,8 @@ public: class Item_func_concat :public Item_str_func { + bool check_arguments() const override + { return check_argument_types_can_return_str(0, arg_count); } protected: String tmp_value; /* @@ -382,6 +384,8 @@ public: */ class Item_func_concat_operator_oracle :public Item_func_concat { + bool check_arguments() const override + { return check_argument_types_can_return_str(0, arg_count); } public: Item_func_concat_operator_oracle(THD *thd, List &list) :Item_func_concat(thd, list) @@ -433,6 +437,8 @@ public: class Item_func_concat_ws :public Item_str_func { + bool check_arguments() const override + { return check_argument_types_can_return_str(0, arg_count); } String tmp_value; public: Item_func_concat_ws(THD *thd, List &list): Item_str_func(thd, list) {} @@ -2011,6 +2017,8 @@ public: class Item_func_set_collation :public Item_str_func { Lex_extended_collation_st m_set_collation; + bool check_arguments() const override + { return check_argument_types_can_return_str(0, 1); } public: Item_func_set_collation(THD *thd, Item *a, const Lex_extended_collation_st &set_collation): diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 74196e5addd..3ffb99a2258 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -4251,6 +4251,9 @@ Item_func_group_concat::fix_fields(THD *thd, Item **ref) return TRUE; /* We should ignore FIELD's in arguments to sum functions */ with_flags|= (args[i]->with_flags & ~item_with_t::FIELD); + if (args[i]->check_type_can_return_str( + Item_func_group_concat::func_name_cstring())) + return true; } /* skip charset aggregation for order columns */ diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 16c0a56a3e0..9c5593db1cf 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -3197,14 +3197,25 @@ String *Item_char_typecast::val_str_generic(String *str) DBUG_ASSERT(fixed()); String *res; - if (has_explicit_length()) - cast_length= adjusted_length_with_warn(cast_length); - if (!(res= args[0]->val_str(str))) { null_value= 1; return 0; } + return val_str_generic_finalize(res, str); +} + + +/* + Adjust the result of: res= args[0]->val_str(str); + according to the cast length. + @param res - the value returned from val_str() + @param str - the value passed to val_str() as a buffer. +*/ +String *Item_char_typecast::val_str_generic_finalize(String *res, String *str) +{ + if (has_explicit_length()) + cast_length= adjusted_length_with_warn(cast_length); if (cast_cs == &my_charset_bin && has_explicit_length() && diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 8aee9f1ca0a..58aef1728d6 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -1397,6 +1397,7 @@ public: } CHARSET_INFO *cast_charset() const { return cast_cs; } String *val_str_generic(String *a); + String *val_str_generic_finalize(String *res, String *str); String *val_str_binary_from_native(String *a); void fix_length_and_dec_generic(); void fix_length_and_dec_numeric(); diff --git a/sql/protocol.cc b/sql/protocol.cc index 1af6a5ed729..aba884bb634 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -1690,6 +1690,7 @@ bool Protocol_text::send_out_parameters(List *sp_params) DBUG_ASSERT(sparam->get_item_param() == NULL); sparam->set_value(thd, thd->spcont, reinterpret_cast(&item_param)); + item_param->expr_event_handler(thd, expr_event_t::DESTRUCT_DYNAMIC_PARAM); } return FALSE; @@ -1940,6 +1941,7 @@ bool Protocol_binary::send_out_parameters(List *sp_params) if (!item_param->get_out_param_info()) continue; // It's an IN-parameter. + item_param->expr_event_handler(thd, expr_event_t::DESTRUCT_DYNAMIC_PARAM); if (out_param_lst.push_back(item_param, thd->mem_root)) return TRUE; } diff --git a/sql/select_result.h b/sql/select_result.h new file mode 100644 index 00000000000..4bc10503885 --- /dev/null +++ b/sql/select_result.h @@ -0,0 +1,184 @@ +/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. + Copyright (c) 2009, 2025, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +#ifndef SELECT_RESULT_INCLUDED +#define SELECT_RESULT_INCLUDED + +/* Pure interface for sending tabular data */ +class select_result_sink: public Sql_alloc +{ +public: + THD *thd; + select_result_sink(THD *thd_arg): thd(thd_arg) {} + int send_data_with_check(List &items, + SELECT_LEX_UNIT *u, + ha_rows sent); + /* + send_data returns 0 on ok, 1 on error and -1 if data was ignored, for + example for a duplicate row entry written to a temp table. + */ + virtual int send_data(List &items)=0; + virtual ~select_result_sink() = default; + // Used in cursors to initialize and reset + void reinit(THD *thd_arg) { thd= thd_arg; } +}; + +class select_result_interceptor; + +/* + Interface for sending tabular data, together with some other stuff: + + - Primary purpose seems to be sending typed tabular data: + = the DDL is sent with send_fields() + = the rows are sent with send_data() + Besides that, + - there seems to be an assumption that the sent data is a result of + SELECT_LEX_UNIT *unit, + - nest_level is used by SQL parser +*/ + +class select_result :public select_result_sink +{ +protected: + /* + All descendant classes have their send_data() skip the first + unit->offset_limit_cnt rows sent. Select_materialize + also uses unit->get_column_types(). + */ + SELECT_LEX_UNIT *unit; + /* Something used only by the parser: */ +public: + ha_rows est_records; /* estimated number of records in the result */ + select_result(THD *thd_arg): select_result_sink(thd_arg), est_records(0) {} + void set_unit(SELECT_LEX_UNIT *unit_arg) { unit= unit_arg; } + virtual ~select_result() = default; + /** + Change wrapped select_result. + + Replace the wrapped result object with new_result and call + prepare() and prepare2() on new_result. + + This base class implementation doesn't wrap other select_results. + + @param new_result The new result object to wrap around + + @retval false Success + @retval true Error + */ + virtual bool change_result(select_result *new_result) + { + return false; + } + virtual int prepare(List &list, SELECT_LEX_UNIT *u) + { + unit= u; + return 0; + } + virtual int prepare2(JOIN *join) { return 0; } + /* + Because of peculiarities of prepared statements protocol + we need to know number of columns in the result set (if + there is a result set) apart from sending columns metadata. + */ + virtual uint field_count(List &fields) const + { return fields.elements; } + virtual bool send_result_set_metadata(List &list, uint flags)=0; + virtual bool initialize_tables (JOIN *join) { return 0; } + virtual bool send_eof()=0; + /** + Check if this query returns a result set and therefore is allowed in + cursors and set an error message if it is not the case. + + @retval FALSE success + @retval TRUE error, an error message is set + */ + virtual bool check_simple_select() const; + virtual void abort_result_set() {} + virtual void reset_for_next_ps_execution(); + void set_thd(THD *thd_arg) { thd= thd_arg; } + void reinit(THD *thd_arg) + { + select_result_sink::reinit(thd_arg); + unit= NULL; + } +#ifdef EMBEDDED_LIBRARY + virtual void begin_dataset() {} +#else + void begin_dataset() {} +#endif + virtual void update_used_tables() {} + + /* this method is called just before the first row of the table can be read */ + virtual void prepare_to_read_rows() {} + + void remove_offset_limit() + { + unit->lim.remove_offset(); + } + + /* + This returns + - NULL if the class sends output row to the client + - this if the output is set elsewhere (a file, @variable, or table). + */ + virtual select_result_interceptor *result_interceptor()=0; + + /* + This method is used to distinguish an normal SELECT from the cursor + structure discovery for cursor%ROWTYPE routine variables. + If this method returns "true", then a SELECT execution performs only + all preparation stages, but does not fetch any rows. + */ + virtual bool view_structure_only() const { return false; } +}; + + + +/* + Base class for select_result descendants which intercept and + transform result set rows. As the rows are not sent to the client, + sending of result set metadata should be suppressed as well. +*/ + +class select_result_interceptor: public select_result +{ +public: + select_result_interceptor(THD *thd_arg): + select_result(thd_arg), suppress_my_ok(false) + { + DBUG_ENTER("select_result_interceptor::select_result_interceptor"); + DBUG_PRINT("enter", ("this %p", this)); + DBUG_VOID_RETURN; + } /* Remove gcc warning */ + uint field_count(List &fields) const override { return 0; } + bool send_result_set_metadata(List &fields, uint flag) override { return FALSE; } + select_result_interceptor *result_interceptor() override { return this; } + + /* + Instruct the object to not call my_ok(). Client output will be handled + elsewhere. (this is used by ANALYZE $stmt feature). + */ + void disable_my_ok_calls() { suppress_my_ok= true; } + void reinit(THD *thd_arg) + { + select_result::reinit(thd_arg); + suppress_my_ok= false; + } +protected: + bool suppress_my_ok; +}; + +#endif // SELECT_RESULT_INCLUDED diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 57cd08db99e..29c8d86c47d 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12299,3 +12299,5 @@ ER_SIGNAL_SKIP_ROW_FROM_TRIGGER eng "The row is skipped by a trigger implementation" ER_TEMPORARY_TABLES_PREVENT_SWITCH_GTID_DOMAIN_ID eng "Cannot modify @@session.gtid_domain_id while there are open temporary tables being binlogged" +ER_TOO_MANY_OPEN_CURSORS + eng "Too many open cursors; max %u cursors allowed" diff --git a/sql/sp_cursor.cc b/sql/sp_cursor.cc new file mode 100644 index 00000000000..62b1d1c5d33 --- /dev/null +++ b/sql/sp_cursor.cc @@ -0,0 +1,74 @@ +/* + Copyright (c) 2025, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + + +#ifdef MYSQL_SERVER +#include "mariadb.h" +#include "sql_class.h" + + +/* + Append a new element into the array. + @return NULL Type_ref_null (with is_null()==true) on error (EOM). + @return not-NULL Type_ref_null on success +*/ +Type_ref_null sp_cursor_array::append(THD *thd) +{ + if (Dynamic_array::append(sp_cursor_array_element())) + { + DBUG_ASSERT(thd->is_error()); + return Type_ref_null(); + } + return Type_ref_null((ulonglong) size() - 1); +} + + +/* + Find a cursor by reference. + @param thd - the current THD + @param ref - the field behind a SYS_REFCURSOR SP variable + @param for_open - if the cursor is needed for OPEN rather than FETCH/CLOSE, + so a new cursor is appended if not found. +*/ +sp_cursor_array_element *sp_cursor_array::get_cursor_by_ref(THD *thd, + Field *ref_field, + bool for_open) +{ + Type_ref_null ref= ref_field->val_ref(thd); + if (ref < (ulonglong) elements()) + return &at((size_t) ref.value());// "ref" points to an initialized sp_cursor + + if (!for_open) + return nullptr; + + /* + We are here when: + - The reference ref.is_null() is true, meaning that the + ref_field's SP variable is not linked to any cursors in "this" array: + * it is the very first "OPEN .. FOR STMT" command for ref_field. + * or the ref_field's SP variable was set to NULL explicitly. + - Or ref_field for some reasons returned a cursor offset outside + or the range [0..elements()-1]. + */ + if (((ref= find_unused()).is_null() && (ref= append(thd)).is_null()) || + ref_field->store_ref(ref, true/*no_conversions*/)) + return nullptr; + + at((size_t) ref.value()).reset(thd, 1/*ref count*/); + return &at((size_t) ref.value()); +} + +#endif // MYSQL_SERVER diff --git a/sql/sp_cursor.h b/sql/sp_cursor.h new file mode 100644 index 00000000000..7e1fb3ebe80 --- /dev/null +++ b/sql/sp_cursor.h @@ -0,0 +1,286 @@ +/* + Copyright (c) 2009, 2025, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +#ifndef SP_CURSOR_INCLUDED +#define SP_CURSOR_INCLUDED + +#include "sp_rcontext_handler.h" + +class Server_side_cursor; + +class sp_cursor_statistics +{ +protected: + 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 +public: + sp_cursor_statistics() + :m_fetch_count(0), + m_row_count(0), + m_found(false) + { } + bool found() const + { return m_found; } + + ulonglong row_count() const + { return m_row_count; } + + ulonglong fetch_count() const + { return m_fetch_count; } + void reset() { *this= sp_cursor_statistics(); } +}; + + +class sp_instr_cpush; + +/* A mediator between stored procedures and server side cursors */ +class sp_lex_keeper; +class sp_cursor: public sp_cursor_statistics +{ +private: + /// An interceptor of cursor result set used to implement + /// FETCH INTO . + class Select_fetch_into_spvars: public select_result_interceptor + { + List *m_fetch_target_list; + uint field_count; + bool m_view_structure_only; + bool send_data_to_variable_list(List &vars, + List &items); + public: + Select_fetch_into_spvars(THD *thd_arg, bool view_structure_only) + :select_result_interceptor(thd_arg), + m_view_structure_only(view_structure_only) + {} + void reset(THD *thd_arg) + { + select_result_interceptor::reinit(thd_arg); + m_fetch_target_list= NULL; + field_count= 0; + } + uint get_field_count() { return field_count; } + void set_spvar_list(List *vars) + { + m_fetch_target_list= vars; + } + + bool send_eof() override { return FALSE; } + int send_data(List &items) override; + int prepare(List &list, SELECT_LEX_UNIT *u) override; + bool view_structure_only() const override { return m_view_structure_only; } +}; + +public: + sp_cursor() + :result(NULL, false), + server_side_cursor(NULL) + { } + sp_cursor(THD *thd_arg, bool view_structure_only) + :result(thd_arg, view_structure_only), + server_side_cursor(NULL) + {} + + virtual ~sp_cursor() + { destroy(); } + + virtual sp_lex_keeper *get_lex_keeper() { return nullptr; } + + int open(THD *thd, bool check_max_open_cursor_counter= true); + + int close(THD *thd); + + my_bool is_open() const + { return MY_TEST(server_side_cursor); } + + int fetch(THD *, List *vars, bool error_on_no_data); + + bool export_structure(THD *thd, Row_definition_list *list); + + void reset(THD *thd_arg) + { + sp_cursor_statistics::reset(); + result.reinit(thd_arg); + server_side_cursor= NULL; + } + + /* + Reset a cursor before reopening (two OPEN without CLOSE in between). + This method does not raise ER_SP_CURSOR_ALREADY_OPEN. + It's used to handle: + c SYS_REFCURSOR; + OPEN c FOR SELECT 1; + OPEN c FOR SELECT 2; -- This is allowed without closing the previous OPEN + */ + void reset_for_reopen(THD *thd_arg) + { + if (is_open()) + close(thd_arg); + reset(thd_arg); + } + + virtual sp_instr_cpush *get_push_instr() { return nullptr; } +private: + Select_fetch_into_spvars result; + Server_side_cursor *server_side_cursor; + void destroy(); +}; + + +class sp_cursor_array_element: public sp_cursor +{ + uint m_ref_count; +public: + sp_cursor_array_element() + :sp_cursor(), + m_ref_count(0) + { } + uint ref_count() const { return m_ref_count; } + void ref_count_inc() { m_ref_count++; } + void ref_count_dec(THD *thd) + { + /* + For performance purposes, the SP instructions in sp_head::m_instr + do not guarantee that the number of ref_cursor_inc() calls matches the + number of ref_cursor_dec() calls: + + We don't add sp_instr_destruct_variable instructions in these cases: + - before sp_instr_freturn and sp_instr_preturn + - after the very last instruction + (the one before the END of the most outer stored routine block) + So sp_head::execute() can leave with some SYS_REFCURORs variables + still attached to thd->m_statement_cursor elements. + + Later they get detached by the sp_rcontext::sp_variable_detach_all() + calls in sp_head::execute_procedure() and sp_head::execute_function(). + Executing a bunch of sp_instr_destruct_variable instructions would + be more expensive. + */ + if (m_ref_count > 0) + { + m_ref_count--; + if (!m_ref_count && is_open()) + close(thd); + } + } + void reset(THD *thd, uint ref_count) + { + sp_cursor::reset(thd); + m_ref_count= ref_count; + } +}; + + +class sp_cursor_array: public Dynamic_array +{ +protected: + Type_ref_null find_unused() + { + for (size_t i= 0 ; i < size(); i++) + { + if (!at(i).is_open() && !at(i).ref_count()) + return Type_ref_null((ulonglong) i); + } + return Type_ref_null(); + } + + Type_ref_null append(THD *thd); + +public: + sp_cursor_array() + :Dynamic_array(PSI_INSTRUMENT_MEM, 0) + {} + ~sp_cursor_array() + { + free(current_thd); + } + + ULonglong_null ref_count(ulonglong offset) const + { + return offset < elements() ? + ULonglong_null((ulonglong) at((size_t) offset).ref_count()) : + ULonglong_null(); + } + + void ref_count_inc(ulonglong offset) + { + if (offset < elements()) + at((size_t) offset).ref_count_inc(); + } + + void ref_count_dec(THD *thd, ulonglong offset) + { + if (offset < elements()) + at((size_t) offset).ref_count_dec(thd); + } + + void ref_count_update(THD *thd, const Type_ref_null &old_value, + const Type_ref_null &new_value) + { + if (old_value.is_null()) + { + if (!new_value.is_null()) + ref_count_inc(new_value.value()); + } + else if (new_value.is_null()) + { + ref_count_dec(thd, old_value.value()); + } + else if (old_value.value() != new_value.value()) + { + ref_count_dec(thd, old_value.value()); + ref_count_inc(new_value.value()); + } + } + + /* + Find a cursor at the offset specified by "ref". + @param thd - current thd + @param ref - the field containing the cursor offset + @param for_open - tells if the cursor is needed for OPEN or + for FETCH/CLOSE and determines the behaviour + on dereference failure. + + Dereference failure means either of these: + - ref->is_null() returned true. + This happens when the reference SYS_REFCURSOR variable + owning the Field "ref" is not assigned to any cursors yet. + - ref->val_int() returned an offset greater than elements()-1. + This can mean that something went wrong in the code. + + If dereference failed, then: + - In case for_open is false the function returns nullptr. + - In case for_open is true, the function searches for an unused cursor. + If all cursors are used, it appends a new cursor to the end of the array. + */ + sp_cursor_array_element *get_cursor_by_ref(THD *thd, Field *ref, + bool for_open); + void close(THD *thd) + { + for (uint i= 0; i < (uint) size(); i++) + { + if (at(i).is_open()) + at(i).close(thd); + } + } + void free(THD *thd) + { + close(thd); + free_memory(); + } +}; + +#endif // SP_CURSOR_INCLUDED diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 2d92b295930..2a78f1cb002 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -76,6 +76,10 @@ void init_sp_psi_keys() PSI_server->register_statement(category, & sp_instr_cursor_copy_struct::psi_info, 1); PSI_server->register_statement(category, & sp_instr_error::psi_info, 1); PSI_server->register_statement(category, & sp_instr_set_case_expr::psi_info, 1); + PSI_server->register_statement(category, & sp_instr_copen_by_ref::psi_info, 1); + PSI_server->register_statement(category, & sp_instr_cclose_by_ref::psi_info, 1); + PSI_server->register_statement(category, & sp_instr_cfetch_by_ref::psi_info, 1); + PSI_server->register_statement(category, & sp_instr_destruct_variable::psi_info, 1); DBUG_ASSERT(SP_PSI_STATEMENT_INFO_COUNT == __LINE__ - num); } @@ -1965,7 +1969,8 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, /* Arguments must be fixed in Item_func_sp::fix_fields */ DBUG_ASSERT(argp[arg_no]->fixed()); - err_status= bind_input_param(thd, argp[arg_no], arg_no, *func_ctx, TRUE); + err_status= bind_input_param(thd, argp[arg_no], arg_no, + octx, *func_ctx, TRUE); if (err_status) goto err_with_cleanup; } @@ -2110,6 +2115,16 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, #endif err_with_cleanup: + + /* + Call SP variable destructors both on success and error, to exit with a + clean state, as the caller can catch the error using a condition handler + and continue the execution. + E.g. SYS_REFCURSOR variables will decrement their cursor ref counters. + */ + if (thd->spcont && m_chistics.agg_type != GROUP_AGGREGATE) + thd->spcont->expr_event_handler_not_persistent(thd, + expr_event_t::DESTRUCT_OUT_OF_SCOPE); thd->spcont= octx; /* @@ -2227,7 +2242,7 @@ sp_head::execute_procedure(THD *thd, List *args) if (!arg_item) break; - err_status= bind_input_param(thd, arg_item, i, nctx, FALSE); + err_status= bind_input_param(thd, arg_item, i, octx, nctx, FALSE); if (err_status) break; } @@ -2346,6 +2361,9 @@ sp_head::execute_procedure(THD *thd, List *args) } } + if (thd->spcont) + thd->spcont->expr_event_handler_not_persistent(thd, + expr_event_t::DESTRUCT_OUT_OF_SCOPE); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (save_security_ctx) m_security_ctx.restore_security_context(thd, save_security_ctx); @@ -2377,6 +2395,7 @@ bool sp_head::bind_input_param(THD *thd, Item *arg_item, uint arg_no, + sp_rcontext *octx, sp_rcontext *nctx, bool is_function) { @@ -2430,6 +2449,7 @@ sp_head::bind_input_param(THD *thd, if (spvar->mode == sp_variable::MODE_OUT) { + // Initialize formal parameters to NULL Item_null *null_item= new (thd->mem_root) Item_null(thd); Item *tmp_item= null_item; @@ -2439,6 +2459,24 @@ sp_head::bind_input_param(THD *thd, DBUG_PRINT("error", ("set variable failed")); DBUG_RETURN(TRUE); } + + /* + The old value of the actual OUT parameter will be overridden + in the end of the routine execution when copying its new value + from the formal parameter in bind_output_param(). + */ + if (Item_splocal *spv= arg_item->get_item_splocal()) + { + /* + In case of a SYS_REFCURSOR variable the call for expr_event_handler() + will decrement the ref counter in the referenced cursor in + sp_cursor_array. See Field_sys_refcursor::expr_event_handler() + for details. + */ + sp_rcontext *octx1= spv->rcontext_handler()->get_rcontext(octx); + octx1->get_variable(spv->get_var_idx())-> + field->expr_event_handler(thd, expr_event_t::DESTRUCT_OUT_OF_SCOPE); + } } else { @@ -3914,6 +3952,32 @@ sp_head::add_set_for_loop_cursor_param_variables(THD *thd, } +/* + When the parser reaches the end of a BEGIN..END inner block + let's add sp_instr_destruct_variable instructions for the block variables + with complex data types (with side effects) such as SYS_REFCURSOR. +*/ +bool sp_head::add_sp_block_destruct_variables(THD *thd, sp_pcontext *pctx) +{ + uint var_count= pctx->context_var_count(); + for (uint i= 0; i < var_count; i++) + { + uint offset= var_count - i - 1; + sp_variable *spv= pctx->get_context_variable(offset); + if (spv->type_handler()-> + Spvar_definition_with_complex_data_types(&spv->field_def)) + { + sp_instr *instr= new (thd->mem_root) sp_instr_destruct_variable( + instructions(), + pctx, spv->offset); + if (!instr || add_instr(instr)) + return true; + } + } + return false; +} + + bool sp_head::spvar_fill_row(THD *thd, sp_variable *spvar, Row_definition_list *defs) diff --git a/sql/sp_head.h b/sql/sp_head.h index 38566a96b33..330fd4f73ac 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -411,6 +411,8 @@ public: bool add_instr_preturn(THD *thd, sp_pcontext *spcont); + bool add_sp_block_destruct_variables(THD *thd, sp_pcontext *pctx); + Item *adjust_assignment_source(THD *thd, Item *val, Item *val2); /** @param thd - the current thd @@ -525,6 +527,7 @@ private: bool bind_input_param(THD *thd, Item *arg_item, uint arg_no, + sp_rcontext *octx, sp_rcontext *nctx, bool is_function); diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 0eb2da7972d..aeedfbcce04 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -41,6 +41,48 @@ static int cmp_rqp_locations(const void *a_, const void *b_) } +static constexpr LEX_CSTRING cursor_str= {C_STRING_WITH_LEN("cursor")}; + +/* + Print the instruction name with an array variable element: + @param str [OUT] The destination string + @param cmd The instruction name + @param rcontext_name The name of the array rcontext + @param array_name The array name + @param index_offest The offset of the index variable. + + Example: "cclose SESSION.cursor[c@1]" + - cclose is the command name + - SESSION is the name of the cursor rcontext + - c@1 is the index variable name and offset +*/ +void sp_instr::print_cmd_and_array_element(String *str, + const LEX_CSTRING &cmd, + const LEX_CSTRING &rcontext_name, + const LEX_CSTRING &array_name, + uint index_offset) const +{ + const sp_variable *pv= m_ctx->find_variable(index_offset); + size_t rsrv= cmd.length + 1/*space*/ + + rcontext_name.length + + array_name.length + 2/*[]*/ + + (pv ? pv->name.length + 1/*@*/ + SP_INSTR_UINT_MAXLEN : 0); + if (str->reserve(rsrv)) + return; + str->qs_append(cmd.str, cmd.length); + str->qs_append(' '); + if (pv) + { + str->qs_append(&rcontext_name); + str->qs_append(&array_name); + str->qs_append('['); + str->qs_append(&pv->name); + str->qs_append('@'); + str->qs_append(pv->offset); + str->qs_append(']'); + } +} + /* StoredRoutinesBinlogging This paragraph applies only to statement-based binlogging. Row-based @@ -555,6 +597,24 @@ int sp_lex_keeper::cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, sp_instr class functions */ +void sp_instr::print_fetch_into(String *str, List varlist) +{ + List_iterator_fast li(varlist); + sp_fetch_target *pv; + while ((pv= li++)) + { + const LEX_CSTRING *prefix= pv->rcontext_handler()->get_name_prefix(); + if (str->reserve(pv->name.length + prefix->length + SP_INSTR_UINT_MAXLEN+2)) + return; + str->qs_append(' '); + str->qs_append(prefix); + str->qs_append(&pv->name); + str->qs_append('@'); + str->qs_append(pv->offset()); + } +} + + int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables) { int result; @@ -1402,6 +1462,44 @@ bool sp_instr_set_trigger_field::on_after_expr_parsing(THD *thd) } +/* + sp_instr_destruct_variable class +*/ +PSI_statement_info sp_instr_destruct_variable::psi_info= +{0, "destruct", 0}; + + +void sp_instr_destruct_variable::print(String *str) +{ + const LEX_CSTRING instr_name= {STRING_WITH_LEN("destruct")}; + const sp_variable *spv= m_ctx->find_variable(m_offset); + const LEX_CSTRING data_type= spv->type_handler()->name().lex_cstring(); + /* destruct datatype name@offset */ + size_t rsrv= instr_name.length + 1 + + data_type.length + 1 + + spv->name.length + 1 + + SP_INSTR_UINT_MAXLEN; + if (str->reserve(rsrv)) + return; + str->qs_append(&instr_name); + str->qs_append(' '); + str->qs_append(&data_type); + str->qs_append(' '); + str->qs_append(&spv->name); + str->qs_append('@'); + str->qs_append(spv->offset); +} + + +int sp_instr_destruct_variable::execute(THD *thd, uint *nextp) +{ + *nextp= m_ip + 1; + thd->spcont->get_variable(m_offset)-> + field->expr_event_handler(thd, expr_event_t::DESTRUCT_OUT_OF_SCOPE); + return 0; +} + + /* sp_instr_jump_if_not class functions */ @@ -1425,7 +1523,7 @@ sp_instr_jump_if_not::exec_core(THD *thd, uint *nextp) int res; it= thd->sp_prepare_func_item(&m_expr, 1); - if (! it) + if (! it || it->check_type_can_return_bool({STRING_WITH_LEN("IF")})) { res= -1; } @@ -1861,13 +1959,13 @@ PSI_statement_info sp_instr_copen::psi_info= int sp_instr_copen::execute(THD *thd, uint *nextp) { + DBUG_ENTER("sp_instr_copen::execute"); /* We don't store a pointer to the cursor in the instruction to be able to reuse the same instruction among different threads in future. */ sp_cursor *c= thd->spcont->get_cursor(m_cursor); int res; - DBUG_ENTER("sp_instr_copen::execute"); if (! c) res= -1; @@ -1989,7 +2087,6 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp) { sp_cursor *c= thd->spcont->get_cursor(m_cursor); int res; - Query_arena backup_arena; DBUG_ENTER("sp_instr_cfetch::execute"); res= c ? c->fetch(thd, &m_fetch_target_list, m_error_on_no_data) : -1; @@ -2002,8 +2099,6 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp) void sp_instr_cfetch::print(String *str) { - List_iterator_fast li(m_fetch_target_list); - sp_fetch_target *pv; const LEX_CSTRING *cursor_name= m_ctx->find_cursor(m_cursor); /* cfetch name@offset vars... */ @@ -2020,17 +2115,7 @@ sp_instr_cfetch::print(String *str) str->qs_append('@'); } str->qs_append(m_cursor); - while ((pv= li++)) - { - const LEX_CSTRING *prefix= pv->rcontext_handler()->get_name_prefix(); - if (str->reserve(pv->name.length+prefix->length+SP_INSTR_UINT_MAXLEN+2)) - return; - str->qs_append(' '); - str->qs_append(prefix); - str->qs_append(&pv->name); - str->qs_append('@'); - str->qs_append(pv->offset()); - } + print_fetch_into(str, m_fetch_target_list); } /* @@ -2169,6 +2254,149 @@ sp_instr_cursor_copy_struct::print(String *str) } +/* + sp_instr_copen_by_ref class functions. + Handles the "OPEN sys_ref_cyrsor FOR stmt" statement. +*/ + +PSI_statement_info sp_instr_copen_by_ref::psi_info= +{ 0, "copen_by_ref", 0}; + + +int +sp_instr_copen_by_ref::execute(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_copen_by_ref::execute"); + m_lex_keeper.disable_query_cache(); + int res= m_lex_keeper.cursor_reset_lex_and_exec_core(thd, nextp, false, this); + *nextp= m_ip + 1; + DBUG_RETURN(res); +} + + +int sp_instr_copen_by_ref::exec_core(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_copen_by_ref::exec_core"); + sp_cursor *cursor; + if (thd->open_cursors_counter() < thd->variables.max_open_cursors) + { + // The limit allows to open new cursors + if (!(cursor= m_deref_rcontext_handler->get_cursor_by_ref(thd, + *this, true))) + DBUG_RETURN(-1); // EOM + /* + The sp_rcontext_addr part of "this" points to an initialized sp_cursor. + It can be a newly added cursor, or an old one (closed or open). + Two consequent OPEN (without a CLOSE in between) are allowed + for SYS_REFCURSORs (unlike for static CURSORs). + Close the first cursor automatically if it's open, e.g.: + OPEN c FOR SELECT 1; + OPEN c FOR SELECT 2; -- this closes "c" and opens it for the new query + */ + cursor->reset_for_reopen(thd); + DBUG_ASSERT(thd->lex == m_lex_keeper.lex()); + // TODO: check with DmitryS if hiding ROOT_FLAG_READ_ONLY is OK: + auto flags_backup= thd->lex->query_arena()->mem_root->flags; + thd->lex->query_arena()->mem_root->flags&= ~ROOT_FLAG_READ_ONLY; + int rc= cursor->open(thd); + thd->lex->query_arena()->mem_root->flags= flags_backup; + DBUG_RETURN(rc); + } + + /* + The limit does not allow to create new open cursors. + Only an existing cursor pointed by the sp_rcontext_addr part of + "this" can be reused, and it must be open. + */ + if (!(cursor= m_deref_rcontext_handler->get_cursor_by_ref(thd, + *this, false)) || + !cursor->is_open()) + { + /* + - The SYS_REFCURSOR variable pointed by the sp_rcontext_addr + part of "this" is not linked to any session cursors. + - Or it is linked, but the referenced session cursor is not open. + */ + my_error(ER_TOO_MANY_OPEN_CURSORS, MYF(0), + thd->variables.max_open_cursors); + DBUG_RETURN(-1); + } + cursor->reset_for_reopen(thd); + DBUG_RETURN(cursor->open(thd, false/*don't check max_open_cursors*/)); +} + + +void +sp_instr_copen_by_ref::print(String *str) +{ + static constexpr LEX_CSTRING instr{STRING_WITH_LEN("copen")}; + print_cmd_and_array_element(str, instr, + m_deref_rcontext_handler->get_name_prefix()[0], + cursor_str, m_offset); +} + + +/* + sp_instr_cclose_by_ref class functions +*/ + +PSI_statement_info sp_instr_cclose_by_ref::psi_info +{ 0, "cclose_by_ref", 0}; + +int +sp_instr_cclose_by_ref::execute(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cclose_by_ref::execute"); + sp_cursor *cursor= Sp_rcontext_handler::get_open_cursor_or_error(thd, *this); + if (!cursor) + DBUG_RETURN(-1); + int res= cursor->close(thd); + *nextp= m_ip + 1; + DBUG_RETURN(res); +} + + +void +sp_instr_cclose_by_ref::print(String *str) +{ + static constexpr LEX_CSTRING instr{STRING_WITH_LEN("cclose")}; + print_cmd_and_array_element(str, instr, + m_deref_rcontext_handler->get_name_prefix()[0], + cursor_str, m_offset); +} + + +/* + sp_instr_cfetch_by_ref class functions +*/ + +PSI_statement_info sp_instr_cfetch_by_ref::psi_info= +{ 0, "cfetch_by_ref", 0}; + +int +sp_instr_cfetch_by_ref::execute(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cfetch_by_ref::execute"); + sp_cursor *cursor= Sp_rcontext_handler::get_open_cursor_or_error(thd, *this); + if (!cursor) + DBUG_RETURN(-1); + int res= cursor->fetch(thd, &m_fetch_target_list, m_error_on_no_data); + *nextp= m_ip + 1; + DBUG_RETURN(res); +} + + +void +sp_instr_cfetch_by_ref::print(String *str) +{ + static constexpr LEX_CSTRING instr= LEX_CSTRING{STRING_WITH_LEN("cfetch")}; + print_cmd_and_array_element(str, instr, + m_deref_rcontext_handler->get_name_prefix()[0], + cursor_str, m_offset); + print_fetch_into(str, m_fetch_target_list); +} + + /* sp_instr_error class functions */ diff --git a/sql/sp_instr.h b/sql/sp_instr.h index 65ab63d3560..c4d66b9cf3a 100644 --- a/sql/sp_instr.h +++ b/sql/sp_instr.h @@ -168,6 +168,13 @@ public: virtual void print(String *str) = 0; + void print_cmd_and_array_element(String *str, + const LEX_CSTRING &cmd, + const LEX_CSTRING &rcontext_name, + const LEX_CSTRING &array_name, + uint index_offset) const; + void print_fetch_into(String *str, List list); + virtual void backpatch(uint dest, sp_pcontext *dst_ctx) {} @@ -344,6 +351,16 @@ public: m_lex->safe_to_cache_query= 0; } + /* + Return m_lex as a const pointer. "const" should be enough + to use in DBUG_ASSERT in sp_instr_xxx methods, e.g.: + DBUG_ASSERT(thd->lex == m_lex_keeper.lex()); + */ + const LEX *lex() const + { + return m_lex; + } + private: /** Clean up and destroy owned LEX object. @@ -828,6 +845,34 @@ public: }; // class sp_instr_trigger_field : public sp_lex_instr +/** + Destruct a variable in the end of a BEGIN..END block +*/ +class sp_instr_destruct_variable: public sp_instr +{ +public: + sp_instr_destruct_variable(uint ip, sp_pcontext *pctx, uint offset) + :sp_instr(ip, pctx), + m_offset(offset) + { } + + virtual ~sp_instr_destruct_variable() = default; + + int execute(THD *thd, uint *nextp) override; + + void print(String *str) override; + + uint offset() const { return m_offset; } + +private: + uint m_offset; + +public: + PSI_statement_info* get_psi_info() override { return & psi_info; } + static PSI_statement_info psi_info; +}; + + /** An abstract class for all instructions with destinations that needs to be updated by the optimizer. @@ -1510,26 +1555,19 @@ public: }; // class sp_instr_cclose : public sp_instr -class sp_instr_cfetch : public sp_instr +class sp_instr_fetch_cursor: public sp_instr { - sp_instr_cfetch(const sp_instr_cfetch &); /**< Prevent use of these */ - void operator=(sp_instr_cfetch &); - + /**< Prevent use of these */ + sp_instr_fetch_cursor(const sp_instr_fetch_cursor &) = delete; + void operator=(sp_instr_fetch_cursor &) = delete; public: - sp_instr_cfetch(uint ip, sp_pcontext *ctx, uint c, bool error_on_no_data) - : sp_instr(ip, ctx), - m_cursor(c), - m_error_on_no_data(error_on_no_data) + sp_instr_fetch_cursor(uint ip, sp_pcontext *ctx, bool error_on_no_data) + :sp_instr(ip, ctx), + m_error_on_no_data(error_on_no_data) { m_fetch_target_list.empty(); } - virtual ~sp_instr_cfetch() = default; - - int execute(THD *thd, uint *nextp) override; - - void print(String *str) override; - bool add_to_fetch_target_list(sp_fetch_target *target) { return m_fetch_target_list.push_back(target); @@ -1540,10 +1578,31 @@ public: m_fetch_target_list= *list; } -private: - uint m_cursor; +protected: List m_fetch_target_list; bool m_error_on_no_data; +}; + + +class sp_instr_cfetch : public sp_instr_fetch_cursor +{ + sp_instr_cfetch(const sp_instr_cfetch &); /**< Prevent use of these */ + void operator=(sp_instr_cfetch &); + +public: + sp_instr_cfetch(uint ip, sp_pcontext *ctx, uint c, bool error_on_no_data) + :sp_instr_fetch_cursor(ip, ctx, error_on_no_data), + m_cursor(c) + { } + + virtual ~sp_instr_cfetch() = default; + + int execute(THD *thd, uint *nextp) override; + + void print(String *str) override; + +private: + uint m_cursor; public: PSI_statement_info* get_psi_info() override { return & psi_info; } @@ -1579,6 +1638,136 @@ public: }; // class sp_instr_agg_cfetch : public sp_instr +class sp_instr_copen_by_ref : public sp_lex_instr, + public sp_rcontext_ref +{ + using SELF= sp_instr_copen_by_ref; + // Prevent use of these + sp_instr_copen_by_ref(const SELF &) = delete; + void operator=(SELF &) = delete; + +public: + sp_instr_copen_by_ref(uint ip, sp_pcontext *ctx, + const sp_rcontext_ref &ref, + sp_lex_cursor *lex) + :sp_lex_instr(ip, ctx, lex, true), + sp_rcontext_ref(ref), + m_metadata_changed(false), + m_cursor_stmt(lex->get_expr_str()) + { } + + virtual ~sp_instr_copen_by_ref() = default; + + int execute(THD *thd, uint *nextp) override; + int exec_core(THD *thd, uint *nextp) override; + + void print(String *str) override; + + bool is_invalid() const override + { + return m_metadata_changed; + } + + void invalidate() override + { + m_metadata_changed= true; + } + + bool on_after_expr_parsing(THD *) override + { + m_metadata_changed= false; + return false; + } + + void get_query(String *sql_query) const override + { + sql_query->append(get_expr_query()); + } + + LEX_CSTRING get_expr_query() const override + { + /* + Lexer on processing the clause CURSOR FOR / CURSOR IS doesn't + move a pointer on cpp_buf after the token FOR/IS so skip it explicitly + in order to get correct value of cursor's query string. + + Note, there is possibly a bug below: only the space character is tested + after FOR and IS. If a TAB or NL or CR character follows the keyword + then something can go wrong. Cannot check at the moment because of abother bug: + + MDEV-36079 Stored routine with a cursor crashes on the second execution ... + */ + if (strncasecmp(m_cursor_stmt.str, "FOR ", 4) == 0) + return LEX_CSTRING{m_cursor_stmt.str + 4, m_cursor_stmt.length - 4}; + if (strncasecmp(m_cursor_stmt.str, "IS ", 3) == 0) + return LEX_CSTRING{m_cursor_stmt.str + 3, m_cursor_stmt.length - 3}; + return m_cursor_stmt; + } + +private: + bool m_metadata_changed; + LEX_CSTRING m_cursor_stmt; + +public: + PSI_statement_info* get_psi_info() override { return & psi_info; } + static PSI_statement_info psi_info; +}; + + +class sp_instr_cclose_by_ref : public sp_instr, + public sp_rcontext_ref +{ + using SELF= sp_instr_cclose_by_ref; + // Prevent use of these + sp_instr_cclose_by_ref(const SELF &) = delete; + void operator=(SELF &) = delete; + +public: + sp_instr_cclose_by_ref(uint ip, sp_pcontext *ctx, + const sp_rcontext_ref &ref) + :sp_instr(ip, ctx), + sp_rcontext_ref(ref) + { } + + virtual ~sp_instr_cclose_by_ref() = default; + + int execute(THD *thd, uint *nextp) override; + + void print(String *str) override; + +public: + PSI_statement_info* get_psi_info() override { return & psi_info; } + static PSI_statement_info psi_info; +}; + + +class sp_instr_cfetch_by_ref : public sp_instr_fetch_cursor, + public sp_rcontext_ref +{ + using SELF= sp_instr_cfetch_by_ref; + // Prevent use of these + sp_instr_cfetch_by_ref(const SELF &) = delete; + void operator=(SELF &) = delete; +public: + sp_instr_cfetch_by_ref(uint ip, sp_pcontext *ctx, + const sp_rcontext_ref &ref, + bool error_on_no_data) + :sp_instr_fetch_cursor(ip, ctx, error_on_no_data), + sp_rcontext_ref(ref) + { } + + virtual ~sp_instr_cfetch_by_ref() = default; + + int execute(THD *thd, uint *nextp) override; + + void print(String *str) override; + +public: + PSI_statement_info* get_psi_info() override { return & psi_info; } + static PSI_statement_info psi_info; +}; + + class sp_instr_error : public sp_instr { sp_instr_error(const sp_instr_error &); /**< Prevent use of these */ diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index 64c95ffec8e..69f50b99954 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -400,7 +400,10 @@ public: REGULAR_SCOPE, /// HANDLER_SCOPE designates SQL-handler blocks. - HANDLER_SCOPE + HANDLER_SCOPE, + + /// Declarations between CREATE PACKAGE and BEGIN + PACKAGE_BODY_SCOPE }; class Lex_for_loop: public Lex_for_loop_st @@ -744,6 +747,10 @@ public: { return m_for_loop; } + enum_scope scope() const + { + return m_scope; + } ///////////////////////////////////////////////////////////////////////// // Record. diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 087ecd5c1cf..3f432514e40 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -30,6 +30,19 @@ Sp_rcontext_handler_local sp_rcontext_handler_local; Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; +Sp_rcontext_handler_statement sp_rcontext_handler_statement; + + +sp_cursor *Sp_rcontext_handler::get_open_cursor_or_error(THD *thd, + const sp_rcontext_ref &ref) +{ + sp_cursor *cursor= get_cursor(thd, ref); + if (cursor && cursor->is_open()) + return cursor; + my_error(ER_SP_CURSOR_NOT_OPEN, MYF(0)); + return nullptr; +} + sp_rcontext *Sp_rcontext_handler_local::get_rcontext(sp_rcontext *ctx) const { @@ -53,6 +66,45 @@ const LEX_CSTRING *Sp_rcontext_handler_package_body::get_name_prefix() const return &sp_package_body_variable_prefix_clex_str; } +const LEX_CSTRING *Sp_rcontext_handler_statement::get_name_prefix() const +{ + static const LEX_CSTRING prefix= {STRING_WITH_LEN("STMT.")}; + return &prefix; +} + + +Item_field *Sp_rcontext_handler_local::get_variable(THD *thd, + uint offset) const +{ + return thd->spcont->get_variable(offset); +} + + +Item_field *Sp_rcontext_handler_package_body::get_variable(THD *thd, + uint offset) const +{ + return Sp_rcontext_handler_package_body::get_rcontext(thd->spcont)-> + get_variable(offset); +} + + +sp_cursor *Sp_rcontext_handler_local::get_cursor(THD *thd, uint offset) const +{ + return thd->spcont->get_cursor(offset); +} + +sp_cursor *Sp_rcontext_handler_statement::get_cursor(THD *thd, uint offset) const +{ + return &thd->statement_cursors()->at(offset); +} + +sp_cursor *Sp_rcontext_handler_statement::get_cursor_by_ref(THD *thd, + const sp_rcontext_addr &ref, + bool for_open) const +{ + Field *field= ref.rcontext_handler()->get_variable(thd, ref.offset())->field; + return thd->statement_cursors()->get_cursor_by_ref(thd, field, for_open); +} /////////////////////////////////////////////////////////////////////////// // sp_rcontext implementation. @@ -396,6 +448,14 @@ bool sp_rcontext::init_var_items(THD *thd, } +void sp_rcontext::expr_event_handler(THD *thd, expr_event_t event, + uint start, uint end) +{ + if (m_var_table) + m_var_table->expr_event_handler(thd, event, start, end); +} + + bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) { DBUG_ASSERT(m_return_value_fld); @@ -686,8 +746,9 @@ bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id, m_case_expr_holders[case_expr_id]->result_type() != case_expr_item->result_type()) { - m_case_expr_holders[case_expr_id]= - create_case_expr_holder(thd, case_expr_item); + if (!(m_case_expr_holders[case_expr_id]= + create_case_expr_holder(thd, case_expr_item))) + return true; // A data type not allowed in CASE WHEN, or EOM } m_case_expr_holders[case_expr_id]->store(case_expr_item); @@ -713,7 +774,7 @@ bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id, 0 in case of success, -1 otherwise */ -int sp_cursor::open(THD *thd) +int sp_cursor::open(THD *thd, bool check_open_cursor_counter) { if (server_side_cursor) { @@ -722,8 +783,18 @@ int sp_cursor::open(THD *thd) MYF(0)); return -1; } + + if (check_open_cursor_counter && + thd->open_cursors_counter() >= thd->variables.max_open_cursors) + { + my_error(ER_TOO_MANY_OPEN_CURSORS, MYF(0), + thd->variables.max_open_cursors); + return -1; + } + if (mysql_open_cursor(thd, &result, &server_side_cursor)) return -1; + thd->open_cursors_counter_increment(); return 0; } @@ -736,6 +807,7 @@ int sp_cursor::close(THD *thd) MYF(0)); return -1; } + thd->open_cursors_counter_decrement(); sp_cursor_statistics::reset(); destroy(); return 0; diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index ab4091d2aaa..46db7b4128f 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -19,6 +19,7 @@ #include "sql_class.h" // select_result_interceptor #include "sp_pcontext.h" // sp_condition_value +#include "sp_rcontext_handler.h" /////////////////////////////////////////////////////////////////////////// // sp_rcontext declaration. @@ -176,6 +177,35 @@ public: return m_root_parsing_ctx->context_var_count(); } + uint max_var_index() const + { + return (uint) m_var_items.size(); + } + + /* + Return: + - for functions and procedures - 0. + - for PACKAGE BODY - the number of its package-wide variables, + which must keep their values even after running of + the PACKAGE BODY executable initialization secion. + */ + uint persistent_variable_count() const + { + /* + The top level sp_pcontext contains function and procedure paramenters. + In case of a PACKAGE BODY there are no parameters, the context for + parameters still exists, with no variables. + PACKAGE BODY variables are in m_root_parsing_ctx->child_context(0). + */ + const sp_pcontext *pc= m_root_parsing_ctx->child_context(0); + if (pc && pc->scope() == sp_pcontext::PACKAGE_BODY_SCOPE) + { + DBUG_ASSERT(m_root_parsing_ctx->context_var_count() == 0); // No params + return pc->current_var_count(); + } + return 0; + } + int set_variable(THD *thd, uint var_idx, Item **value); int set_variable_row_field(THD *thd, uint var_idx, uint field_idx, Item **value); @@ -207,6 +237,24 @@ public: bool set_return_value(THD *thd, Item **return_value_item); + /* + Run the event handler for all SP variables (i.e. Fields in m_var_table) + in the range [start..end-1]. + */ + void expr_event_handler(THD *thd, expr_event_t event, uint start, uint end); + + /* + Run the event (e.g. destruction) handler for all variables + except PACKAGE BODY variables, which must keep their values even after + running of the PACKAGE BODY executable initialization secion. + */ + void expr_event_handler_not_persistent(THD *thd, expr_event_t event) + { + uint start= thd->spcont->persistent_variable_count(); + uint end= max_var_index(); + return expr_event_handler(thd, event, start, end); + } + bool is_return_value_set() const { return m_return_value_set; } diff --git a/sql/sp_rcontext_handler.h b/sql/sp_rcontext_handler.h new file mode 100644 index 00000000000..4640b1a2f47 --- /dev/null +++ b/sql/sp_rcontext_handler.h @@ -0,0 +1,165 @@ +#ifndef SP_RCONTEXT_HANDLER_INCLUDED +#define SP_RCONTEXT_HANDLER_INCLUDED + +/* Copyright (c) 2009, 2025, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + + +class sp_rcontext; +class sp_cursor; + +/** + A helper class to handle the run time context of various components of SP: + Variables: + - local SP variables and SP parameters + - PACKAGE BODY routine variables + - (there will be more kinds in the future) + Cursors: + - static cursors + - SYS_REFCURSORs +*/ + +class Sp_rcontext_handler +{ +public: + /* + Get a cursor using its direct address or a reference. + + addr_or_ref.deref_rcontext_handler()==nullptr means + that addr_or_ref contains the direct address of the cursor. + + A not-null addr_or_ref.deref_rcontext_handler() value + means that it is a reference and should be dereferenced. + */ + static sp_cursor *get_cursor(THD *thd, + const sp_rcontext_ref &addr_or_ref) + { + return addr_or_ref.deref_rcontext_handler() ? + addr_or_ref.deref_rcontext_handler()->get_cursor_by_ref(thd, addr_or_ref, + false) : + addr_or_ref.rcontext_handler()->get_cursor(thd, addr_or_ref.offset()); + } + /* + Get a cursor using its direct address or a reference. + If the cursor is not found or is not open, + the ER_SP_CURSOR_NOT_OPEN error is raised. + */ + static sp_cursor *get_open_cursor_or_error(THD *thd, + const sp_rcontext_ref &addr_or_ref); + + virtual ~Sp_rcontext_handler() = default; + + + /** + A prefix used for SP variable names in queries: + - EXPLAIN EXTENDED + - SHOW PROCEDURE CODE + Local variables and SP parameters have empty prefixes. + Package body variables are marked with a special prefix. + This improves readability of the output of these queries, + especially when a local variable or a parameter has the same + name with a package body variable. + */ + virtual const LEX_CSTRING *get_name_prefix() const= 0; + /** + At execution time THD->spcont points to the run-time context (sp_rcontext) + of the currently executed routine. + Local variables store their data in the sp_rcontext pointed by thd->spcont. + Package body variables store data in separate sp_rcontext that belongs + to the package. + This method provides access to the proper sp_rcontext structure, + depending on the SP variable kind. + */ + virtual sp_rcontext *get_rcontext(sp_rcontext *ctx) const= 0; + virtual Item_field *get_variable(THD *thd, uint offset) const= 0; + virtual sp_cursor *get_cursor(THD *thd, uint offset) const= 0; + virtual sp_cursor *get_cursor_by_ref(THD *thd, + const sp_rcontext_addr &ref, + bool for_open) const= 0; +}; + + +class Sp_rcontext_handler_local final :public Sp_rcontext_handler +{ +public: + const LEX_CSTRING *get_name_prefix() const override; + sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; + Item_field *get_variable(THD *thd, uint offset) const override; + sp_cursor *get_cursor(THD *thd, uint offset) const override; + sp_cursor *get_cursor_by_ref(THD *thd, const sp_rcontext_addr &ref, + bool for_open) const override + { + DBUG_ASSERT(0); // References to static cursors are not supported + return nullptr; + } +}; + + +class Sp_rcontext_handler_package_body final :public Sp_rcontext_handler +{ +public: + const LEX_CSTRING *get_name_prefix() const override; + sp_rcontext *get_rcontext(sp_rcontext *ctx) const override; + Item_field *get_variable(THD *thd, uint offset) const override; + sp_cursor *get_cursor(THD *thd, uint offset) const override + { + /* + There are no package body wide static cursors yet: + MDEV-36053 Syntax error on a CURSOR..IS declaration in PACKAGE BODY + */ + DBUG_ASSERT(0); + return nullptr; + } + sp_cursor *get_cursor_by_ref(THD *thd, const sp_rcontext_addr &ref, + bool for_open) const override + { + DBUG_ASSERT(0); // References to static cursors are not supported + return nullptr; + } +}; + + +class Sp_rcontext_handler_statement final :public Sp_rcontext_handler +{ +public: + const LEX_CSTRING *get_name_prefix() const override; + sp_rcontext *get_rcontext(sp_rcontext *ctx) const override + { + DBUG_ASSERT(0); // There are no session wide SP variables yet. + return nullptr; + } + Item_field *get_variable(THD *thd, uint offset) const override + { + DBUG_ASSERT(0); // There are no session wide SP variables yet. + return nullptr; + } + sp_cursor *get_cursor(THD *thd, uint offset) const override; + sp_cursor *get_cursor_by_ref(THD *thd, const sp_rcontext_addr &ref, + bool for_open) const override; +}; + + +extern MYSQL_PLUGIN_IMPORT + Sp_rcontext_handler_local sp_rcontext_handler_local; + + +extern MYSQL_PLUGIN_IMPORT + Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; + + +extern MYSQL_PLUGIN_IMPORT + Sp_rcontext_handler_statement sp_rcontext_handler_statement; + +#endif // SP_RCONTEXT_HANDLER_INCLUDED diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 4c438ab421c..8e499e20196 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1573,6 +1573,7 @@ void THD::change_user(void) get_sequence_last_key, free_sequence_last, HASH_THREAD_SPECIFIC); sp_caches_clear(); + statement_rcontext_reinit(); opt_trace.delete_traces(); } @@ -1704,6 +1705,7 @@ void THD::cleanup(void) my_hash_free(&user_vars); my_hash_free(&sequences); sp_caches_clear(); + statement_rcontext_reinit(); auto_inc_intervals_forced.empty(); auto_inc_intervals_in_cur_stmt_for_binlog.empty(); @@ -1859,6 +1861,9 @@ THD::~THD() #ifndef EMBEDDED_LIBRARY if (rgi_slave) rgi_slave->cleanup_after_session(); + + statement_rcontext_reinit(); + my_free(semisync_info); #endif main_lex.free_set_stmt_mem_root(); @@ -2418,6 +2423,15 @@ void THD::cleanup_after_query() thd_progress_end(this); + if (!spcont && !in_sub_stmt) + { + /* + We're at the end of the top level statement + (not just in the end of a stored routine individual statement). + */ + statement_rcontext_reinit(); + } + /* Reset RAND_USED so that detection of calls to rand() will save random seeds if needed by the slave. @@ -3198,6 +3212,25 @@ void Item_change_list::rollback_item_tree_changes() ** Functions to provide a interface to select results *****************************************************************************/ +int select_result_sink::send_data_with_check(List &items, + SELECT_LEX_UNIT *u, + ha_rows sent) +{ + if (u->lim.check_offset(sent)) + return 0; + + if (u->thd->killed == ABORT_QUERY) + return 0; + + int rc= send_data(items); + + if (thd->stmt_arena->with_complex_data_types()) + thd->stmt_arena->expr_event_handler_for_free_list(thd, + expr_event_t::DESTRUCT_RESULT_SET_ROW_FIELD); + return rc; +} + + void select_result::reset_for_next_ps_execution() { /* do nothing */ @@ -4098,6 +4131,37 @@ Query_arena::Type Query_arena::type() const } +bool Query_arena::check_free_list_no_complex_data_types(const char *op) +{ + DBUG_ENTER("Query_arena::check_free_list_no_complex_data_types"); + for (Item *item= free_list; item; item= item->next) + { + if (item->fixed()) + { + const Type_handler *th= item->type_handler(); + if (th->is_complex()) + { + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + th->name().ptr(), op); + DBUG_RETURN(true); + } + } + } + DBUG_RETURN(false); +} + + +void Query_arena::expr_event_handler_for_free_list(THD *thd, + expr_event_t event) +{ + for (Item *item= free_list; item; item= item->next) + { + if (item->fixed()) + item->expr_event_handler(thd, event); + } +} + + void Query_arena::free_items() { Item *next; diff --git a/sql/sql_class.h b/sql/sql_class.h index 8cdf1e7ee93..73c643898b3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. - Copyright (c) 2009, 2024, MariaDB Corporation. + Copyright (c) 2009, 2025, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -855,6 +855,7 @@ typedef struct system_variables uint column_compression_threshold; uint column_compression_zlib_level; uint in_subquery_conversion_threshold; + uint max_open_cursors; int max_user_connections; /** @@ -1202,6 +1203,9 @@ struct THD_count #ifdef MYSQL_SERVER +#include "select_result.h" +#include "statement_rcontext.h" + void free_tmp_table(THD *thd, TABLE *entry); @@ -1250,6 +1254,28 @@ public: enum_state state; + /* + Bit-ORed mask of Item::with_flag for *some* items in free_list. + The goal is to have the cumulated COMPLEX_DATA_TYPE flag. + So far only only some items can can COMPLEX_DATA_TYPE: + Item_param, Item_func, Item_sp_variable, Item_row + For other Item types this flag is not collected. + */ + item_with_t with_flags_bit_or_for_complex_data_types; + + bool with_complex_data_types() const + { + return (bool) (with_flags_bit_or_for_complex_data_types & + item_with_t::COMPLEX_DATA_TYPE); + } + + /* + Raise an error if free_list contains items with complex data types. + @param op - the operation name for the error message, e.g. "CREATE VIEW" + @return - true if the error was raised, or false otherwise + */ + bool check_free_list_no_complex_data_types(const char *op); + public: /* We build without RTTI, so dynamic_cast can't be used. */ enum Type @@ -1258,7 +1284,8 @@ public: }; Query_arena(MEM_ROOT *mem_root_arg, enum enum_state state_arg) : - free_list(0), mem_root(mem_root_arg), state(state_arg) + free_list(0), mem_root(mem_root_arg), state(state_arg), + with_flags_bit_or_for_complex_data_types(item_with_t::NONE) { INIT_ARENA_DBUG_INFO; } /* This constructor is used only when Query_arena is created as @@ -1524,6 +1551,8 @@ public: void set_query_arena(Query_arena *set); + void expr_event_handler_for_free_list(THD *thd, expr_event_t event); + void free_items(); /* Close the active state associated with execution of this statement */ virtual bool cleanup_stmt(bool /*restore_set_statement_vars*/); @@ -1559,8 +1588,6 @@ public: }; -class Server_side_cursor; - /* Struct to catch changes in column metadata that is sent to client. in the "result set metadata". Used to support @@ -2950,7 +2977,6 @@ enum class THD_WHERE }; -class THD; const char *thd_where(THD *thd); @@ -2973,7 +2999,8 @@ class THD: public THD_count, /* this must be first */ public Item_change_list, public MDL_context_owner, public Open_tables_state, - public Sp_caches + public Sp_caches, + public Statement_rcontext { private: inline bool is_stmt_prepare() const @@ -3744,6 +3771,19 @@ public: { m_row_count_func= row_count_func; } + + /* + Free all top level statement data (e.g. belonging to SYS_REFCURSORs) + and reinit it for a new top level statement. + It's called in the very end of the top level statement + (it's not called for individual stored routune statements). + */ + void statement_rcontext_reinit() + { + Statement_rcontext::reinit(this); + with_flags_bit_or_for_complex_data_types= item_with_t::NONE; + } + inline void set_affected_rows(longlong row_count_func) { /* @@ -6230,144 +6270,6 @@ public: class JOIN; -/* Pure interface for sending tabular data */ -class select_result_sink: public Sql_alloc -{ -public: - THD *thd; - select_result_sink(THD *thd_arg): thd(thd_arg) {} - inline int send_data_with_check(List &items, - SELECT_LEX_UNIT *u, - ha_rows sent) - { - if (u->lim.check_offset(sent)) - return 0; - - if (u->thd->killed == ABORT_QUERY) - return 0; - - return send_data(items); - } - /* - send_data returns 0 on ok, 1 on error and -1 if data was ignored, for - example for a duplicate row entry written to a temp table. - */ - virtual int send_data(List &items)=0; - virtual ~select_result_sink() = default; - // Used in cursors to initialize and reset - void reinit(THD *thd_arg) { thd= thd_arg; } -}; - -class select_result_interceptor; - -/* - Interface for sending tabular data, together with some other stuff: - - - Primary purpose seems to be sending typed tabular data: - = the DDL is sent with send_fields() - = the rows are sent with send_data() - Besides that, - - there seems to be an assumption that the sent data is a result of - SELECT_LEX_UNIT *unit, - - nest_level is used by SQL parser -*/ - -class select_result :public select_result_sink -{ -protected: - /* - All descendant classes have their send_data() skip the first - unit->offset_limit_cnt rows sent. Select_materialize - also uses unit->get_column_types(). - */ - SELECT_LEX_UNIT *unit; - /* Something used only by the parser: */ -public: - ha_rows est_records; /* estimated number of records in the result */ - select_result(THD *thd_arg): select_result_sink(thd_arg), est_records(0) {} - void set_unit(SELECT_LEX_UNIT *unit_arg) { unit= unit_arg; } - virtual ~select_result() = default; - /** - Change wrapped select_result. - - Replace the wrapped result object with new_result and call - prepare() and prepare2() on new_result. - - This base class implementation doesn't wrap other select_results. - - @param new_result The new result object to wrap around - - @retval false Success - @retval true Error - */ - virtual bool change_result(select_result *new_result) - { - return false; - } - virtual int prepare(List &list, SELECT_LEX_UNIT *u) - { - unit= u; - return 0; - } - virtual int prepare2(JOIN *join) { return 0; } - /* - Because of peculiarities of prepared statements protocol - we need to know number of columns in the result set (if - there is a result set) apart from sending columns metadata. - */ - virtual uint field_count(List &fields) const - { return fields.elements; } - virtual bool send_result_set_metadata(List &list, uint flags)=0; - virtual bool initialize_tables (JOIN *join) { return 0; } - virtual bool send_eof()=0; - /** - Check if this query returns a result set and therefore is allowed in - cursors and set an error message if it is not the case. - - @retval FALSE success - @retval TRUE error, an error message is set - */ - virtual bool check_simple_select() const; - virtual void abort_result_set() {} - virtual void reset_for_next_ps_execution(); - void set_thd(THD *thd_arg) { thd= thd_arg; } - void reinit(THD *thd_arg) - { - select_result_sink::reinit(thd_arg); - unit= NULL; - } -#ifdef EMBEDDED_LIBRARY - virtual void begin_dataset() {} -#else - void begin_dataset() {} -#endif - virtual void update_used_tables() {} - - /* this method is called just before the first row of the table can be read */ - virtual void prepare_to_read_rows() {} - - void remove_offset_limit() - { - unit->lim.remove_offset(); - } - - /* - This returns - - NULL if the class sends output row to the client - - this if the output is set elsewhere (a file, @variable, or table). - */ - virtual select_result_interceptor *result_interceptor()=0; - - /* - This method is used to distinguish an normal SELECT from the cursor - structure discovery for cursor%ROWTYPE routine variables. - If this method returns "true", then a SELECT execution performs only - all preparation stages, but does not fetch any rows. - */ - virtual bool view_structure_only() const { return false; } -}; - - /* This is a select_result_sink which simply writes all data into a (temporary) table. Creation/deletion of the table is outside of the scope of the class @@ -6414,145 +6316,6 @@ private: }; -/* - Base class for select_result descendants which intercept and - transform result set rows. As the rows are not sent to the client, - sending of result set metadata should be suppressed as well. -*/ - -class select_result_interceptor: public select_result -{ -public: - select_result_interceptor(THD *thd_arg): - select_result(thd_arg), suppress_my_ok(false) - { - DBUG_ENTER("select_result_interceptor::select_result_interceptor"); - DBUG_PRINT("enter", ("this %p", this)); - DBUG_VOID_RETURN; - } /* Remove gcc warning */ - uint field_count(List &fields) const override { return 0; } - bool send_result_set_metadata(List &fields, uint flag) override { return FALSE; } - select_result_interceptor *result_interceptor() override { return this; } - - /* - Instruct the object to not call my_ok(). Client output will be handled - elsewhere. (this is used by ANALYZE $stmt feature). - */ - void disable_my_ok_calls() { suppress_my_ok= true; } - void reinit(THD *thd_arg) - { - select_result::reinit(thd_arg); - suppress_my_ok= false; - } -protected: - bool suppress_my_ok; -}; - - -class sp_cursor_statistics -{ -protected: - 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 -public: - sp_cursor_statistics() - :m_fetch_count(0), - m_row_count(0), - m_found(false) - { } - bool found() const - { return m_found; } - - ulonglong row_count() const - { return m_row_count; } - - ulonglong fetch_count() const - { return m_fetch_count; } - void reset() { *this= sp_cursor_statistics(); } -}; - - -class sp_instr_cpush; - -/* A mediator between stored procedures and server side cursors */ -class sp_lex_keeper; -class sp_cursor: public sp_cursor_statistics -{ -private: - /// An interceptor of cursor result set used to implement - /// FETCH INTO . - class Select_fetch_into_spvars: public select_result_interceptor - { - List *m_fetch_target_list; - uint field_count; - bool m_view_structure_only; - bool send_data_to_variable_list(List &vars, - List &items); - public: - Select_fetch_into_spvars(THD *thd_arg, bool view_structure_only) - :select_result_interceptor(thd_arg), - m_view_structure_only(view_structure_only) - {} - void reset(THD *thd_arg) - { - select_result_interceptor::reinit(thd_arg); - m_fetch_target_list= NULL; - field_count= 0; - } - uint get_field_count() { return field_count; } - void set_spvar_list(List *vars) - { - m_fetch_target_list= vars; - } - - bool send_eof() override { return FALSE; } - int send_data(List &items) override; - int prepare(List &list, SELECT_LEX_UNIT *u) override; - bool view_structure_only() const override { return m_view_structure_only; } -}; - -public: - sp_cursor() - :result(NULL, false), - server_side_cursor(NULL) - { } - sp_cursor(THD *thd_arg, bool view_structure_only) - :result(thd_arg, view_structure_only), - server_side_cursor(NULL) - {} - - virtual ~sp_cursor() - { destroy(); } - - virtual sp_lex_keeper *get_lex_keeper() { return nullptr; } - - int open(THD *thd); - - int close(THD *thd); - - my_bool is_open() - { return MY_TEST(server_side_cursor); } - - int fetch(THD *, List *vars, bool error_on_no_data); - - bool export_structure(THD *thd, Row_definition_list *list); - - void reset(THD *thd_arg) - { - sp_cursor_statistics::reset(); - result.reinit(thd_arg); - server_side_cursor= NULL; - } - - virtual sp_instr_cpush *get_push_instr() { return nullptr; } -private: - Select_fetch_into_spvars result; - Server_side_cursor *server_side_cursor; - void destroy(); -}; - - class select_send :public select_result { /** True if we have sent result set metadata to the client. diff --git a/sql/sql_const.h b/sql/sql_const.h index 50e43b12b24..d0f99016bc9 100644 --- a/sql/sql_const.h +++ b/sql/sql_const.h @@ -263,6 +263,6 @@ */ #define MAX_TIME_ZONE_NAME_LENGTH (NAME_LEN + 1) -#define SP_PSI_STATEMENT_INFO_COUNT 19 +#define SP_PSI_STATEMENT_INFO_COUNT 23 #endif /* SQL_CONST_INCLUDED */ diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 232d473378d..5fa19025113 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -883,6 +883,14 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) 0)) { thd->create_tmp_table_for_derived= FALSE; + if (thd->is_error()) + { + /* + EOM error, or attempted to a create a table with a Field + of a not allowed data type, e.g. SYS_REFCURSOR. + */ + res= true; + } goto exit; } thd->create_tmp_table_for_derived= FALSE; diff --git a/sql/sql_do.cc b/sql/sql_do.cc index 0e5b40dec11..20afa355adf 100644 --- a/sql/sql_do.cc +++ b/sql/sql_do.cc @@ -33,7 +33,10 @@ bool mysql_do(THD *thd, List &values) THD_WHERE::DO_STATEMENT)) DBUG_RETURN(TRUE); while ((value = li++)) + { (void) value->is_null(); + value->expr_event_handler(thd, expr_event_t::DESTRUCT_ROUTINE_ARG); + } free_underlaid_joins(thd, thd->lex->first_select_lex()); if (unlikely(thd->is_error())) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 89fe4af5f48..1befa690da5 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -6697,6 +6697,21 @@ LEX::find_variable(const LEX_CSTRING *name, } +bool LEX::check_variable_is_refcursor(const LEX_CSTRING &verb_clause, + const sp_variable *var) const +{ + const LEX_CSTRING tname= var->type_handler()->name().lex_cstring(); + if (my_charset_latin1.strnncollsp(tname.str, tname.length, + STRING_WITH_LEN("sys_refcursor"))) + { + my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), + tname.str, verb_clause.str); + return true; + } + return false; +} + + sp_fetch_target *LEX::make_fetch_target(THD *thd, const Lex_ident_sys_st &name) { sp_pcontext *spc; @@ -7269,8 +7284,12 @@ bool LEX::sp_for_loop_cursor_condition_test(THD *thd, DBUG_ASSERT(cursor_name); if (unlikely(!(expr= new (thd->mem_root) - Item_func_cursor_found(thd, cursor_name, - loop.m_cursor_offset)))) + Item_func_cursor_found(thd, + Cursor_ref(cursor_name, + // Static cursor + &sp_rcontext_handler_local, + loop.m_cursor_offset, + NULL))))) return true; if (thd->lex->sp_while_loop_expression(thd, expr, empty_clex_str)) return true; @@ -7528,6 +7547,80 @@ bool LEX::sp_open_cursor(THD *thd, const LEX_CSTRING *name, } +/* + Add instructions for "OPEN sys_ref_cursor FROM stmt". + This statement is only supported for SYS_REFCURSOR variables. + It's not supported for static cursors. +*/ +bool LEX::sp_open_cursor_for_stmt(THD *thd, const LEX_CSTRING *name, + sp_lex_cursor *stmt) +{ + const Sp_rcontext_handler *rh; + sp_variable *spv; + if (!(spv= find_variable(name, &rh))) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), name->str); + return true; + } + if (spv->mode == sp_variable::MODE_IN && + spv->offset < sphead->get_parse_context()->context_var_count()) + { + /* + OPEN for IN SYS_REFCURSOR parameters is not supported in Oracle. + Let's also disallow this. The error message might be misleading + about "not supported *yet*". But we don't have a better message. + */ + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "OPEN IN_sp_parameter"); + return true; + } + if (check_variable_is_refcursor({STRING_WITH_LEN("OPEN")}, spv)) + return true; + auto *i= new (thd->mem_root) sp_instr_copen_by_ref( + sphead->instructions(), spcont, + sp_rcontext_ref( + sp_rcontext_addr(rh, spv->offset), + &sp_rcontext_handler_statement), + stmt); + return i == NULL || sphead->add_instr(i); +} + + +/* + Add instructions for "CLOSE cur". + It handles both static cursors and SYS_REFCURORs. +*/ +bool LEX::sp_close(THD *thd, const Lex_ident_sys_st &name) +{ + uint offset; + + // Search for a static cursor with the given name first + if (spcont->find_cursor(&name, &offset, false)) + { + sp_instr_cclose *i= + new (thd->mem_root) sp_instr_cclose(sphead->instructions(), + spcont, offset); + return i == nullptr || sphead->add_instr(i); + } + + // Search for a SYS_REFCURSOR variable + const Sp_rcontext_handler *rh; + const sp_variable *spv= find_variable(&name, &rh); + if (spv) + { + if (check_variable_is_refcursor({STRING_WITH_LEN("CLOSE")}, spv)) + return true; + auto *i= new (thd->mem_root) sp_instr_cclose_by_ref( + sphead->instructions(), spcont, + sp_rcontext_ref( + sp_rcontext_addr(rh, spv->offset), + &sp_rcontext_handler_statement)); + return i == nullptr || sphead->add_instr(i); + } + my_error(ER_SP_CURSOR_MISMATCH, MYF(0), name.str); + return true; +} + + bool LEX::sp_handler_declaration_init(THD *thd, int type) { sp_handler *h= spcont->add_handler(thd, (sp_handler::enum_type) type); @@ -7580,6 +7673,14 @@ bool LEX::sp_handler_declaration_finalize(THD *thd, int type) } +void LEX::sp_block_init_package_body(THD *thd) +{ + spcont->push_label(thd, &empty_clex_str, + sphead->instructions(), sp_label::BEGIN); + spcont= spcont->push_context(thd, sp_pcontext::PACKAGE_BODY_SCOPE); +} + + void LEX::sp_block_init(THD *thd, const LEX_CSTRING *label) { spcont->push_label(thd, label, sphead->instructions(), sp_label::BEGIN); @@ -7611,6 +7712,10 @@ bool LEX::sp_block_finalize(THD *thd, const Lex_spblock_st spblock, unlikely(sp->add_instr(i))) return true; } + + if (sphead->add_sp_block_destruct_variables(thd, spcont)) + return true; + spcont= ctx->pop_context(); *splabel= spcont->pop_label(); return false; @@ -8306,24 +8411,50 @@ Item *LEX::make_item_colon_ident_ident(THD *thd, } +/* + Make an Item for Oracle style cursor attributes: + cur%ISOPEN + cur%FOUND + cur%NOTFOUND + cur%ROWCOUNT + Works for static cursors and SYS_REFCURSORs. +*/ Item *LEX::make_item_plsql_cursor_attr(THD *thd, const LEX_CSTRING *name, plsql_cursor_attr_t attr) { uint offset; - if (unlikely(!spcont || !spcont->find_cursor(name, &offset, false))) + const Sp_rcontext_handler *rh= nullptr; + const Sp_rcontext_handler *deref_rcontext_handler= nullptr; + const sp_variable *spv= nullptr; + if (spcont && spcont->find_cursor(name, &offset, false)) + { + rh= &sp_rcontext_handler_local; // A static cursor found + } + else if (spcont && (spv= find_variable(name, &rh))) + { + static constexpr LEX_CSTRING cursor_attr= + {STRING_WITH_LEN("%cursor_attr")}; + if (check_variable_is_refcursor(cursor_attr, spv)) + return nullptr; + // A SYS_REFCURSOR variable found + offset= spv->offset; + deref_rcontext_handler= &sp_rcontext_handler_statement; + } + else { my_error(ER_SP_CURSOR_MISMATCH, MYF(0), name->str); return NULL; } + const Cursor_ref ref(name, rh, offset, deref_rcontext_handler); switch (attr) { case PLSQL_CURSOR_ATTR_ISOPEN: - return new (thd->mem_root) Item_func_cursor_isopen(thd, name, offset); + return new (thd->mem_root) Item_func_cursor_isopen(thd, ref); case PLSQL_CURSOR_ATTR_FOUND: - return new (thd->mem_root) Item_func_cursor_found(thd, name, offset); + return new (thd->mem_root) Item_func_cursor_found(thd, ref); case PLSQL_CURSOR_ATTR_NOTFOUND: - return new (thd->mem_root) Item_func_cursor_notfound(thd, name, offset); + return new (thd->mem_root) Item_func_cursor_notfound(thd, ref); case PLSQL_CURSOR_ATTR_ROWCOUNT: - return new (thd->mem_root) Item_func_cursor_rowcount(thd, name, offset); + return new (thd->mem_root) Item_func_cursor_rowcount(thd, ref); } DBUG_ASSERT(0); return NULL; @@ -9512,22 +9643,43 @@ int set_statement_var_if_exists(THD *thd, const char *var_name, } -sp_instr_cfetch *LEX::sp_add_instr_cfetch(THD *thd, const LEX_CSTRING *name) +/* + Add instructions to handle "FETCH cur INTO targets". + It covers both static cursors and SYS_REFCUSORs. +*/ +sp_instr_fetch_cursor * +LEX::sp_add_instr_fetch_cursor(THD *thd, const LEX_CSTRING *name) { uint offset; - sp_instr_cfetch *i; - if (!spcont->find_cursor(name, &offset, false)) + // Search for a static cursor with the given name first + if (spcont->find_cursor(name, &offset, false)) { - my_error(ER_SP_CURSOR_MISMATCH, MYF(0), name->str); - return nullptr; + sp_instr_cfetch *i= new (thd->mem_root) + sp_instr_cfetch(sphead->instructions(), + spcont, offset, + !(thd->variables.sql_mode & MODE_ORACLE)); + return i == nullptr || sphead->add_instr(i) ? nullptr : i; } - i= new (thd->mem_root) - sp_instr_cfetch(sphead->instructions(), spcont, offset, - !(thd->variables.sql_mode & MODE_ORACLE)); - if (unlikely(i == NULL) || unlikely(sphead->add_instr(i))) - return nullptr; - return i; + + // Search for a SYS_REFCURSOR variable + const Sp_rcontext_handler *rh; + const sp_variable *spv= find_variable(name, &rh); + if (spv) + { + if (check_variable_is_refcursor({STRING_WITH_LEN("FETCH")}, spv)) + return nullptr; + auto *i= new (thd->mem_root) sp_instr_cfetch_by_ref( + sphead->instructions(), spcont, + sp_rcontext_ref( + sp_rcontext_addr(rh, spv->offset), + &sp_rcontext_handler_statement), + !(thd->variables.sql_mode & MODE_ORACLE)); + return i == nullptr || sphead->add_instr(i) ? nullptr : i; + } + + my_error(ER_SP_CURSOR_MISMATCH, MYF(0), name->str); + return nullptr; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2ea96fd1764..2702fc7960a 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -148,7 +148,7 @@ class LEX_COLUMN; class sp_head; class sp_name; class sp_instr; -class sp_instr_cfetch; +class sp_instr_fetch_cursor; class sp_pcontext; class sp_variable; class sp_fetch_target; @@ -3220,6 +3220,7 @@ private: MEM_ROOT *mem_root_for_set_stmt; bool sp_block_finalize(THD *thd, const Lex_spblock_st spblock, class sp_label **splabel); + bool sp_block_destruct_variables(THD *thd, sp_pcontext *pctx); bool sp_change_context(THD *thd, const sp_pcontext *ctx, bool exclusive); bool sp_exit_block(THD *thd, sp_label *lab); bool sp_exit_block(THD *thd, sp_label *lab, Item *when, @@ -3875,6 +3876,14 @@ public: sp_pcontext *not_used_ctx; return find_variable(name, ¬_used_ctx, rh); } + /* + Check if a variable can be used as a refcursor for a cursor statement: + OPEN name FOR stmt; + FETCH name ...; + CLOSE name; + */ + bool check_variable_is_refcursor(const LEX_CSTRING &verb_clause, + const sp_variable *var) const; sp_fetch_target *make_fetch_target(THD *thd, const Lex_ident_sys_st &name); bool set_variable(const Lex_ident_sys_st *name, Item *item, const LEX_CSTRING &expr_str); @@ -3950,6 +3959,10 @@ public: bool sp_open_cursor(THD *thd, const LEX_CSTRING *name, List *parameters); + bool sp_open_cursor_for_stmt(THD *thd, const LEX_CSTRING *name, + sp_lex_cursor *stmt); + bool sp_close(THD *thd, const Lex_ident_sys_st &name); + Item_splocal *create_item_for_sp_var(const Lex_ident_cli_st *name, sp_variable *spvar); @@ -4213,6 +4226,7 @@ public: // Unlabeled blocks get an empty label sp_block_init(thd, &empty_clex_str); } + void sp_block_init_package_body(THD *thd); bool sp_block_finalize(THD *thd, const Lex_spblock_st spblock) { class sp_label *tmp; @@ -4506,7 +4520,8 @@ public: create_info.add(options); return check_create_options(create_info); } - sp_instr_cfetch *sp_add_instr_cfetch(THD *thd, const LEX_CSTRING *name); + sp_instr_fetch_cursor* sp_add_instr_fetch_cursor(THD *thd, + const LEX_CSTRING *name); bool sp_add_agg_cfetch(); bool set_command_with_check(enum_sql_command command, diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 4654cf857cd..92d6a3191ba 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -260,6 +260,19 @@ private: bool reprepare(); bool validate_metadata(Prepared_statement *copy); void swap_prepared_statement(Prepared_statement *copy); + + // Run the expression event handler for all placeholders + void placeholders_expr_event_handler(expr_event_t event) + { + List_iterator_fast item_param_it(lex->param_list); + for (Item_param *item_param= item_param_it++; + item_param; + item_param= item_param_it++) + { + item_param->expr_event_handler(thd, event); + } + } + }; /** @@ -5192,6 +5205,8 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) thd->protocol->send_out_parameters(&this->lex->param_list); } + placeholders_expr_event_handler(expr_event_t::DESTRUCT_DYNAMIC_PARAM); + error: error|= thd->lex->restore_set_statement_var(); flags&= ~ (uint) IS_IN_USE; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c3b51a7581e..fb00d2f2480 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -12834,7 +12834,14 @@ end: static JOIN_TAB *next_breadth_first_tab(JOIN_TAB *first_top_tab, uint n_top_tabs_count, JOIN_TAB *tab) { - n_top_tabs_count += tab->join->aggr_tables; + /* + tab->join == NULL means that we're performing JOIN::cleanup() + after a raised error: on EOM, or on an attempt to create a temporary table + with a column of a non allowed data type, such as SYS_REFCURSOR. + */ + DBUG_ASSERT(tab->join || current_thd->is_error()); + if (tab->join) + n_top_tabs_count += tab->join->aggr_tables; if (!tab->bush_root_tab) { /* We're at top level. Get the next top-level tab */ @@ -22087,7 +22094,7 @@ bool Create_tmp_table::add_fields(THD *thd, param->force_copy_fields); if (unlikely(!new_field)) { - if (unlikely(thd->is_fatal_error)) + if (unlikely(thd->is_fatal_error || item->cols() == 1)) goto err; // Got OOM continue; // Some kind of const item } @@ -22838,6 +22845,11 @@ void Virtual_tmp_table::setup_field_pointers() } } cur_field->reset(); + /* + SYS_REFCURSOR SP variables need NULL as the initial Field value + to watch sp_cursor_array_element::m_ref_count properly. + */ + cur_field->set_null(); field_pos+= cur_field->pack_length(); } } diff --git a/sql/sql_select.h b/sql/sql_select.h index b20a48eaaa3..f2e634edcfb 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -2427,6 +2427,25 @@ public: for (uint i= 0; i < s->fields; i++) field[i]->set_null(); } + + /* + Run the event handler for all fields in the table + in the range [start, end-1]. + */ + void expr_event_handler(THD *thd, expr_event_t event, uint start, uint end) + { + DBUG_ASSERT(start <= end); + DBUG_ASSERT(end <= s->fields); + for (uint i= start; i < end; i++) + field[i]->expr_event_handler(thd, event); + } + + // Run the event handler for all fields in the table + void expr_event_handler(THD *thd, expr_event_t event) + { + expr_event_handler(thd, event, 0, s->fields); + } + /** Set all fields from a compatible item list. The number of fields in "this" must be equal to the number diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 2540c6d6c11..2df3be3f790 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -3416,6 +3416,24 @@ bool Type_handler_bit:: } +/*************************************************************************/ +bool Type_handler_row::Spvar_definition_with_complex_data_types( + Spvar_definition *def) const +{ + if (def->row_field_definitions()) + { + List_iterator it(*(def->row_field_definitions())); + Spvar_definition *member; + while ((member= it++)) + { + if (member->type_handler()->is_complex()) + return true; + } + } + return false; +} + + /*************************************************************************/ bool Type_handler::Key_part_spec_init_primary(Key_part_spec *part, const Column_definition &def, @@ -5292,6 +5310,15 @@ bool Type_handler::Item_func_hybrid_field_type_get_date_with_warn(THD *thd, } +Type_ref_null +Type_handler::Item_func_hybrid_field_type_val_ref(THD *thd, + Item_func_hybrid_field_type *item) + const +{ + return Type_ref_null(); +} + + /************************************************************************/ void Type_handler_decimal_result::Item_get_date(THD *thd, Item *item, Temporal::Warn *warn, diff --git a/sql/sql_type.h b/sql/sql_type.h index 33c479f4f0d..d779eeb7b0e 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -25,6 +25,7 @@ #include "sql_time.h" #include "sql_type_string.h" #include "sql_type_real.h" +#include "sql_type_ref.h" #include "compat56.h" #include "log_event_data_type.h" @@ -52,6 +53,8 @@ class Item_func_hex; class Item_hybrid_func; class Item_func_min_max; class Item_func_hybrid_field_type; +class Item_func_last_value; +class Item_func_sp; class Item_bool_func2; class Item_bool_rowready_func2; class Item_func_between; @@ -157,6 +160,57 @@ scalar_comparison_op_to_lex_cstring(scalar_comparison_op op) } +enum class expr_event_t : uint32 +{ + NONE= 0, + + /* + A result set row has been sent to the result sink, e.g. to the client. + All values in the row are not needed any more. + */ + DESTRUCT_RESULT_SET_ROW_FIELD= 1 << 0, + + /* + An Item_func evaluated its result, + argument values are not needed any more. + */ + DESTRUCT_ROUTINE_ARG= 1 << 1, + + /* + A value has been assigned to an SP variable. + */ + DESTRUCT_ASSIGNMENT_RIGHT_HAND= 1 << 2, + + /* + Flow control has left a BEGIN..END block, + all variables declared in this block are not needed any more. + */ + DESTRUCT_OUT_OF_SCOPE= 1 << 3, + + /* + A prepared statement has finished. + The values of Item_param instances are not needed any more. + */ + DESTRUCT_DYNAMIC_PARAM= 1 << 4, + + /* + Any kind of destruction listed above. + */ + DESTRUCT_ANY= (uint32) (DESTRUCT_RESULT_SET_ROW_FIELD | + DESTRUCT_ROUTINE_ARG | + DESTRUCT_ASSIGNMENT_RIGHT_HAND | + DESTRUCT_OUT_OF_SCOPE | + DESTRUCT_DYNAMIC_PARAM) +}; + + +static inline constexpr expr_event_t operator&(const expr_event_t a, + const expr_event_t b) +{ + return (expr_event_t) (((uint32) a) & ((uint32) b)); +} + + class Hasher { ulong m_nr1; @@ -4065,6 +4119,18 @@ public: { return this; } + + /* + Returns true if this data type is complex (has some side effect), + so values of this type need additional handling, e.g. on destruction. + For example, SYS_REFCURSOR has a side effect: + it writes and reads THD::m_statement_cursors. + */ + virtual bool is_complex() const + { + return false; + } + virtual bool partition_field_check(const LEX_CSTRING &field_name, Item *) const { @@ -4083,6 +4149,15 @@ public: type_handler_adjusted_to_max_octet_length(uint max_octet_length, CHARSET_INFO *cs) const { return this; } + /* + Check if an Spvar_definition instance is of a complex data type, + or contains a complex data type in its components (e.g. a ROW member). + */ + virtual bool Spvar_definition_with_complex_data_types(Spvar_definition *def) + const + { + return false; + } virtual bool adjust_spparam_type(Spvar_definition *def, Item *from) const { return false; @@ -4095,6 +4170,7 @@ public: */ bool is_traditional_scalar_type() const; virtual bool is_scalar_type() const { return true; } + virtual bool can_return_bool() const { return can_return_int(); } virtual bool can_return_int() const { return true; } virtual bool can_return_decimal() const { return true; } virtual bool can_return_real() const { return true; } @@ -4338,6 +4414,9 @@ public: virtual void Item_param_setup_conversion(THD *thd, Item_param *) const {} virtual void Item_param_set_param_func(Item_param *param, uchar **pos, ulong len) const; + virtual void Item_param_expr_event_handler(THD *thd, Item_param *param, + expr_event_t event) const + { } virtual bool Item_param_set_from_value(THD *thd, Item_param *param, const Type_all_attributes *attr, @@ -4345,6 +4424,12 @@ public: virtual bool Item_param_val_native(THD *thd, Item_param *item, Native *to) const; + virtual Type_ref_null Item_param_val_ref(THD *thd, const Item_param *item) + const + { + return Type_ref_null(); + } + virtual bool Item_send(Item *item, Protocol *p, st_value *buf) const= 0; virtual int Item_save_in_field(Item *item, Field *field, bool no_conversions) const= 0; @@ -4581,6 +4666,10 @@ public: MYSQL_TIME *, date_mode_t) const; virtual + Type_ref_null Item_func_hybrid_field_type_val_ref(THD *thd, + Item_func_hybrid_field_type *item) + const; + virtual String *Item_func_min_max_val_str(Item_func_min_max *, String *) const= 0; virtual double Item_func_min_max_val_real(Item_func_min_max *) const= 0; @@ -4753,6 +4842,9 @@ public: { return false; } + bool Spvar_definition_with_complex_data_types(Spvar_definition *def) + const override; + Field *make_table_field(MEM_ROOT *, const LEX_CSTRING *, const Record_addr &, const Type_all_attributes &, TABLE_SHARE *) const override @@ -7902,7 +7994,7 @@ extern MYSQL_PLUGIN_IMPORT Named_type_handler type_han extern MYSQL_PLUGIN_IMPORT Named_type_handler type_handler_slonglong; extern Named_type_handler type_handler_utiny; -extern Named_type_handler type_handler_ushort; +extern MYSQL_PLUGIN_IMPORT Named_type_handler type_handler_ushort; extern Named_type_handler type_handler_uint24; extern MYSQL_PLUGIN_IMPORT Named_type_handler type_handler_ulong; extern MYSQL_PLUGIN_IMPORT Named_type_handler type_handler_ulonglong; diff --git a/sql/sql_type_fixedbin.h b/sql/sql_type_fixedbin.h index c1a1567c03b..8fb991944b0 100644 --- a/sql/sql_type_fixedbin.h +++ b/sql/sql_type_fixedbin.h @@ -1177,6 +1177,7 @@ public: bool is_scalar_type() const override { return true; } bool is_val_native_ready() const override { return true; } + bool can_return_bool() const override { return true; } bool can_return_int() const override { return false; } bool can_return_decimal() const override { return false; } bool can_return_real() const override { return false; } diff --git a/sql/sql_type_geom.h b/sql/sql_type_geom.h index 97764be7047..9dc4a55edaa 100644 --- a/sql/sql_type_geom.h +++ b/sql/sql_type_geom.h @@ -178,7 +178,7 @@ public: const Bit_addr &bit, const Column_definition_attributes *attr, uint32 flags) const override; - + bool can_return_bool() const override { return true; } bool can_return_int() const override { return false; } bool can_return_decimal() const override { return false; } bool can_return_real() const override { return false; } diff --git a/sql/sql_type_int.h b/sql/sql_type_int.h index b8ebf7d4d42..7cbe5f6c97e 100644 --- a/sql/sql_type_int.h +++ b/sql/sql_type_int.h @@ -152,9 +152,15 @@ public: class ULonglong_null: public ULonglong, public Null_flag { public: + ULonglong_null() + :ULonglong(0), Null_flag(true) + { } ULonglong_null(ulonglong nr, bool is_null) :ULonglong(nr), Null_flag(is_null) { } + explicit ULonglong_null(ulonglong nr) + :ULonglong(nr), Null_flag(false) + { } /* Multiply two ulonglong values. diff --git a/sql/sql_type_ref.h b/sql/sql_type_ref.h new file mode 100644 index 00000000000..f0e81a596d1 --- /dev/null +++ b/sql/sql_type_ref.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2018, 2025, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#ifndef SQL_TYPE_REF_INCLUDED +#define SQL_TYPE_REF_INCLUDED + +#include "sql_type_int.h" // Null_flag + + +class Type_ref_null: public Null_flag +{ + ulonglong m_value; +public: + Type_ref_null() + :Null_flag(true), + m_value(0) + { } + Type_ref_null(ulonglong value) + :Null_flag(false), + m_value(value) + { } + ulonglong value() const { return m_value; } + bool operator<(ulonglong val) const + { + return !m_is_null && m_value < val; + } +}; + +#endif // SQL_TYPE_REF_INCLUDED diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 01d7a056c89..f3645e23c2a 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -600,6 +600,26 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, goto err; } + // Disallow non-deterministic data types such as SYS_REFCURSOR + if (thd->stmt_arena->with_complex_data_types()) + { + /* + There are some complex data types. + Let's find which exactly and raise an error. + */ + if (thd->stmt_arena->check_free_list_no_complex_data_types("CREATE VIEW")) + { + res= true; + goto err; + } + /* + Perhaps some item tree transformation happened. + All items with complex data types return false in const_item(), + so no transformation should happen. + */ + DBUG_ASSERT(0); + } + #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Compare/check grants on view with grants of underlying tables diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c95af7682e0..ab86d09a97b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -258,7 +258,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() Item_basic_constant *item_basic_constant; Key_part_spec *key_part; LEX *lex; - sp_instr_cfetch *instr_cfetch; + sp_instr_fetch_cursor *instr_fetch_cursor; sp_expr_lex *expr_lex; sp_assignment_lex *assignment_lex; class sp_lex_cursor *sp_cursor_stmt; @@ -1589,7 +1589,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); sp_cursor_stmt_lex sp_cursor_stmt -%type +%type sp_proc_stmt_fetch_head %type @@ -4371,22 +4371,27 @@ sp_proc_stmt_open: if (unlikely(Lex->sp_open_cursor(thd, &$2, $3))) MYSQL_YYABORT; } + | OPEN_SYM ident FOR_SYM sp_cursor_stmt + { + if (Lex->sp_open_cursor_for_stmt(thd, &$2, $4)) + MYSQL_YYABORT; + } ; sp_proc_stmt_fetch_head: FETCH_SYM ident INTO { - if (unlikely(!($$= Lex->sp_add_instr_cfetch(thd, &$2)))) + if (unlikely(!($$= Lex->sp_add_instr_fetch_cursor(thd, &$2)))) MYSQL_YYABORT; } | FETCH_SYM FROM ident INTO { - if (unlikely(!($$= Lex->sp_add_instr_cfetch(thd, &$3)))) + if (unlikely(!($$= Lex->sp_add_instr_fetch_cursor(thd, &$3)))) MYSQL_YYABORT; } | FETCH_SYM NEXT_SYM FROM ident INTO { - if (unlikely(!($$= Lex->sp_add_instr_cfetch(thd, &$4)))) + if (unlikely(!($$= Lex->sp_add_instr_fetch_cursor(thd, &$4)))) MYSQL_YYABORT; } ; @@ -4406,17 +4411,7 @@ sp_proc_stmt_fetch: sp_proc_stmt_close: CLOSE_SYM ident { - LEX *lex= Lex; - sp_head *sp= lex->sphead; - uint offset; - sp_instr_cclose *i; - - if (unlikely(!lex->spcont->find_cursor(&$2, &offset, false))) - my_yyabort_error((ER_SP_CURSOR_MISMATCH, MYF(0), $2.str)); - i= new (thd->mem_root) - sp_instr_cclose(sp->instructions(), lex->spcont, offset); - if (unlikely(i == NULL) || - unlikely(sp->add_instr(i))) + if (Lex->sp_close(thd, $2)) MYSQL_YYABORT; } ; @@ -20064,7 +20059,7 @@ create_routine: Lex->sp_chistics)))) MYSQL_YYABORT; Lex->sphead->set_body_start(thd, YYLIP->get_cpp_tok_start()); - Lex->sp_block_init(thd); + Lex->sp_block_init_package_body(thd); } sp_tail_is package_implementation_declare_section diff --git a/sql/statement_rcontext.h b/sql/statement_rcontext.h new file mode 100644 index 00000000000..01de528620a --- /dev/null +++ b/sql/statement_rcontext.h @@ -0,0 +1,84 @@ +/* + Copyright (c) 2025, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +#ifndef STATEMENT_RCONTEXT_INCLUDED +#define STATEMENT_RCONTEXT_INCLUDED + +#include "sp_cursor.h" + +/* + Top level statement-wide run time context for stored routines. + Unlike sp_rcontext, which contains structures + (e.g. variables, cursors, condition handlers) belonging to one stored routine, + Statement_rcontext contains structures shared between all stored routines + during the top level statement execution. +*/ +class Statement_rcontext +{ + /* + Open cursor counter. It watches OPEN/CLOSE for all kinds of cursors: + - Static cursors: + DECLARE c CURSOR FOR SELECT ...; + - SYS_REFCURSORs: + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT ...; + It's used to respect the @@max_open_cursors system variable. + */ + uint m_open_cursors_counter; + + sp_cursor_array m_statement_cursors; + +public: + Statement_rcontext() + :m_open_cursors_counter(0) + { } + + sp_cursor_array *statement_cursors() + { + return &m_statement_cursors; + } + + uint open_cursors_counter() const + { + return m_open_cursors_counter; + } + void open_cursors_counter_increment() + { + m_open_cursors_counter++; + } + void open_cursors_counter_decrement() + { + DBUG_ASSERT(m_open_cursors_counter > 0); + m_open_cursors_counter--; + } + + /* + Free all top level statement data data and reinit "this" + for a new top level statement. + It's called in the very end of the top level statement + (it's not called for individual stored routune statements). + */ + void reinit(THD *thd) + { + // Close SYS_REFCURSORs which did not have explicit CLOSE statement: + m_statement_cursors.free(thd); + // Zero open cursor counter: + DBUG_ASSERT(open_cursors_counter() == 0); + m_open_cursors_counter= 0; // Safety + } +}; + +#endif // STATEMENT_RCONTEXT_INCLUDED diff --git a/sql/structs.h b/sql/structs.h index ac0510bed5d..3dda2047d0b 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -1128,4 +1128,37 @@ protected: uint m_offset; ///< Frame offset }; + +/* + This class stores run time references + (variables that store offsets of another variable or a cursor). + + - The "sp_rcontext_addr" contains the address of the reference variable. + Its value is evaluated (using val_ref() of the variable's Field) + to get the rcontext offset of the referenced variable. + + - The m_deref_rcontext_handler member contains the rcontext handler of + the referenced variable (or cursor). + m_deref_rcontext_handler can be set to nullptr to mean that + the sp_rcontext_ref instance is not a reference. In this case its + sp_rcontext_addr contains the direct address of the target variable/cursor + and does not need dereferencing. +*/ +class sp_rcontext_ref: public sp_rcontext_addr +{ +public: + sp_rcontext_ref(const sp_rcontext_addr &addr, + const Sp_rcontext_handler *deref_rcontext_handler) + :sp_rcontext_addr(addr), + m_deref_rcontext_handler(deref_rcontext_handler) + { } + const Sp_rcontext_handler *deref_rcontext_handler() const + { + return m_deref_rcontext_handler; + } +protected: + const Sp_rcontext_handler *m_deref_rcontext_handler; +}; + + #endif /* STRUCTS_INCLUDED */ diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index e601ad4c41d..379401644b2 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2733,6 +2733,12 @@ static Sys_var_ulong Sys_max_sp_recursion_depth( VALID_RANGE(0, 255), DEFAULT(0), BLOCK_SIZE(1)); +static Sys_var_uint Sys_max_open_cursors( + "max_open_cursors", + "The maximum number of open cursors allowed per session", + SESSION_VAR(max_open_cursors), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 64*1024), DEFAULT(50), BLOCK_SIZE(1)); + static bool if_checking_enabled(sys_var *self, THD *thd, set_var *var) { if (session_readonly(self, thd, var))