mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-39518 Allow prepared statements in stored functions in assignment right hand
Allowing prepared statements in stored functions when
a stored function is used in an assignment right hand.
Both DEFAULT clause of a variable initialization and
the right side of the SET statement are supported:
CREATE PROCEDURE p1()
BEGIN
-- case 1: DEFAULT clause
DECLARE spvar1 INT DEFAULT f1_with_ps(); -- OK
-- case 2: SP variable assignment statement
DECLARE spvar2 INT;
SET spvar2= f1_with_ps(); -- OK
END;
- Only assignments to SP variables works for now:
* SET spvar= func_with_ps(); -- OK
* SET @uvar= func_with_ps(); -- Error
- Only bare function calls are supported for now. Using a function in
an expression does not make it PS-safe yet:
SET v= f1()+0;
- The parser now does not reject PS statements in stored functions.
PS applicability in stored functions is now detected at run time.
Note, PS statements in triggers are still prohibited by the parser.
- Functions with PS do not acquire MDL locks on tables, and no MDL is
taken on the routines themselves either. They work like procedures in
terms of table opening and routine locking: a concurrent DROP FUNCTION
can complete while such a function is executing.
- Functions with PS are not replicated as a single `SELECT f1()` call.
They are replicated per-statement, like procedures.
Helper changes:
- Changing the return result for LEX::sp_variable_declarations_init()
from void to bool to catch errors in the caller properly.
Misc:
- This patch incorporates fixes for the following bugs found during debugging:
MDEV-40224,MDEV-40225,MDEV-40226,MDEV-40227,MDEV-40240,
MDEV-40285,MDEV-40288,MDEV-40315,MDEV-40318,MDEV-40890,MDEV-40900,
MDEV-40901,MDEV-40913,MDEV-40914,MDEV-41013,MDEV-41015,MDEV-41019
Assisted-by: Claude - reviews and minor clean-ups
This commit is contained in:
36
mysql-test/include/ps_in_func-mixture-01-create.inc
Normal file
36
mysql-test/include/ps_in_func-mixture-01-create.inc
Normal file
@@ -0,0 +1,36 @@
|
||||
--echo #
|
||||
--echo # A function without PS called differently:
|
||||
--echo # - from a procedure through a function with PS
|
||||
--echo # - from a procedure directly
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (val VARCHAR(30), caller VARCHAR(128));
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_without_ps(ofs INT, caller VARCHAR(128)) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE caller2 VARCHAR(128) DEFAULT CONCAT(caller,'/f_without_ps');
|
||||
INSERT INTO t1 (val, caller) VALUES (ofs+1, caller2);
|
||||
INSERT INTO t1 (val, caller) VALUES (ofs+2, caller2);
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE FUNCTION f_with_ps(ofs INT, caller VARCHAR(128)) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE query TEXT DEFAULT 'INSERT INTO t1 (val,caller) VALUES (?,?)';
|
||||
DECLARE caller2 VARCHAR(128) DEFAULT CONCAT(caller,'/f_with_ps');
|
||||
DECLARE a INT DEFAULT f_without_ps(ofs, caller2);
|
||||
EXECUTE IMMEDIATE query USING ofs+3, caller2;
|
||||
EXECUTE IMMEDIATE query USING ofs+4, caller2;
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE query TEXT DEFAULT 'INSERT INTO t1 (val,caller) VALUES (?,?)';
|
||||
DECLARE v1 INT DEFAULT f_with_ps(100, 'p1');
|
||||
DECLARE v2 INT DEFAULT f_without_ps(200, 'p1');
|
||||
EXECUTE IMMEDIATE query USING 301, 'p1';
|
||||
EXECUTE IMMEDIATE query USING 302, 'p1';
|
||||
END $$
|
||||
DELIMITER ;$$
|
||||
4
mysql-test/include/ps_in_func-mixture-01-drop.inc
Normal file
4
mysql-test/include/ps_in_func-mixture-01-drop.inc
Normal file
@@ -0,0 +1,4 @@
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f_with_ps;
|
||||
DROP FUNCTION f_without_ps;
|
||||
DROP TABLE t1;
|
||||
169
mysql-test/main/client_multi_results-debug.result
Normal file
169
mysql-test/main/client_multi_results-debug.result
Normal file
@@ -0,0 +1,169 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# A procedure with a plain SELECT needs CLIENT_MULTI_RESULTS
|
||||
#
|
||||
CREATE PROCEDURE p1() SELECT 1 AS c;
|
||||
CALL p1;
|
||||
c
|
||||
1
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
ERROR 0A000: PROCEDURE test.p1 can't return a result set in the given context
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
DROP PROCEDURE p1;
|
||||
#
|
||||
# A CALL of a procedure with EXECUTE requires the client
|
||||
# to support CLIENT_MULTI_RESULTS: the executed statement
|
||||
# can return a result set.
|
||||
#
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT 1 AS c';
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
c
|
||||
1
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
ERROR 0A000: PROCEDURE test.p1 can't return a result set in the given context
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
c
|
||||
1
|
||||
DROP PROCEDURE p1;
|
||||
#
|
||||
# The same for EXECUTE IMMEDIATE
|
||||
#
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c';
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
c
|
||||
1
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
ERROR 0A000: PROCEDURE test.p1 can't return a result set in the given context
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
c
|
||||
1
|
||||
DROP PROCEDURE p1;
|
||||
#
|
||||
# A procedure without EXECUTE does not need CLIENT_MULTI_RESULTS,
|
||||
# even if it uses PREPARE and DEALLOCATE PREPARE.
|
||||
#
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT 1 AS c';
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
DROP PROCEDURE p1;
|
||||
#
|
||||
# A procedure calling a function with a prepared statement
|
||||
# in the assignment right hand does not return a result set,
|
||||
# so it does not need CLIENT_MULTI_RESULTS.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A procedure with EXECUTE 'INSERT..' called from a function
|
||||
# used in a safe context.
|
||||
# The client capability is not relevant here.
|
||||
# The CALL works with and without the client capability.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A procedure with EXECUTE 'SELECT..' called from a function
|
||||
# used in a safe context.
|
||||
# The client capability is not relevant here: a dynamic statement
|
||||
# returning a result set inside a function is rejected at run time,
|
||||
# with and without the client capability.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p3_select()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_select() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3_select();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_select()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2_select();
|
||||
END;
|
||||
$$
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1_select;
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1_select;
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
DROP PROCEDURE p1_select;
|
||||
DROP FUNCTION f2_select;
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
195
mysql-test/main/client_multi_results-debug.test
Normal file
195
mysql-test/main/client_multi_results-debug.test
Normal file
@@ -0,0 +1,195 @@
|
||||
--source include/have_debug.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with a plain SELECT needs CLIENT_MULTI_RESULTS
|
||||
--echo #
|
||||
|
||||
CREATE PROCEDURE p1() SELECT 1 AS c;
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
--error ER_SP_BADSELECT
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A CALL of a procedure with EXECUTE requires the client
|
||||
--echo # to support CLIENT_MULTI_RESULTS: the executed statement
|
||||
--echo # can return a result set.
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT 1 AS c';
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
--error ER_SP_BADSELECT
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The same for EXECUTE IMMEDIATE
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c';
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
--error ER_SP_BADSELECT
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure without EXECUTE does not need CLIENT_MULTI_RESULTS,
|
||||
--echo # even if it uses PREPARE and DEALLOCATE PREPARE.
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT 1 AS c';
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure calling a function with a prepared statement
|
||||
--echo # in the assignment right hand does not return a result set,
|
||||
--echo # so it does not need CLIENT_MULTI_RESULTS.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with EXECUTE 'INSERT..' called from a function
|
||||
--echo # used in a safe context.
|
||||
--echo # The client capability is not relevant here.
|
||||
--echo # The CALL works with and without the client capability.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with EXECUTE 'SELECT..' called from a function
|
||||
--echo # used in a safe context.
|
||||
--echo # The client capability is not relevant here: a dynamic statement
|
||||
--echo # returning a result set inside a function is rejected at run time,
|
||||
--echo # with and without the client capability.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3_select()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_select() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3_select();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_select()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2_select();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @@session.debug_dbug='+d,sp_no_client_multi_results';
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p1_select;
|
||||
SET @@session.debug_dbug='-d,sp_no_client_multi_results';
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p1_select;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1_select;
|
||||
DROP FUNCTION f2_select;
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
@@ -4831,7 +4831,9 @@ SELECT @@max_sort_length;
|
||||
1024
|
||||
SET @@max_sort_length=DEFAULT;
|
||||
#
|
||||
# Similar to prepared EXECUTE, IMMEDIATE is not allowed in stored functions
|
||||
# A function with EXECUTE IMMEDIATE can be used with restrictions:
|
||||
# Usually it's not allowed (e.g. in SELECT list)
|
||||
# But it's allowed as an assignment right hand
|
||||
#
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
@@ -4839,7 +4841,17 @@ EXECUTE IMMEDIATE 'DO 1';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a=f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
#
|
||||
# Status variables
|
||||
#
|
||||
|
||||
@@ -4337,10 +4337,11 @@ SET @@max_sort_length=DEFAULT;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Similar to prepared EXECUTE, IMMEDIATE is not allowed in stored functions
|
||||
--echo # A function with EXECUTE IMMEDIATE can be used with restrictions:
|
||||
--echo # Usually it's not allowed (e.g. in SELECT list)
|
||||
--echo # But it's allowed as an assignment right hand
|
||||
--echo #
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DO 1';
|
||||
@@ -4348,6 +4349,19 @@ BEGIN
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a=f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo #
|
||||
|
||||
12
mysql-test/main/ps_in_func-caller-tables.combinations
Normal file
12
mysql-test/main/ps_in_func-caller-tables.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
285
mysql-test/main/ps_in_func-caller-tables.result
Normal file
285
mysql-test/main/ps_in_func-caller-tables.result
Normal file
@@ -0,0 +1,285 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# Called from the select list or the WHERE clause,
|
||||
# function modifies the scanned table
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT f1() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Called from the select list of the WHERE clause,
|
||||
# function modifies another table
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT f1() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# A caller without tables of its own
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT 1 FROM DUAL WHERE f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CALL p1();
|
||||
n
|
||||
1
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# An assignment right hand side is safe even with an open cursor
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
DECLARE v INT;
|
||||
DECLARE c CURSOR FOR SELECT a FROM t1 ORDER BY a;
|
||||
OPEN c;
|
||||
FETCH c INTO v;
|
||||
SELECT v;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
CLOSE c;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
v
|
||||
1
|
||||
n
|
||||
1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# The check is conservative: it is based on the static
|
||||
# CONTAINS_DYNAMIC_SQL flag, so a function which only mentions a
|
||||
# prepared statement without ever executing one is rejected as well
|
||||
# when the caller holds table locks
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1';
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c CURSOR FOR stmt;
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
f1()
|
||||
10
|
||||
SELECT f1() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A function without dynamic SQL keeps pre-MDEV-39518 behavior
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (2);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# PS is not allowed if a function has subquery arguments:
|
||||
# An assignment right hand side is safe only when the assignment
|
||||
# statement has no tables of its own. A subquery in the function
|
||||
# argument list opens and locks tables, and the calling statement is
|
||||
# still using them while the function body runs.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CALL p2();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
# Under LOCK TABLES as well
|
||||
LOCK TABLES t1 WRITE;
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CALL p2();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
UNLOCK TABLES;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# The same for a CALL of a procedure with dynamic SQL:
|
||||
# a subquery in the procedure argument list opens and locks tables,
|
||||
# and they stay open and locked for the entire duration of the
|
||||
# procedure body.
|
||||
# PS in p2() is not allowed here, as it would destroy the state
|
||||
# of the caller.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE PROCEDURE p2(x INT)
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DO 1';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2((SELECT MAX(a) FROM t1));
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2(1);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1b()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
# Under LOCK TABLES as well
|
||||
LOCK TABLES t1 WRITE;
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
UNLOCK TABLES;
|
||||
# A CALL without tables of its own is still allowed
|
||||
CALL p1b();
|
||||
v
|
||||
1
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p1b;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# The table can be used by a statement which does not touch
|
||||
# the tables of the function itself.
|
||||
# PS is not allowed anyway.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
# End of 13.1 tests
|
||||
315
mysql-test/main/ps_in_func-caller-tables.test
Normal file
315
mysql-test/main/ps_in_func-caller-tables.test
Normal file
@@ -0,0 +1,315 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
# Outside of an assignment right hand side, a dynamic statement inside a
|
||||
# stored function is rejected. The function must be rejected before it is
|
||||
# entered: it is not pre-locked, so the calling statement did not enter
|
||||
# prelocked mode, and the cleanup of the rejected inner statement would
|
||||
# release the caller's lock while the caller is still reading its tables.
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Called from the select list or the WHERE clause,
|
||||
--echo # function modifies the scanned table
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() FROM t1;
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Called from the select list of the WHERE clause,
|
||||
--echo # function modifies another table
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() FROM t1;
|
||||
SELECT * FROM t2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A caller without tables of its own
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT 1 FROM DUAL WHERE f1();
|
||||
CALL p1();
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # An assignment right hand side is safe even with an open cursor
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
DECLARE v INT;
|
||||
DECLARE c CURSOR FOR SELECT a FROM t1 ORDER BY a;
|
||||
OPEN c;
|
||||
FETCH c INTO v;
|
||||
SELECT v;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
CLOSE c;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The check is conservative: it is based on the static
|
||||
--echo # CONTAINS_DYNAMIC_SQL flag, so a function which only mentions a
|
||||
--echo # prepared statement without ever executing one is rejected as well
|
||||
--echo # when the caller holds table locks
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1';
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c CURSOR FOR stmt;
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
SELECT f1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() FROM t1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A function without dynamic SQL keeps pre-MDEV-39518 behavior
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (2);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # PS is not allowed if a function has subquery arguments:
|
||||
--echo # An assignment right hand side is safe only when the assignment
|
||||
--echo # statement has no tables of its own. A subquery in the function
|
||||
--echo # argument list opens and locks tables, and the calling statement is
|
||||
--echo # still using them while the function body runs.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p2();
|
||||
--echo # Under LOCK TABLES as well
|
||||
LOCK TABLES t1 WRITE;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p2();
|
||||
UNLOCK TABLES;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The same for a CALL of a procedure with dynamic SQL:
|
||||
--echo # a subquery in the procedure argument list opens and locks tables,
|
||||
--echo # and they stay open and locked for the entire duration of the
|
||||
--echo # procedure body.
|
||||
--echo # PS in p2() is not allowed here, as it would destroy the state
|
||||
--echo # of the caller.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p2(x INT)
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DO 1';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2((SELECT MAX(a) FROM t1));
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2(1);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1b()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f2();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
--echo # Under LOCK TABLES as well
|
||||
LOCK TABLES t1 WRITE;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
UNLOCK TABLES;
|
||||
--echo # A CALL without tables of its own is still allowed
|
||||
CALL p1b();
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1b;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The table can be used by a statement which does not touch
|
||||
--echo # the tables of the function itself.
|
||||
--echo # PS is not allowed anyway.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1((SELECT MAX(a) FROM t1));
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
SELECT * FROM t2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
159
mysql-test/main/ps_in_func-commit.result
Normal file
159
mysql-test/main/ps_in_func-commit.result
Normal file
@@ -0,0 +1,159 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# PREPARE/EXECUTE/DEALLOCATE
|
||||
#
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
PREPARE stmt FROM query;
|
||||
EXECUTE stmt;
|
||||
SET nrows= ROW_COUNT();
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CALL p1('CREATE TABLE t1 (a INT)');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CREATE TABLE t1 (a INT);
|
||||
CALL p1('ALTER TABLE t1 MODIFY a BIGINT');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('DROP TABLE t1');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('START TRANSACTION');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('COMMIT');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('ROLLBACK');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('LOCK TABLES t1 READ');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('LOCK TABLES t1 WRITE');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
DROP TABLE t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
#
|
||||
# Same as above but with EXECUTE IMMEDIATE
|
||||
# Statements causing implicit or explicit commit are not allowed, e.g:
|
||||
# - DDL statements
|
||||
# - COMMIT
|
||||
# Even in assignment context
|
||||
#
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
EXECUTE IMMEDIATE query;
|
||||
SET nrows= ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CALL p1('CREATE TABLE t1 (a INT)');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CREATE TABLE t1 (a INT);
|
||||
CALL p1('ALTER TABLE t1 MODIFY a BIGINT');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('DROP TABLE t1');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('START TRANSACTION');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('COMMIT');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('ROLLBACK');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('LOCK TABLES t1 READ');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
CALL p1('LOCK TABLES t1 WRITE');
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
DROP TABLE t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
#
|
||||
# MDEV-40225 Assertion `!thd->in_sub_stmt && !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)' failed in int Locked_tables_list::unlock_locked_tables(THD *)
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'LOCK TABLES t1 WRITE';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# MDEV-40890 ROLLBACK TO SAVEPOINT crashes the server
|
||||
#
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
SAVEPOINT s;
|
||||
UPDATE t1 SET a=11;
|
||||
ROLLBACK TO SAVEPOINT s;
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SAVEPOINT s';
|
||||
UPDATE t1 SET a=11;
|
||||
EXECUTE IMMEDIATE 'ROLLBACK TO SAVEPOINT s';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
185
mysql-test/main/ps_in_func-commit.test
Normal file
185
mysql-test/main/ps_in_func-commit.test
Normal file
@@ -0,0 +1,185 @@
|
||||
--source include/have_innodb.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
# Statements causing implicit or explicit commit are not allowed in functions
|
||||
# - DDL statements
|
||||
# - COMMIT
|
||||
|
||||
--echo #
|
||||
--echo # PREPARE/EXECUTE/DEALLOCATE
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
PREPARE stmt FROM query;
|
||||
EXECUTE stmt;
|
||||
SET nrows= ROW_COUNT();
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('CREATE TABLE t1 (a INT)');
|
||||
CREATE TABLE t1 (a INT);
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('ALTER TABLE t1 MODIFY a BIGINT');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('DROP TABLE t1');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('START TRANSACTION');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('COMMIT');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('ROLLBACK');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('LOCK TABLES t1 READ');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('LOCK TABLES t1 WRITE');
|
||||
DROP TABLE t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Same as above but with EXECUTE IMMEDIATE
|
||||
--echo # Statements causing implicit or explicit commit are not allowed, e.g:
|
||||
--echo # - DDL statements
|
||||
--echo # - COMMIT
|
||||
--echo # Even in assignment context
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
EXECUTE IMMEDIATE query;
|
||||
SET nrows= ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('CREATE TABLE t1 (a INT)');
|
||||
CREATE TABLE t1 (a INT);
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('ALTER TABLE t1 MODIFY a BIGINT');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('DROP TABLE t1');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('START TRANSACTION');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('COMMIT');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('ROLLBACK');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('LOCK TABLES t1 READ');
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1('LOCK TABLES t1 WRITE');
|
||||
DROP TABLE t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40225 Assertion `!thd->in_sub_stmt && !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)' failed in int Locked_tables_list::unlock_locked_tables(THD *)
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'LOCK TABLES t1 WRITE';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40890 ROLLBACK TO SAVEPOINT crashes the server
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
SAVEPOINT s;
|
||||
UPDATE t1 SET a=11;
|
||||
ROLLBACK TO SAVEPOINT s;
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SAVEPOINT s';
|
||||
UPDATE t1 SET a=11;
|
||||
EXECUTE IMMEDIATE 'ROLLBACK TO SAVEPOINT s';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
39
mysql-test/main/ps_in_func-compound.result
Normal file
39
mysql-test/main/ps_in_func-compound.result
Normal file
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
BEGIN NOT ATOMIC
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC INSERT INTO t1 VALUES (1); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
45
mysql-test/main/ps_in_func-compound.test
Normal file
45
mysql-test/main/ps_in_func-compound.test
Normal file
@@ -0,0 +1,45 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
BEGIN NOT ATOMIC
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC INSERT INTO t1 VALUES (1); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
52
mysql-test/main/ps_in_func-condition-handler,innodb.rdiff
Normal file
52
mysql-test/main/ps_in_func-condition-handler,innodb.rdiff
Normal file
@@ -0,0 +1,52 @@
|
||||
--- ps_in_func-condition-handler.result 2026-08-28 12:14:17.026330090 +0400
|
||||
+++ ps_in_func-condition-handler,innodb.reject 2026-08-28 12:14:37.257080495 +0400
|
||||
@@ -37,12 +37,8 @@
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
-Warnings:
|
||||
-Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
-10
|
||||
-20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
@@ -86,12 +82,8 @@
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
-Warnings:
|
||||
-Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
-10
|
||||
-20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
@@ -137,11 +129,8 @@
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
ROLLBACK;
|
||||
-Warnings:
|
||||
-Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
-10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
@@ -187,11 +176,8 @@
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
ROLLBACK;
|
||||
-Warnings:
|
||||
-Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
-10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
12
mysql-test/main/ps_in_func-condition-handler.combinations
Normal file
12
mysql-test/main/ps_in_func-condition-handler.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
397
mysql-test/main/ps_in_func-condition-handler.result
Normal file
397
mysql-test/main/ps_in_func-condition-handler.result
Normal file
@@ -0,0 +1,397 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
# CONTINUE handler inside a PS safe function
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
END;
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
Warnings:
|
||||
Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
SET @error=NULL;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# CONTINUE handler inside a procedure calling a PS safe function
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE CONTINUE HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
END;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
Warnings:
|
||||
Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
20
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
SET @error=NULL;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# EXIT handler inside a PS-safe function
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE EXIT HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
RESIGNAL;
|
||||
END;
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
ROLLBACK;
|
||||
Warnings:
|
||||
Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
SET @error=NULL;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# EXIT handler inside a procedure calling a PS-safe function
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE EXIT HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
RESIGNAL;
|
||||
END;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ERROR 22003: Out of range value for column 'a' at row 1
|
||||
ROLLBACK;
|
||||
Warnings:
|
||||
Warning 1196 Some non-transactional changed tables couldn't be rolled back
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
SELECT @error;
|
||||
@error
|
||||
Error caught
|
||||
SET @error=NULL;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# EXECUTE IMMEDIATE 'SELECT' + CONTINUE HANDLER in the caller
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1 ORDER BY a';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE done INT DEFAULT 0;
|
||||
DECLARE c CURSOR FOR SELECT a FROM t1 ORDER BY a;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ SET done= done+1;
|
||||
OPEN c;
|
||||
FETCH c INTO v;
|
||||
SELECT v AS v_after_first_fetch;
|
||||
SET v= f_sel();
|
||||
SELECT v AS v_after_error, done;
|
||||
FETCH c INTO v;
|
||||
SELECT v AS v_after_second_fetch;
|
||||
SET v= f_sel();
|
||||
SET v= f_sel();
|
||||
SELECT v AS v_end, done AS done_end;
|
||||
CLOSE c;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
v_after_first_fetch
|
||||
1
|
||||
v_after_error done
|
||||
NULL 1
|
||||
v_after_second_fetch
|
||||
2
|
||||
v_end done_end
|
||||
NULL 3
|
||||
CALL p1();
|
||||
v_after_first_fetch
|
||||
1
|
||||
v_after_error done
|
||||
NULL 1
|
||||
v_after_second_fetch
|
||||
2
|
||||
v_end done_end
|
||||
NULL 3
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Reuse the same PS at top level after the failure
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1 WHERE a>? ORDER BY a';
|
||||
CREATE FUNCTION f1_ps_select() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt USING 1;
|
||||
RETURN 2;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f1_ps_select();
|
||||
SELECT v AS v_p2;
|
||||
END;
|
||||
$$
|
||||
CALL p2();
|
||||
v_p2
|
||||
NULL
|
||||
EXECUTE stmt USING 1;
|
||||
a
|
||||
2
|
||||
3
|
||||
CALL p2();
|
||||
v_p2
|
||||
NULL
|
||||
EXECUTE stmt USING 2;
|
||||
a
|
||||
3
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ps_select;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# SET STATEMENT ... FOR EXECUTE for 'SELECT..' inside the function
|
||||
# Handling ER_SP_NO_RETSET
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1 WHERE a>? ORDER BY a';
|
||||
CREATE FUNCTION f1_ss() RETURNS INT
|
||||
BEGIN
|
||||
SET STATEMENT max_sort_length=2048 FOR EXECUTE stmt USING CONCAT('1');
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f1_ss();
|
||||
SET v= f1_ss();
|
||||
SELECT v AS v_p2;
|
||||
END;
|
||||
$$
|
||||
CALL p2();
|
||||
v_p2
|
||||
NULL
|
||||
EXECUTE stmt USING 1;
|
||||
a
|
||||
2
|
||||
3
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ss;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# SET STATEMENT ... FOR EXECUTE for 'INSERT..' inside the function
|
||||
# Handling ER_DUP_ENTRY
|
||||
#
|
||||
SET @@max_sort_length=1024;
|
||||
CREATE TABLE t1(a INT PRIMARY KEY);
|
||||
INSERT INTO t1 VALUES (2048);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (?)';
|
||||
CREATE FUNCTION f1_ss(msl INT) RETURNS INT
|
||||
BEGIN
|
||||
SET STATEMENT max_sort_length=msl FOR EXECUTE stmt USING @@max_sort_length;
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE raised INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1062 /*ER_DUP_ENTRY*/
|
||||
BEGIN SET raised= raised+1; END;
|
||||
SET v= f1_ss(2049); -- This insert with no errors
|
||||
SET v= f1_ss(2048); -- This fires the handler on "duplicate key" error
|
||||
SET v= f1_ss(2048); -- This fires the handler on "duplicate key" error
|
||||
SELECT v, raised;
|
||||
END;
|
||||
$$
|
||||
CALL p2();
|
||||
v raised
|
||||
NULL 2
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
2048
|
||||
2049
|
||||
EXECUTE stmt USING 1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ss;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
SET @@max_sort_length=DEFAULT;
|
||||
#
|
||||
# EXECUTE for 'SELECT..' with nested functions
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
CREATE FUNCTION f1_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_outer() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE x INT DEFAULT f1_sel();
|
||||
RETURN x+100;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f2_outer();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p3();
|
||||
v
|
||||
NULL
|
||||
CALL p3();
|
||||
v
|
||||
NULL
|
||||
DROP PROCEDURE p3;
|
||||
DROP FUNCTION f2_outer;
|
||||
DROP FUNCTION f1_sel;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
371
mysql-test/main/ps_in_func-condition-handler.test
Normal file
371
mysql-test/main/ps_in_func-condition-handler.test
Normal file
@@ -0,0 +1,371 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo # CONTINUE handler inside a PS safe function
|
||||
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
END;
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
SET @error=NULL;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # CONTINUE handler inside a procedure calling a PS safe function
|
||||
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE CONTINUE HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
END;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
SET @error=NULL;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # EXIT handler inside a PS-safe function
|
||||
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE EXIT HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
RESIGNAL;
|
||||
END;
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
--error ER_WARN_DATA_OUT_OF_RANGE
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
--error ER_WARN_DATA_OUT_OF_RANGE
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
SET @error=NULL;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # EXIT handler inside a procedure calling a PS-safe function
|
||||
|
||||
CREATE TABLE t1 (a TINYINT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (1000);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE EXIT HANDLER FOR SQLSTATE '22003' /*Numeric value out of range*/
|
||||
BEGIN
|
||||
SET @error='Error caught';
|
||||
RESIGNAL;
|
||||
END;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
SET a= f1();
|
||||
INSERT INTO t1 VALUES (20);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
--error ER_WARN_DATA_OUT_OF_RANGE
|
||||
CALL p1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
TRUNCATE TABLE t1;
|
||||
SET @error=NULL;
|
||||
START TRANSACTION;
|
||||
--error ER_WARN_DATA_OUT_OF_RANGE
|
||||
CALL p1;
|
||||
ROLLBACK;
|
||||
SELECT * FROM t1;
|
||||
SELECT @error;
|
||||
SET @error=NULL;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # EXECUTE IMMEDIATE 'SELECT' + CONTINUE HANDLER in the caller
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1 ORDER BY a';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE done INT DEFAULT 0;
|
||||
DECLARE c CURSOR FOR SELECT a FROM t1 ORDER BY a;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ SET done= done+1;
|
||||
OPEN c;
|
||||
FETCH c INTO v;
|
||||
SELECT v AS v_after_first_fetch;
|
||||
SET v= f_sel();
|
||||
SELECT v AS v_after_error, done;
|
||||
FETCH c INTO v;
|
||||
SELECT v AS v_after_second_fetch;
|
||||
SET v= f_sel();
|
||||
SET v= f_sel();
|
||||
SELECT v AS v_end, done AS done_end;
|
||||
CLOSE c;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Reuse the same PS at top level after the failure
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1 WHERE a>? ORDER BY a';
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_ps_select() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt USING 1;
|
||||
RETURN 2;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f1_ps_select();
|
||||
SELECT v AS v_p2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p2();
|
||||
EXECUTE stmt USING 1;
|
||||
CALL p2();
|
||||
EXECUTE stmt USING 2;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ps_select;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # SET STATEMENT ... FOR EXECUTE for 'SELECT..' inside the function
|
||||
--echo # Handling ER_SP_NO_RETSET
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
PREPARE stmt FROM 'SELECT a FROM t1 WHERE a>? ORDER BY a';
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_ss() RETURNS INT
|
||||
BEGIN
|
||||
SET STATEMENT max_sort_length=2048 FOR EXECUTE stmt USING CONCAT('1');
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f1_ss();
|
||||
SET v= f1_ss();
|
||||
SELECT v AS v_p2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p2();
|
||||
EXECUTE stmt USING 1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ss;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # SET STATEMENT ... FOR EXECUTE for 'INSERT..' inside the function
|
||||
--echo # Handling ER_DUP_ENTRY
|
||||
--echo #
|
||||
|
||||
SET @@max_sort_length=1024;
|
||||
CREATE TABLE t1(a INT PRIMARY KEY);
|
||||
INSERT INTO t1 VALUES (2048);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (?)';
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_ss(msl INT) RETURNS INT
|
||||
BEGIN
|
||||
SET STATEMENT max_sort_length=msl FOR EXECUTE stmt USING @@max_sort_length;
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE raised INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1062 /*ER_DUP_ENTRY*/
|
||||
BEGIN SET raised= raised+1; END;
|
||||
SET v= f1_ss(2049); -- This insert with no errors
|
||||
SET v= f1_ss(2048); -- This fires the handler on "duplicate key" error
|
||||
SET v= f1_ss(2048); -- This fires the handler on "duplicate key" error
|
||||
SELECT v, raised;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p2();
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
EXECUTE stmt USING 1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1_ss;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
SET @@max_sort_length=DEFAULT;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # EXECUTE for 'SELECT..' with nested functions
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_outer() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE x INT DEFAULT f1_sel();
|
||||
RETURN x+100;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
DECLARE CONTINUE HANDLER FOR 1415 /*ER_SP_NO_RETSET*/ BEGIN END;
|
||||
SET v= f2_outer();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p3();
|
||||
CALL p3();
|
||||
DROP PROCEDURE p3;
|
||||
DROP FUNCTION f2_outer;
|
||||
DROP FUNCTION f1_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
100
mysql-test/main/ps_in_func-cursor.result
Normal file
100
mysql-test/main/ps_in_func-cursor.result
Normal file
@@ -0,0 +1,100 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# Prepare a statement outside of the function, open a cursor inside
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c0 SYS_REFCURSOR;
|
||||
DECLARE v0 INT;
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO v0;
|
||||
CLOSE c0;
|
||||
RETURN v0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Prepare a statement inside the function, open a cursor
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c0 SYS_REFCURSOR;
|
||||
DECLARE v0 INT;
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO v0;
|
||||
CLOSE c0;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN v0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
CALL p1;
|
||||
n
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
116
mysql-test/main/ps_in_func-cursor.test
Normal file
116
mysql-test/main/ps_in_func-cursor.test
Normal file
@@ -0,0 +1,116 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # Prepare a statement outside of the function, open a cursor inside
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c0 SYS_REFCURSOR;
|
||||
DECLARE v0 INT;
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO v0;
|
||||
CLOSE c0;
|
||||
RETURN v0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
|
||||
# Assignment works
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
# DEFAULT also works
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Prepare a statement inside the function, open a cursor
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE c0 SYS_REFCURSOR;
|
||||
DECLARE v0 INT;
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO v0;
|
||||
CLOSE c0;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN v0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
|
||||
# Assignment works
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
# DEFAULT also works
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f1();
|
||||
SELECT n;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
169
mysql-test/main/ps_in_func-innodb.result
Normal file
169
mysql-test/main/ps_in_func-innodb.result
Normal file
@@ -0,0 +1,169 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
# p1 - f1(direct+immediate)
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
SHOW CREATE TABLE t1;
|
||||
Table Create Table
|
||||
t1 CREATE TABLE `t1` (
|
||||
`a` int(11) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
|
||||
CREATE FUNCTION f1_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (9);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f1_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
10
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
9
|
||||
10
|
||||
10
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
10
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
9
|
||||
10
|
||||
10
|
||||
DROP FUNCTION f1_direct_immediate;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP TABLE t1;
|
||||
# Check that functions with PS do not commit
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
SHOW CREATE TABLE t1;
|
||||
Table Create Table
|
||||
t1 CREATE TABLE `t1` (
|
||||
`a` int(11) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
|
||||
CREATE FUNCTION f2_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (9);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
START TRANSACTION;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
10
|
||||
ROLLBACK/*This should roll back all f2_direct_immediate() changes*/;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
9
|
||||
10
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP FUNCTION f2_direct_immediate;
|
||||
DROP TABLE t1;
|
||||
# Testing with two tables
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE TABLE t2 (a INT) ENGINE=InnoDB;
|
||||
CREATE FUNCTION f1() RETURNS INT BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= (SELECT MAX(a) FROM t1);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (?)' USING v;
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# MDEV-40226 Assertion `inited == NONE || table->open_by_handler' failed in int handler::ha_external_lock(THD *, int)
|
||||
#
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT * FROM t1 WHERE a = f();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT f() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A function called in the argument list of another function is not
|
||||
# in a safe PS context, so the statements of its body leave their
|
||||
# statement transaction open.
|
||||
# PS in the outer function is rejected:
|
||||
# it would run on top of that open statement transaction.
|
||||
#
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE FUNCTION g1() RETURNS INT
|
||||
BEGIN
|
||||
RETURN (SELECT MAX(a) FROM t1);
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1(g1());
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION g1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
162
mysql-test/main/ps_in_func-innodb.test
Normal file
162
mysql-test/main/ps_in_func-innodb.test
Normal file
@@ -0,0 +1,162 @@
|
||||
--source include/have_innodb.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo # p1 - f1(direct+immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
SHOW CREATE TABLE t1;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (9);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f1_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP FUNCTION f1_direct_immediate;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # Check that functions with PS do not commit
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
SHOW CREATE TABLE t1;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (9);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_direct_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
ROLLBACK/*This should roll back all f2_direct_immediate() changes*/;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP FUNCTION f2_direct_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # Testing with two tables
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE TABLE t2 (a INT) ENGINE=InnoDB;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= (SELECT MAX(a) FROM t1);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (?)' USING v;
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
SELECT * FROM t2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40226 Assertion `inited == NONE || table->open_by_handler' failed in int handler::ha_external_lock(THD *, int)
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a = f();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f() FROM t1;
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A function called in the argument list of another function is not
|
||||
--echo # in a safe PS context, so the statements of its body leave their
|
||||
--echo # statement transaction open.
|
||||
--echo # PS in the outer function is rejected:
|
||||
--echo # it would run on top of that open statement transaction.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION g1() RETURNS INT
|
||||
BEGIN
|
||||
RETURN (SELECT MAX(a) FROM t1);
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1(x INT) RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f1(g1());
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION g1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
45
mysql-test/main/ps_in_func-locks-01.inc
Normal file
45
mysql-test/main/ps_in_func-locks-01.inc
Normal file
@@ -0,0 +1,45 @@
|
||||
# Run $routine, which parks itself on the user lock 'l1' after having read
|
||||
# t1, and check that ALTER TABLE t1 can complete meanwhile, i.e. that the
|
||||
# metadata lock taken on t1 was released at the end of the statement which
|
||||
# read it, rather than kept until $routine returns.
|
||||
#
|
||||
# Parameters:
|
||||
# $routine procedure to call
|
||||
# $comment table comment ALTER TABLE sets, to wait for
|
||||
|
||||
connect (c2,localhost,root,,);
|
||||
connect (c3,localhost,root,,);
|
||||
|
||||
connection c2;
|
||||
--disable_ps2_protocol
|
||||
SELECT GET_LOCK('l1', 0);
|
||||
--enable_ps2_protocol
|
||||
|
||||
connection default;
|
||||
--send_eval CALL $routine()
|
||||
|
||||
connection c3;
|
||||
--let $wait_condition= SELECT COUNT(*) FROM information_schema.processlist WHERE state='User lock'
|
||||
--source include/wait_condition.inc
|
||||
--send_eval ALTER TABLE t1 COMMENT '$comment'
|
||||
|
||||
connection c2;
|
||||
--let $wait_condition= SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='test' AND table_name='t1' AND table_comment='$comment'
|
||||
--source include/wait_condition.inc
|
||||
--echo # ALTER TABLE t1 completed while $routine is still running
|
||||
--disable_ps2_protocol
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
--enable_ps2_protocol
|
||||
|
||||
connection default;
|
||||
--reap
|
||||
--disable_ps2_protocol
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
--enable_ps2_protocol
|
||||
connection c3;
|
||||
--reap
|
||||
|
||||
connection default;
|
||||
DELETE FROM t2;
|
||||
disconnect c2;
|
||||
disconnect c3;
|
||||
150
mysql-test/main/ps_in_func-locks.result
Normal file
150
mysql-test/main/ps_in_func-locks.result
Normal file
@@ -0,0 +1,150 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# Statements inside a function with PS end like statements inside a
|
||||
# stored procedure do: they commit their statement transaction and
|
||||
# they release their metadata locks. Releasing the locks is the part
|
||||
# which is easy to get half right - the statement transaction would
|
||||
# be committed while the locks stayed until the function returns,
|
||||
# blocking DDL on every table the function has touched so far.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
#
|
||||
# Control: a plain stored procedure
|
||||
#
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
DECLARE lk INT;
|
||||
INSERT INTO t2 VALUES (1);
|
||||
SET v= (SELECT COUNT(*) FROM t1);
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
END;
|
||||
$$
|
||||
connect c2,localhost,root,,;
|
||||
connect c3,localhost,root,,;
|
||||
connection c2;
|
||||
SELECT GET_LOCK('l1', 0);
|
||||
GET_LOCK('l1', 0)
|
||||
1
|
||||
connection default;
|
||||
CALL p1();
|
||||
connection c3;
|
||||
ALTER TABLE t1 COMMENT 'altered_by_control';
|
||||
connection c2;
|
||||
# ALTER TABLE t1 completed while p1 is still running
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection default;
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection c3;
|
||||
connection default;
|
||||
DELETE FROM t2;
|
||||
disconnect c2;
|
||||
disconnect c3;
|
||||
DROP PROCEDURE p1;
|
||||
#
|
||||
# Subject: a function with PS, called in an assignment right hand side
|
||||
#
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
DECLARE lk INT;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (1)';
|
||||
SET v= (SELECT COUNT(*) FROM t1);
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
END;
|
||||
$$
|
||||
connect c2,localhost,root,,;
|
||||
connect c3,localhost,root,,;
|
||||
connection c2;
|
||||
SELECT GET_LOCK('l1', 0);
|
||||
GET_LOCK('l1', 0)
|
||||
1
|
||||
connection default;
|
||||
CALL p1();
|
||||
connection c3;
|
||||
ALTER TABLE t1 COMMENT 'altered_by_subject';
|
||||
connection c2;
|
||||
# ALTER TABLE t1 completed while p1 is still running
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection default;
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection c3;
|
||||
connection default;
|
||||
DELETE FROM t2;
|
||||
disconnect c2;
|
||||
disconnect c3;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# The same for the table touched by the dynamic statement itself.
|
||||
# Such a statement does not go through
|
||||
# sp_lex_keeper::reset_lex_and_exec_core(), it ends in the finish:
|
||||
# block of mysql_execute_command(), so it needs the metadata locks
|
||||
# to be released there as well.
|
||||
#
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE lk INT;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (1)';
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
END;
|
||||
$$
|
||||
connect c2,localhost,root,,;
|
||||
connect c3,localhost,root,,;
|
||||
connection c2;
|
||||
SELECT GET_LOCK('l1', 0);
|
||||
GET_LOCK('l1', 0)
|
||||
1
|
||||
connection default;
|
||||
CALL p1();
|
||||
connection c3;
|
||||
ALTER TABLE t2 COMMENT 'altered_while_f1_runs';
|
||||
connection c2;
|
||||
# ALTER TABLE t2 completed while f1 is still parked on the user lock
|
||||
SELECT table_comment FROM information_schema.tables
|
||||
WHERE table_schema='test' AND table_name='t2';
|
||||
table_comment
|
||||
altered_while_f1_runs
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection default;
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
RELEASE_LOCK('l1')
|
||||
1
|
||||
connection c3;
|
||||
connection default;
|
||||
disconnect c2;
|
||||
disconnect c3;
|
||||
connection default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t2;
|
||||
# End of 13.1 tests
|
||||
134
mysql-test/main/ps_in_func-locks.test
Normal file
134
mysql-test/main/ps_in_func-locks.test
Normal file
@@ -0,0 +1,134 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # Statements inside a function with PS end like statements inside a
|
||||
--echo # stored procedure do: they commit their statement transaction and
|
||||
--echo # they release their metadata locks. Releasing the locks is the part
|
||||
--echo # which is easy to get half right - the statement transaction would
|
||||
--echo # be committed while the locks stayed until the function returns,
|
||||
--echo # blocking DDL on every table the function has touched so far.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
|
||||
--echo #
|
||||
--echo # Control: a plain stored procedure
|
||||
--echo #
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
DECLARE lk INT;
|
||||
INSERT INTO t2 VALUES (1);
|
||||
SET v= (SELECT COUNT(*) FROM t1);
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--let $routine= p1
|
||||
--let $comment= altered_by_control
|
||||
--source ps_in_func-locks-01.inc
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
--echo #
|
||||
--echo # Subject: a function with PS, called in an assignment right hand side
|
||||
--echo #
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
DECLARE lk INT;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (1)';
|
||||
SET v= (SELECT COUNT(*) FROM t1);
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--let $routine= p1
|
||||
--let $comment= altered_by_subject
|
||||
--source ps_in_func-locks-01.inc
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The same for the table touched by the dynamic statement itself.
|
||||
--echo # Such a statement does not go through
|
||||
--echo # sp_lex_keeper::reset_lex_and_exec_core(), it ends in the finish:
|
||||
--echo # block of mysql_execute_command(), so it needs the metadata locks
|
||||
--echo # to be released there as well.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE lk INT;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (1)';
|
||||
SET lk= GET_LOCK('l1', 120);
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
connect (c2,localhost,root,,);
|
||||
connect (c3,localhost,root,,);
|
||||
|
||||
connection c2;
|
||||
--disable_ps2_protocol
|
||||
SELECT GET_LOCK('l1', 0);
|
||||
--enable_ps2_protocol
|
||||
|
||||
connection default;
|
||||
--send CALL p1()
|
||||
|
||||
connection c3;
|
||||
--let $wait_condition= SELECT COUNT(*) FROM information_schema.processlist WHERE state='User lock'
|
||||
--source include/wait_condition.inc
|
||||
--send ALTER TABLE t2 COMMENT 'altered_while_f1_runs'
|
||||
|
||||
connection c2;
|
||||
--let $wait_condition= SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='test' AND table_name='t2' AND table_comment='altered_while_f1_runs'
|
||||
--source include/wait_condition.inc
|
||||
--echo # ALTER TABLE t2 completed while f1 is still parked on the user lock
|
||||
SELECT table_comment FROM information_schema.tables
|
||||
WHERE table_schema='test' AND table_name='t2';
|
||||
--disable_ps2_protocol
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
--enable_ps2_protocol
|
||||
|
||||
connection default;
|
||||
--reap
|
||||
--disable_ps2_protocol
|
||||
SELECT RELEASE_LOCK('l1');
|
||||
--enable_ps2_protocol
|
||||
connection c3;
|
||||
--reap
|
||||
|
||||
connection default;
|
||||
disconnect c2;
|
||||
disconnect c3;
|
||||
connection default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t2;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
110
mysql-test/main/ps_in_func-package.result
Normal file
110
mysql-test/main/ps_in_func-package.result
Normal file
@@ -0,0 +1,110 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# MDEV-40288 PACKAGE BODY initialization section that contains dynamic SQL fails with ER_NO_SUCH_TABLE for a table that exists
|
||||
#
|
||||
#
|
||||
# Calling PS in the package instantiating section is not allowed from a function
|
||||
#
|
||||
SET sql_mode=ORACLE;
|
||||
SET @px_cnt=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE PACKAGE pkg1 AS
|
||||
FUNCTION f RETURN INT;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
FUNCTION f RETURN INT AS
|
||||
BEGIN
|
||||
RETURN 42;
|
||||
END;
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM t1 INTO @px_cnt';
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1() AS
|
||||
v INT;
|
||||
BEGIN
|
||||
v := pkg1.f();
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SELECT @px_cnt;
|
||||
@px_cnt
|
||||
NULL
|
||||
#
|
||||
# Calling PS in the package instantiating section is OK from a procedure
|
||||
#
|
||||
SET sql_mode=ORACLE;
|
||||
SET @px_cnt=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
CREATE PACKAGE pkg1 AS
|
||||
PROCEDURE p1;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
PROCEDURE p1 AS BEGIN
|
||||
SET @a=10;
|
||||
END;
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM t1 INTO @px_cnt';
|
||||
END;$$
|
||||
CREATE PROCEDURE p1() AS
|
||||
v INT;
|
||||
BEGIN
|
||||
CALL pkg1.p1;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SELECT @px_cnt;
|
||||
@px_cnt
|
||||
1
|
||||
SET sql_mode=DEFAULT;
|
||||
#
|
||||
# A function with dynamic SQL instantiates a package body
|
||||
#
|
||||
SET sql_mode=ORACLE;
|
||||
CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a VARCHAR(32));
|
||||
CREATE PACKAGE pkg1 AS
|
||||
FUNCTION f1() RETURN INT;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
FUNCTION f1() RETURN INT AS
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES(''func pkg1.f1'')';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
BEGIN
|
||||
INSERT INTO t1 (a) VALUES ('package body pkg1');
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= pkg1.f1();
|
||||
SELECT a;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
a
|
||||
1
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
id a
|
||||
1 package body pkg1
|
||||
2 func pkg1.f1
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SET sql_mode=DEFAULT;
|
||||
# End of 13.1 tests
|
||||
121
mysql-test/main/ps_in_func-package.test
Normal file
121
mysql-test/main/ps_in_func-package.test
Normal file
@@ -0,0 +1,121 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40288 PACKAGE BODY initialization section that contains dynamic SQL fails with ER_NO_SUCH_TABLE for a table that exists
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # Calling PS in the package instantiating section is not allowed from a function
|
||||
--echo #
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
SET @px_cnt=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1 AS
|
||||
FUNCTION f RETURN INT;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
FUNCTION f RETURN INT AS
|
||||
BEGIN
|
||||
RETURN 42;
|
||||
END;
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM t1 INTO @px_cnt';
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1() AS
|
||||
v INT;
|
||||
BEGIN
|
||||
v := pkg1.f();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SELECT @px_cnt;
|
||||
|
||||
--echo #
|
||||
--echo # Calling PS in the package instantiating section is OK from a procedure
|
||||
--echo #
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
SET @px_cnt=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1 AS
|
||||
PROCEDURE p1;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
PROCEDURE p1 AS BEGIN
|
||||
SET @a=10;
|
||||
END;
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM t1 INTO @px_cnt';
|
||||
END;$$
|
||||
CREATE PROCEDURE p1() AS
|
||||
v INT;
|
||||
BEGIN
|
||||
CALL pkg1.p1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SELECT @px_cnt;
|
||||
SET sql_mode=DEFAULT;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A function with dynamic SQL instantiates a package body
|
||||
--echo #
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a VARCHAR(32));
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1 AS
|
||||
FUNCTION f1() RETURN INT;
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
FUNCTION f1() RETURN INT AS
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES(''func pkg1.f1'')';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
BEGIN
|
||||
INSERT INTO t1 (a) VALUES ('package body pkg1');
|
||||
END;
|
||||
$$
|
||||
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= pkg1.f1();
|
||||
SELECT a;
|
||||
END;
|
||||
$$
|
||||
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY id;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP PACKAGE pkg1;
|
||||
DROP TABLE t1;
|
||||
SET sql_mode=DEFAULT;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
54
mysql-test/main/ps_in_func-prelocking.result
Normal file
54
mysql-test/main/ps_in_func-prelocking.result
Normal file
@@ -0,0 +1,54 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 2;
|
||||
END;
|
||||
$$
|
||||
#
|
||||
# The updated table has a trigger
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
UPDATE t1 SET a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# The updated table is referenced by a foreign key with ON UPDATE CASCADE
|
||||
#
|
||||
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (b INT, KEY(b),
|
||||
FOREIGN KEY (b) REFERENCES t1(a) ON UPDATE CASCADE) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
INSERT INTO t2 VALUES (1);
|
||||
UPDATE t1 SET a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
SELECT * FROM t2;
|
||||
b
|
||||
1
|
||||
DROP TABLE t2, t1;
|
||||
#
|
||||
# The inserted table has a DEFAULT expression using a sequence
|
||||
#
|
||||
CREATE SEQUENCE s1;
|
||||
CREATE TABLE t1 (a INT DEFAULT (NEXTVAL(s1)), b INT);
|
||||
INSERT INTO t1 (b) VALUES (f1());
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
DROP TABLE t1;
|
||||
DROP SEQUENCE s1;
|
||||
DROP FUNCTION f1;
|
||||
# End of 13.1 tests
|
||||
72
mysql-test/main/ps_in_func-prelocking.test
Normal file
72
mysql-test/main/ps_in_func-prelocking.test
Normal file
@@ -0,0 +1,72 @@
|
||||
--source include/have_innodb.inc
|
||||
--source include/have_sequence.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
# A statement calling a function with dynamic SQL cannot be prelocked,
|
||||
# see Query_tables_list::requires_prelocking(). When the statement itself
|
||||
# needs prelocking - because its tables have triggers, or foreign keys with
|
||||
# a cascading action, or DEFAULT expressions using sequences - the two
|
||||
# requirements contradict each other. Make sure such a statement is rejected
|
||||
# with a diagnostic, rather than looping in open_tables() forever, throwing
|
||||
# the prelocking set away and collecting it again on every pass.
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The updated table has a trigger
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
UPDATE t1 SET a=f1();
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The updated table is referenced by a foreign key with ON UPDATE CASCADE
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (b INT, KEY(b),
|
||||
FOREIGN KEY (b) REFERENCES t1(a) ON UPDATE CASCADE) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
INSERT INTO t2 VALUES (1);
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
UPDATE t1 SET a=f1();
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
DROP TABLE t2, t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The inserted table has a DEFAULT expression using a sequence
|
||||
--echo #
|
||||
|
||||
CREATE SEQUENCE s1;
|
||||
CREATE TABLE t1 (a INT DEFAULT (NEXTVAL(s1)), b INT);
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 (b) VALUES (f1());
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
DROP SEQUENCE s1;
|
||||
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
142
mysql-test/main/ps_in_func-recursion.result
Normal file
142
mysql-test/main/ps_in_func-recursion.result
Normal file
@@ -0,0 +1,142 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# SUB_STMT_PS_SAFE_CONTEXT is established per call, it is not
|
||||
# inherited down the call stack, so every level has to be entered
|
||||
# from a safe context of its own.
|
||||
#
|
||||
SET @save_max_sp_recursion_depth= @@max_sp_recursion_depth;
|
||||
SET @@max_sp_recursion_depth= 5;
|
||||
CREATE TABLE t1 (a INT);
|
||||
# A function cannot call itself, with or without PS
|
||||
CREATE FUNCTION f1(n INT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
IF n > 0 THEN
|
||||
SET v= f1(n - 1);
|
||||
END IF;
|
||||
RETURN v + 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1(2);
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR HY000: Recursive stored functions and triggers are not allowed
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DELETE FROM t1;
|
||||
# Re-entering the function through its own dynamic statement
|
||||
# is rejected by the same check
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT f1() INTO @x';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR HY000: Recursive stored functions and triggers are not allowed
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
# The dynamic statement can also re-enter the function from an
|
||||
# anonymous block, where the call is in an assignment right hand
|
||||
# side and would therefore be a safe context of its own. The
|
||||
# recursion check rejects it just the same.
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC DECLARE v INT; SET v= f1(); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR HY000: Recursive stored functions and triggers are not allowed
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
# The same with the assignment written as a DEFAULT clause
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC DECLARE v INT DEFAULT f1(); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR HY000: Recursive stored functions and triggers are not allowed
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
# A recursive PROCEDURE can call a function with PS at every level:
|
||||
# each level enters the function from an assignment right hand side
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (3)';
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(n INT)
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
IF n > 0 THEN
|
||||
CALL p1(n - 1);
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
CALL p1(3);
|
||||
SELECT COUNT(*) FROM t1;
|
||||
COUNT(*)
|
||||
4
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DELETE FROM t1;
|
||||
# The safe context is not inherited: f1() is entered safely, but it
|
||||
# calls f2() in RETURN, which is not an assignment right hand side
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (5)';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
RETURN f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT COUNT(*) FROM t1;
|
||||
COUNT(*)
|
||||
0
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION f2;
|
||||
DROP TABLE t1;
|
||||
SET @@max_sp_recursion_depth= @save_max_sp_recursion_depth;
|
||||
# End of 13.1 tests
|
||||
160
mysql-test/main/ps_in_func-recursion.test
Normal file
160
mysql-test/main/ps_in_func-recursion.test
Normal file
@@ -0,0 +1,160 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # SUB_STMT_PS_SAFE_CONTEXT is established per call, it is not
|
||||
--echo # inherited down the call stack, so every level has to be entered
|
||||
--echo # from a safe context of its own.
|
||||
--echo #
|
||||
|
||||
SET @save_max_sp_recursion_depth= @@max_sp_recursion_depth;
|
||||
SET @@max_sp_recursion_depth= 5;
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
--echo # A function cannot call itself, with or without PS
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(n INT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)';
|
||||
IF n > 0 THEN
|
||||
SET v= f1(n - 1);
|
||||
END IF;
|
||||
RETURN v + 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1(2);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RECURSION
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DELETE FROM t1;
|
||||
|
||||
--echo # Re-entering the function through its own dynamic statement
|
||||
--echo # is rejected by the same check
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT f1() INTO @x';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RECURSION
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
--echo # The dynamic statement can also re-enter the function from an
|
||||
--echo # anonymous block, where the call is in an assignment right hand
|
||||
--echo # side and would therefore be a safe context of its own. The
|
||||
--echo # recursion check rejects it just the same.
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC DECLARE v INT; SET v= f1(); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RECURSION
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
--echo # The same with the assignment written as a DEFAULT clause
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'BEGIN NOT ATOMIC DECLARE v INT DEFAULT f1(); END';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RECURSION
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
--echo # A recursive PROCEDURE can call a function with PS at every level:
|
||||
--echo # each level enters the function from an assignment right hand side
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (3)';
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(n INT)
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
IF n > 0 THEN
|
||||
CALL p1(n - 1);
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1(3);
|
||||
SELECT COUNT(*) FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DELETE FROM t1;
|
||||
|
||||
--echo # The safe context is not inherited: f1() is entered safely, but it
|
||||
--echo # calls f2() in RETURN, which is not an assignment right hand side
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (5)';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
RETURN f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1;
|
||||
SELECT COUNT(*) FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION f2;
|
||||
|
||||
DROP TABLE t1;
|
||||
SET @@max_sp_recursion_depth= @save_max_sp_recursion_depth;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
1
mysql-test/main/ps_in_func-rollback-master.opt
Normal file
1
mysql-test/main/ps_in_func-rollback-master.opt
Normal file
@@ -0,0 +1 @@
|
||||
--loose-innodb_lock_wait_timeout=1 --loose-innodb_rollback_on_timeout
|
||||
65
mysql-test/main/ps_in_func-rollback.result
Normal file
65
mysql-test/main/ps_in_func-rollback.result
Normal file
@@ -0,0 +1,65 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# A statement inside a function with PS ends the way a statement inside
|
||||
# a stored procedure does, so it also honours a rollback requested by
|
||||
# the engine, at the end of that statement.
|
||||
#
|
||||
# The server is started with --innodb-rollback-on-timeout, so the lock
|
||||
# wait timeout in f1() below makes InnoDB request a rollback of the
|
||||
# whole transaction while the function is running. This is the only
|
||||
# way into the transaction_rollback_request branch of
|
||||
# sp_lex_keeper::reset_lex_and_exec_core().
|
||||
#
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, a INT) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1,1);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
SET v= (SELECT a FROM t1 WHERE id=1 FOR UPDATE);
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
connect c2,localhost,root,,;
|
||||
BEGIN;
|
||||
SELECT a FROM t1 WHERE id=1 FOR UPDATE;
|
||||
a
|
||||
1
|
||||
connection default;
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES (1);
|
||||
CALL p1;
|
||||
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
|
||||
# The caller's transaction was rolled back, its row is gone.
|
||||
SELECT @@in_transaction;
|
||||
@@in_transaction
|
||||
0
|
||||
SELECT COUNT(*) FROM t2;
|
||||
COUNT(*)
|
||||
0
|
||||
# The caller also released its metadata locks. If the rollback had
|
||||
# been done inside the function, restore_sub_statement_state() would
|
||||
# have brought OPTION_BEGIN back and the connection would hold the
|
||||
# locks of every statement it runs from now on.
|
||||
connect c3,localhost,root,,;
|
||||
SET SESSION lock_wait_timeout=5;
|
||||
ALTER TABLE t2 COMMENT 'altered';
|
||||
disconnect c3;
|
||||
connection default;
|
||||
connection c2;
|
||||
COMMIT;
|
||||
disconnect c2;
|
||||
connection default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
# End of 13.1 tests
|
||||
73
mysql-test/main/ps_in_func-rollback.test
Normal file
73
mysql-test/main/ps_in_func-rollback.test
Normal file
@@ -0,0 +1,73 @@
|
||||
--source include/have_innodb.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # A statement inside a function with PS ends the way a statement inside
|
||||
--echo # a stored procedure does, so it also honours a rollback requested by
|
||||
--echo # the engine, at the end of that statement.
|
||||
--echo #
|
||||
--echo # The server is started with --innodb-rollback-on-timeout, so the lock
|
||||
--echo # wait timeout in f1() below makes InnoDB request a rollback of the
|
||||
--echo # whole transaction while the function is running. This is the only
|
||||
--echo # way into the transaction_rollback_request branch of
|
||||
--echo # sp_lex_keeper::reset_lex_and_exec_core().
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY, a INT) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (a INT) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (1,1);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT 0;
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
SET v= (SELECT a FROM t1 WHERE id=1 FOR UPDATE);
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT;
|
||||
SET r= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
connect (c2,localhost,root,,);
|
||||
BEGIN;
|
||||
SELECT a FROM t1 WHERE id=1 FOR UPDATE;
|
||||
|
||||
connection default;
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES (1);
|
||||
--error ER_LOCK_WAIT_TIMEOUT
|
||||
CALL p1;
|
||||
|
||||
--echo # The caller's transaction was rolled back, its row is gone.
|
||||
SELECT @@in_transaction;
|
||||
SELECT COUNT(*) FROM t2;
|
||||
|
||||
--echo # The caller also released its metadata locks. If the rollback had
|
||||
--echo # been done inside the function, restore_sub_statement_state() would
|
||||
--echo # have brought OPTION_BEGIN back and the connection would hold the
|
||||
--echo # locks of every statement it runs from now on.
|
||||
connect (c3,localhost,root,,);
|
||||
SET SESSION lock_wait_timeout=5;
|
||||
ALTER TABLE t2 COMMENT 'altered';
|
||||
disconnect c3;
|
||||
connection default;
|
||||
|
||||
connection c2;
|
||||
COMMIT;
|
||||
disconnect c2;
|
||||
|
||||
connection default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
165
mysql-test/main/ps_in_func-row.result
Normal file
165
mysql-test/main/ps_in_func-row.result
Normal file
@@ -0,0 +1,165 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# ROW variables
|
||||
#
|
||||
CREATE TABLE t1 (a INT,b VARCHAR(10));
|
||||
INSERT INTO t1 VALUES (10,'b10');
|
||||
CREATE FUNCTION f1() RETURNS ROW(a INT, b VARCHAR(10))
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+11';
|
||||
RETURN ROW(100,'b100');
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10)) DEFAULT f1();
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10));
|
||||
SET r= f1();
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 21000: Operand should contain 1 column(s)
|
||||
CALL p1;
|
||||
r.a r.b
|
||||
100 b100
|
||||
CALL p2;
|
||||
r.a r.b
|
||||
100 b100
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# ROW variables initialized from a nested ROW value
|
||||
# This produces ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
|
||||
# The test just makes sure Item_row::set_in_ps_safe_context() works fine
|
||||
#
|
||||
CREATE TABLE t1 (a INT,b VARCHAR(10));
|
||||
INSERT INTO t1 VALUES (10,'b10');
|
||||
CREATE FUNCTION f1() RETURNS ROW(a INT, b VARCHAR(10))
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+11';
|
||||
RETURN ROW(100,'b100');
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10)) DEFAULT ROW(f1(),100);
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10));
|
||||
SET r= ROW(f1(),100);
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 21000: Operand should contain 1 column(s)
|
||||
CALL p1;
|
||||
ERROR HY000: Cannot cast 'row' as 'int' in assignment of `a`
|
||||
CALL p2;
|
||||
ERROR HY000: Cannot cast 'row' as 'int' in assignment of `a`
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# ROW fields
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+1';
|
||||
RETURN 100;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW (a INT, b VARCHAR(10)) DEFAULT ROW(f1(),'b100');
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW (a INT, b VARCHAR(10));
|
||||
SET r.a= f1();
|
||||
SET r.b= 'b100';
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CALL p1;
|
||||
r.a r.b
|
||||
100 b100
|
||||
CALL p2;
|
||||
r.a r.b
|
||||
100 b100
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
12
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A ROW value naming two functions, only the first one has PS.
|
||||
#
|
||||
# The statement is not pre-locked, so no MDL is taken on its routines
|
||||
# and their tables are not collected. The routines still have to be
|
||||
# put into the sp cache, all of them: Item_func_sp::fix_fields()
|
||||
# resolves them from the cache only, so a routine which is missing
|
||||
# there is reported as non existing.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
RETURN 20;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT, b INT);
|
||||
SET r= ROW(f1(), f2());
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT, b INT) DEFAULT ROW(f1(), f2());
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
r.a r.b
|
||||
10 20
|
||||
CALL p2;
|
||||
r.a r.b
|
||||
10 20
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION f2;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
169
mysql-test/main/ps_in_func-row.test
Normal file
169
mysql-test/main/ps_in_func-row.test
Normal file
@@ -0,0 +1,169 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # ROW variables
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT,b VARCHAR(10));
|
||||
INSERT INTO t1 VALUES (10,'b10');
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS ROW(a INT, b VARCHAR(10))
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+11';
|
||||
RETURN ROW(100,'b100');
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10)) DEFAULT f1();
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10));
|
||||
SET r= f1();
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_OPERAND_COLUMNS
|
||||
SELECT f1();
|
||||
CALL p1;
|
||||
CALL p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # ROW variables initialized from a nested ROW value
|
||||
--echo # This produces ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
|
||||
--echo # The test just makes sure Item_row::set_in_ps_safe_context() works fine
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT,b VARCHAR(10));
|
||||
INSERT INTO t1 VALUES (10,'b10');
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS ROW(a INT, b VARCHAR(10))
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+11';
|
||||
RETURN ROW(100,'b100');
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10)) DEFAULT ROW(f1(),100);
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT,b VARCHAR(10));
|
||||
SET r= ROW(f1(),100);
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_OPERAND_COLUMNS
|
||||
SELECT f1();
|
||||
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
|
||||
CALL p1;
|
||||
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
|
||||
CALL p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # ROW fields
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+1';
|
||||
RETURN 100;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW (a INT, b VARCHAR(10)) DEFAULT ROW(f1(),'b100');
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW (a INT, b VARCHAR(10));
|
||||
SET r.a= f1();
|
||||
SET r.b= 'b100';
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
CALL p1;
|
||||
CALL p2;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A ROW value naming two functions, only the first one has PS.
|
||||
--echo #
|
||||
--echo # The statement is not pre-locked, so no MDL is taken on its routines
|
||||
--echo # and their tables are not collected. The routines still have to be
|
||||
--echo # put into the sp cache, all of them: Item_func_sp::fix_fields()
|
||||
--echo # resolves them from the cache only, so a routine which is missing
|
||||
--echo # there is reported as non existing.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
RETURN 20;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT, b INT);
|
||||
SET r= ROW(f1(), f2());
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT, b INT) DEFAULT ROW(f1(), f2());
|
||||
SELECT r.a, r.b;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p2;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP FUNCTION f1;
|
||||
DROP FUNCTION f2;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
163
mysql-test/main/ps_in_func-sink.result
Normal file
163
mysql-test/main/ps_in_func-sink.result
Normal file
@@ -0,0 +1,163 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# MDEV-40315 Executing dynamic SQL (via PS) bypasses ERROR ER_SP_NO_RETSET
|
||||
#
|
||||
#
|
||||
# EXECUTE IMMEDIATE 'SELECT..' is not OK
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# PREPARE 'SELECT..' + EXECUTE is not OK
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT a FROM t1';
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# SELECT .. UNION is not OK
|
||||
#
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c1 UNION SELECT 2';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
#
|
||||
# DELETE RETURNING is not OK
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DELETE FROM t1 WHERE a>=2 RETURNING a';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# UPDATE RETURNING is not OK
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+10 RETURNING a';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# SELECT INTO @var is OK
|
||||
#
|
||||
SET @a=NULL;
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT MAX(a) INTO @a FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
SELECT @a;
|
||||
@a
|
||||
3
|
||||
SET @a=NULL;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# INSERT .. SELECT is OK
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
CREATE TABLE t2 LIKE t1;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 SELECT * FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
SELECT * FROM t2;
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
194
mysql-test/main/ps_in_func-sink.test
Normal file
194
mysql-test/main/ps_in_func-sink.test
Normal file
@@ -0,0 +1,194 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40315 Executing dynamic SQL (via PS) bypasses ERROR ER_SP_NO_RETSET
|
||||
--echo #
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # EXECUTE IMMEDIATE 'SELECT..' is not OK
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT a FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # PREPARE 'SELECT..' + EXECUTE is not OK
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT a FROM t1';
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # SELECT .. UNION is not OK
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 AS c1 UNION SELECT 2';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # DELETE RETURNING is not OK
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DELETE FROM t1 WHERE a>=2 RETURNING a';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # UPDATE RETURNING is not OK
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=a+10 RETURNING a';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # SELECT INTO @var is OK
|
||||
--echo #
|
||||
|
||||
SET @a=NULL;
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT MAX(a) INTO @a FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p();
|
||||
SELECT @a;
|
||||
SET @a=NULL;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # INSERT .. SELECT is OK
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
CREATE TABLE t2 LIKE t1;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_sel() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 SELECT * FROM t1';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_sel();
|
||||
SELECT * FROM t2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_sel;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
12
mysql-test/main/ps_in_func-stack2-fp.combinations
Normal file
12
mysql-test/main/ps_in_func-stack2-fp.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
45
mysql-test/main/ps_in_func-stack2-fp.result
Normal file
45
mysql-test/main/ps_in_func-stack2-fp.result
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT f1() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
SELECT f1() FROM t1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1, t2;
|
||||
# End of 13.1 tests
|
||||
55
mysql-test/main/ps_in_func-stack2-fp.test
Normal file
55
mysql-test/main/ps_in_func-stack2-fp.test
Normal file
@@ -0,0 +1,55 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (2)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() FROM t1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t2 VALUES (2)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
CALL p2();
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() FROM t1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM t1 WHERE a=f1();
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
12
mysql-test/main/ps_in_func-stack2-pf.combinations
Normal file
12
mysql-test/main/ps_in_func-stack2-pf.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
166
mysql-test/main/ps_in_func-stack2-pf.result
Normal file
166
mysql-test/main/ps_in_func-stack2-pf.result
Normal file
@@ -0,0 +1,166 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
# p1, f2(immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f2_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f2_immediate();
|
||||
END;
|
||||
$$
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
11
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
11
|
||||
11
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
11
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
11
|
||||
11
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2_immediate;
|
||||
DROP TABLE t1;
|
||||
# p1, f2(direct+immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f2_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (10);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f2_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
11
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
11
|
||||
11
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
11
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
11
|
||||
11
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2_direct_and_immediate;
|
||||
DROP TABLE t1;
|
||||
# p1, f2a(direct+immediate) + f2b(direct+immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f2a_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (10);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2b_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (20);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (21)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2a_direct_and_immediate();
|
||||
DECLARE b INT DEFAULT f2b_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a,b INT;
|
||||
SET a= f2a_direct_and_immediate();
|
||||
SET b= f2b_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
11
|
||||
20
|
||||
21
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
11
|
||||
11
|
||||
20
|
||||
20
|
||||
21
|
||||
21
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
11
|
||||
20
|
||||
21
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
11
|
||||
11
|
||||
20
|
||||
20
|
||||
21
|
||||
21
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2a_direct_and_immediate;
|
||||
DROP FUNCTION f2b_direct_and_immediate;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
140
mysql-test/main/ps_in_func-stack2-pf.test
Normal file
140
mysql-test/main/ps_in_func-stack2-pf.test
Normal file
@@ -0,0 +1,140 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
|
||||
--echo # p1, f2(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f2_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, f2(direct+immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (10);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a= f2_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2_direct_and_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, f2a(direct+immediate) + f2b(direct+immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2a_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (10);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2b_direct_and_immediate() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (20);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (21)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2a_direct_and_immediate();
|
||||
DECLARE b INT DEFAULT f2b_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE a,b INT;
|
||||
SET a= f2a_direct_and_immediate();
|
||||
SET b= f2b_direct_and_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP FUNCTION f2a_direct_and_immediate;
|
||||
DROP FUNCTION f2b_direct_and_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
149
mysql-test/main/ps_in_func-stack3-pff-01.inc
Normal file
149
mysql-test/main/ps_in_func-stack3-pff-01.inc
Normal file
@@ -0,0 +1,149 @@
|
||||
--let $part1= $(regexp_substr($param,'^[_a-z]*(?=[|])'))
|
||||
--let $part2= $(regexp_substr($param,'(?<=[|])[_a-z]*(?=[|])'))
|
||||
--let $part3= $(regexp_substr($param,'(?<=[|])[_a-z]*\$'))
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # [$part1][$part2][$part3]
|
||||
--echo #
|
||||
--echo
|
||||
|
||||
CREATE TABLE t1 (a VARCHAR(30));
|
||||
|
||||
############################ f3
|
||||
|
||||
DELIMITER $$;
|
||||
if ($part3 == 'none')
|
||||
{
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'immediate')
|
||||
{
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f3-immediate'')';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'direct')
|
||||
{
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES ('f3-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'direct_immediate')
|
||||
{
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f3-immediate'')';
|
||||
INSERT INTO t1 VALUES ('f3-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
|
||||
############################ f2
|
||||
|
||||
DELIMITER $$;
|
||||
|
||||
if ($part2 == 'none')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'immediate')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f2-immediate'')';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'direct')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'direct_immediate')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f2-immediate'')';
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
|
||||
############################ f1
|
||||
|
||||
DELIMITER $$;
|
||||
|
||||
if ($part1 == 'none')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'immediate')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p1-immediate'')';
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'direct')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
INSERT INTO t1 VALUES ('p1-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'direct_immediate')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p1-immediate'')';
|
||||
INSERT INTO t1 VALUES ('p1-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
##########################
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a DESC;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f3;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $param=
|
||||
12
mysql-test/main/ps_in_func-stack3-pff.combinations
Normal file
12
mysql-test/main/ps_in_func-stack3-pff.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
2812
mysql-test/main/ps_in_func-stack3-pff.result
Normal file
2812
mysql-test/main/ps_in_func-stack3-pff.result
Normal file
File diff suppressed because it is too large
Load Diff
544
mysql-test/main/ps_in_func-stack3-pff.test
Normal file
544
mysql-test/main/ps_in_func-stack3-pff.test
Normal file
@@ -0,0 +1,544 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
# Call stack: PROCEDURE - FUNCTION - FUNCTION
|
||||
|
||||
--echo # P(none,default/set)-F(none,set)-F(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f_immediate();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2_set();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_set();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_select()
|
||||
BEGIN
|
||||
SELECT f2_set();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p3_select();
|
||||
CALL p3_default;
|
||||
CALL p3_default;
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p3_set;
|
||||
CALL p3_set;
|
||||
SELECT * FROM t1;
|
||||
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_set;
|
||||
DROP FUNCTION f_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # P(none,default/set)-F(none,default)-F(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_default() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f_immediate();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2_default();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_default();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p3_default;
|
||||
CALL p3_default;
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p3_set;
|
||||
CALL p3_set;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_default;
|
||||
DROP FUNCTION f_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # P(none,default/set)-F(none,default)-F(direct+immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
INSERT INTO t1 VALUES (9);
|
||||
SET nrows= ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN nrows + ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_default() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1_direct_immediate();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_select()
|
||||
BEGIN
|
||||
SELECT f2_default();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2_default();
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_default();
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p3_select();
|
||||
CALL p3_default();
|
||||
CALL p3_default();
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p3_set();
|
||||
CALL p3_set();
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f1_direct_immediate;
|
||||
DROP FUNCTION f2_default;
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # P(direct+immediate,default/set)-F(direct+immediate,set)-F(direct+immediate)
|
||||
|
||||
CREATE TABLE t1 (id SERIAL, a VARCHAR(100));
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_direct_immediate() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
INSERT INTO t1 (a) VALUES ('f1_direct_immediate: direct');
|
||||
SET nrows= ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES (?)' USING 'f1_direct_immediate: immediate';
|
||||
RETURN nrows + ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows1, nrows2, nrows3 INT;
|
||||
INSERT INTO t1 (a) VALUES ('f2_set: direct before f1_direct_immediate');
|
||||
SET nrows1= ROW_COUNT();
|
||||
SET nrows2= f1_direct_immediate();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES (?)'
|
||||
USING 'f2_set: immediate after f1_direct_immediate';
|
||||
SET nrows3= ROW_COUNT();
|
||||
RETURN nrows1 + nrows2 + nrows3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default()
|
||||
BEGIN
|
||||
DECLARE nrows1 INT DEFAULT f2_set();
|
||||
DECLARE nrows2, nrows3 INT;
|
||||
INSERT INTO t1 (a) VALUES('p3_default: direct after default f2_set');
|
||||
SET nrows2= ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES (?)'
|
||||
USING 'p3_default: immediate after default p2_set';
|
||||
SET nrows3= ROW_COUNT();
|
||||
SELECT nrows1, nrows2, nrows3;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows1, nrows2, nrows3 INT;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (a) VALUES (?)'
|
||||
USING 'p3_set: immediate before f2_set';
|
||||
SET nrows1= ROW_COUNT();
|
||||
SET nrows2= f2_set();
|
||||
INSERT INTO t1 (a) VALUES('p3_set: direct after p2_set');
|
||||
SET nrows3= ROW_COUNT();
|
||||
SELECT nrows1, nrows2, nrows3;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p3_default();
|
||||
CALL p3_default();
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p3_set();
|
||||
CALL p3_set();
|
||||
DROP FUNCTION f1_direct_immediate;
|
||||
DROP FUNCTION f2_set;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # P(none,default/set)-F(none,default)-F(sys_refcursor)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (9), (10);
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_sys_refcursor() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
va INT;
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO va;
|
||||
CLOSE c0;
|
||||
RETURN va;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_default() RETURN INT AS
|
||||
va INT := f1_sys_refcursor();
|
||||
BEGIN
|
||||
RETURN va;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_select() AS
|
||||
BEGIN
|
||||
SELECT f2_default();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default() AS
|
||||
va INT DEFAULT f2_default();
|
||||
BEGIN
|
||||
SELECT va;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set() AS
|
||||
va INT;
|
||||
BEGIN
|
||||
va:= f2_default();
|
||||
SELECT va;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p3_select();
|
||||
CALL p3_default();
|
||||
CALL p3_default();
|
||||
CALL p3_set();
|
||||
CALL p3_set();
|
||||
DROP FUNCTION f1_sys_refcursor;
|
||||
DROP FUNCTION f2_default;
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP TABLE t1;
|
||||
SET sql_mode=DEFAULT;
|
||||
|
||||
|
||||
--echo # P(none,default/set)-F(none,default)-F(direct+sys_refcursor)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (9), (10);
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_sys_refcursor() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
va INT;
|
||||
BEGIN
|
||||
va:= (SELECT MAX(a) FROM t1);
|
||||
INSERT INTO t1 VALUES (va+1);
|
||||
PREPARE stmt FROM 'SELECT MAX(a) FROM t1';
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
FETCH c0 INTO va;
|
||||
CLOSE c0;
|
||||
RETURN va;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_default() RETURN INT AS
|
||||
va INT := f1_sys_refcursor();
|
||||
BEGIN
|
||||
RETURN va;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_select() AS
|
||||
BEGIN
|
||||
SELECT f2_default();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_default() AS
|
||||
va INT DEFAULT f2_default();
|
||||
BEGIN
|
||||
SELECT va;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set() AS
|
||||
va INT;
|
||||
BEGIN
|
||||
va:= f2_default();
|
||||
SELECT va;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p3_select();
|
||||
CALL p3_default();
|
||||
CALL p3_default();
|
||||
CALL p3_set();
|
||||
CALL p3_set();
|
||||
DROP FUNCTION f1_sys_refcursor;
|
||||
DROP FUNCTION f2_default;
|
||||
DROP PROCEDURE p3_select;
|
||||
DROP PROCEDURE p3_default;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP TABLE t1;
|
||||
SET sql_mode=DEFAULT;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40240 Dynamic SQL must be rejected with ERROR 1336 when using non-safe context
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_leaf() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (777)';
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE FUNCTION f_mid() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE x INT;
|
||||
SET x = 100 + f_leaf(); -- Not safe call
|
||||
RETURN x;
|
||||
END
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_mid();
|
||||
END
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p();
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_mid;
|
||||
DROP FUNCTION f_leaf;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_leaf() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (777)';
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE FUNCTION f_mid() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE x INT;
|
||||
INSERT INTO t1 VALUES (f_leaf()); -- Not safe call
|
||||
RETURN x;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_mid();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_mid;
|
||||
DROP FUNCTION f_leaf;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_leaf() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (777,777)'; -- Bad statement
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_mid() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE x INT;
|
||||
INSERT INTO t1 VALUES (f_leaf());
|
||||
RETURN x;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_mid();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_mid;
|
||||
DROP FUNCTION f_leaf;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40913 Function with EXECUTE IMMEDIATE crashes on INSERT with an InnoDB table
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a VARCHAR(30));
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f3-immediate'')';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a DESC;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f3;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40914 Function with direct INSERT into Aria table crashes
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a VARCHAR(30));
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f3() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES ('f3-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v3 VARCHAR(10) DEFAULT f3();
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f3;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # Various combinations
|
||||
--echo #
|
||||
|
||||
let $i1= 0;
|
||||
while ($i1 < 4)
|
||||
{
|
||||
let $part1=;
|
||||
if ($i1 == 0) { let $part1=none; }
|
||||
if ($i1 == 1) { let $part1=direct; }
|
||||
if ($i1 == 2) { let $part1=immediate; }
|
||||
if ($i1 == 3) { let $part1=direct_immediate; }
|
||||
inc $i1;
|
||||
|
||||
echo #;
|
||||
echo # $part1|*|*;
|
||||
echo #;
|
||||
|
||||
let $i2= 0;
|
||||
while ($i2 < 4)
|
||||
{
|
||||
let $part2=;
|
||||
if ($i2 == 0) { let $part2=none; }
|
||||
if ($i2 == 1) { let $part2=direct; }
|
||||
if ($i2 == 2) { let $part2=immediate; }
|
||||
if ($i2 == 3) { let $part2=direct_immediate; }
|
||||
inc $i2;
|
||||
|
||||
let $param='$part1|$part2|none';
|
||||
source ps_in_func-stack3-pff-01.inc;
|
||||
|
||||
let $param='$part1|$part2|direct';
|
||||
source ps_in_func-stack3-pff-01.inc;
|
||||
|
||||
let $param='$part1|$part2|immediate';
|
||||
source ps_in_func-stack3-pff-01.inc;
|
||||
|
||||
let $param='$part1|$part2|direct_immediate';
|
||||
source ps_in_func-stack3-pff-01.inc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
147
mysql-test/main/ps_in_func-stack3-pfp-01.inc
Normal file
147
mysql-test/main/ps_in_func-stack3-pfp-01.inc
Normal file
@@ -0,0 +1,147 @@
|
||||
--let $part1= $(regexp_substr($param,'^[_a-z]*(?=[|])'))
|
||||
--let $part2= $(regexp_substr($param,'(?<=[|])[_a-z]*(?=[|])'))
|
||||
--let $part3= $(regexp_substr($param,'(?<=[|])[_a-z]*\$'))
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # [$part1][$part2][$part3]
|
||||
--echo #
|
||||
--echo
|
||||
|
||||
|
||||
CREATE TABLE t1 (a VARCHAR(30));
|
||||
|
||||
############################ f3
|
||||
|
||||
DELIMITER $$;
|
||||
if ($part3 == 'none')
|
||||
{
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
BEGIN END;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'immediate')
|
||||
{
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p3-immediate'')';
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'direct')
|
||||
{
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES ('f3-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part3 == 'direct_immediate')
|
||||
{
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p3-immediate'')';
|
||||
INSERT INTO t1 VALUES ('p3-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
|
||||
############################ f2
|
||||
|
||||
DELIMITER $$;
|
||||
|
||||
if ($part2 == 'none')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'immediate')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f2-immediate'')';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'direct')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part2 == 'direct_immediate')
|
||||
{
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''f2-immediate'')';
|
||||
INSERT INTO t1 VALUES ('f2-direct');
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
|
||||
############################ f1
|
||||
|
||||
DELIMITER $$;
|
||||
|
||||
if ($part1 == 'none')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'immediate')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p1-immediate'')';
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'direct')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
INSERT INTO t1 VALUES ('p1-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
if ($part1 == 'direct_immediate')
|
||||
{
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v2 VARCHAR(10) DEFAULT f2();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''p1-immediate'')';
|
||||
INSERT INTO t1 VALUES ('p1-direct');
|
||||
END;
|
||||
$$
|
||||
}
|
||||
DELIMITER ;$$
|
||||
##########################
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a DESC;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $param=
|
||||
12
mysql-test/main/ps_in_func-stack3-pfp.combinations
Normal file
12
mysql-test/main/ps_in_func-stack3-pfp.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
2526
mysql-test/main/ps_in_func-stack3-pfp.result
Normal file
2526
mysql-test/main/ps_in_func-stack3-pfp.result
Normal file
File diff suppressed because it is too large
Load Diff
304
mysql-test/main/ps_in_func-stack3-pfp.test
Normal file
304
mysql-test/main/ps_in_func-stack3-pfp.test
Normal file
@@ -0,0 +1,304 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo # Testing various stack combinations
|
||||
|
||||
--echo # p1, f2, p3_direct_immediate
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3_direct_immediate()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (9)';
|
||||
INSERT INTO t1 VALUES (10);
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3_direct_immediate();
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
# Top-level procedures
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_select()
|
||||
BEGIN
|
||||
SELECT f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP PROCEDURE p1_select;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3_direct_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, f2, p3_immediate_direct
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3_direct_immediate()
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (9);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3_direct_immediate();
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
# Top-level procedures
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_select()
|
||||
BEGIN
|
||||
SELECT f2();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
SELECT * FROM t1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP PROCEDURE p1_select;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3_direct_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, f2(direct+immediate+direct), p3(direct+immediate+direct)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3_direct_immediate_direct(OUT nrows INT)
|
||||
BEGIN
|
||||
SET nrows= 0;
|
||||
INSERT INTO t1 VALUES(11);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (21)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(31);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_direct_immediate_direct() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT 0;
|
||||
DECLARE nrows1 INT DEFAULT 0;
|
||||
INSERT INTO t1 VALUES(10);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (20)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(30);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
CALL p3_direct_immediate_direct(nrows1);
|
||||
RETURN nrows+nrows1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_select()
|
||||
BEGIN
|
||||
SELECT f2_direct_immediate_direct();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f2_direct_immediate_direct();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_direct_immediate_direct();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
SELECT * FROM t1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1_select();
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_default;
|
||||
SELECT * FROM t1;
|
||||
TRUNCATE TABLE t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
CALL p1_set;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p3_direct_immediate_direct;
|
||||
DROP FUNCTION f2_direct_immediate_direct;
|
||||
DROP PROCEDURE p1_select;
|
||||
DROP PROCEDURE p1_default;
|
||||
DROP PROCEDURE p1_set;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40900 Procedure with dynamic SQL crashes on an InnoDB table
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (9)';
|
||||
INSERT INTO t1 VALUES (10);
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40901 Unexpected Table does not exist error in procedure with PS
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (9)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3();
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE n INT;
|
||||
SET n= f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Various combinations
|
||||
--echo #
|
||||
|
||||
|
||||
let $i1= 0;
|
||||
while ($i1 < 4)
|
||||
{
|
||||
let $part1=;
|
||||
if ($i1 == 0) { let $part1=none; }
|
||||
if ($i1 == 1) { let $part1=direct; }
|
||||
if ($i1 == 2) { let $part1=immediate; }
|
||||
if ($i1 == 3) { let $part1=direct_immediate; }
|
||||
inc $i1;
|
||||
|
||||
echo #;
|
||||
echo # $part1|*|*;
|
||||
echo #;
|
||||
|
||||
let $i2= 0;
|
||||
while ($i2 < 4)
|
||||
{
|
||||
let $part2=;
|
||||
if ($i2 == 0) { let $part2=none; }
|
||||
if ($i2 == 1) { let $part2=direct; }
|
||||
if ($i2 == 2) { let $part2=immediate; }
|
||||
if ($i2 == 3) { let $part2=direct_immediate; }
|
||||
inc $i2;
|
||||
|
||||
let $param='$part1|$part2|none';
|
||||
source ps_in_func-stack3-pfp-01.inc;
|
||||
|
||||
let $param='$part1|$part2|direct';
|
||||
source ps_in_func-stack3-pfp-01.inc;
|
||||
|
||||
let $param='$part1|$part2|immediate';
|
||||
source ps_in_func-stack3-pfp-01.inc;
|
||||
|
||||
let $param='$part1|$part2|direct_immediate';
|
||||
source ps_in_func-stack3-pfp-01.inc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
12
mysql-test/main/ps_in_func-stack3-ppf.combinations
Normal file
12
mysql-test/main/ps_in_func-stack3-ppf.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
164
mysql-test/main/ps_in_func-stack3-ppf.result
Normal file
164
mysql-test/main/ps_in_func-stack3-ppf.result
Normal file
@@ -0,0 +1,164 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
# p1, p2(default), f3(immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f3_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f3_immediate();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
nrows
|
||||
1
|
||||
CALL p1;
|
||||
nrows
|
||||
1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP FUNCTION f3_immediate;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
# p1, p2(default), f3a(immediate) + f3b(immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f3a_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f3b_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows_a INT DEFAULT f3a_immediate();
|
||||
DECLARE nrows_b INT DEFAULT f3b_immediate();
|
||||
SELECT nrows_a, nrows_b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
nrows_a nrows_b
|
||||
1 1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
11
|
||||
CALL p1;
|
||||
nrows_a nrows_b
|
||||
1 1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
11
|
||||
11
|
||||
DROP FUNCTION f3a_immediate;
|
||||
DROP FUNCTION f3b_immediate;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
# p1, p2(set), f3(immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f3_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a=f3_immediate();
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_set();
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
a
|
||||
10
|
||||
CALL p1();
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP FUNCTION f3_immediate;
|
||||
DROP PROCEDURE p2_set;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
# p1, p2(default), f3(direct_immediate+direct)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1_direct_immediate_direct() RETURNS INT
|
||||
BEGIN
|
||||
-- A mixture of direct and prepared execution
|
||||
DECLARE nrows INT DEFAULT 0;
|
||||
INSERT INTO t1 VALUES(10);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (20)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(30);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1_direct_immediate_direct();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
nrows
|
||||
3
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
20
|
||||
30
|
||||
CALL p1;
|
||||
nrows
|
||||
3
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
20
|
||||
20
|
||||
30
|
||||
30
|
||||
DROP FUNCTION f1_direct_immediate_direct;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
152
mysql-test/main/ps_in_func-stack3-ppf.test
Normal file
152
mysql-test/main/ps_in_func-stack3-ppf.test
Normal file
@@ -0,0 +1,152 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
# Call stack: PROCEDURE - PROCEDURE - FUNCTION
|
||||
|
||||
--echo # p1, p2(default), f3(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f3_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f3_immediate();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP FUNCTION f3_immediate;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, p2(default), f3a(immediate) + f3b(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f3a_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f3b_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows_a INT DEFAULT f3a_immediate();
|
||||
DECLARE nrows_b INT DEFAULT f3b_immediate();
|
||||
SELECT nrows_a, nrows_b;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP FUNCTION f3a_immediate;
|
||||
DROP FUNCTION f3b_immediate;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, p2(set), f3(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f3_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_set()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
SET a=f3_immediate();
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_set();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
DROP FUNCTION f3_immediate;
|
||||
DROP PROCEDURE p2_set;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, p2(default), f3(direct_immediate+direct)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_direct_immediate_direct() RETURNS INT
|
||||
BEGIN
|
||||
-- A mixture of direct and prepared execution
|
||||
DECLARE nrows INT DEFAULT 0;
|
||||
INSERT INTO t1 VALUES(10);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (20)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(30);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2_default()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1_direct_immediate_direct();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2_default;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP FUNCTION f1_direct_immediate_direct;
|
||||
DROP PROCEDURE p2_default;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
12
mysql-test/main/ps_in_func-stack4-pppf.combinations
Normal file
12
mysql-test/main/ps_in_func-stack4-pppf.combinations
Normal file
@@ -0,0 +1,12 @@
|
||||
[myisam]
|
||||
default-storage-engine=myisam
|
||||
|
||||
[memory]
|
||||
default-storage-engine=memory
|
||||
|
||||
[innodb]
|
||||
innodb
|
||||
default-storage-engine=innodb
|
||||
|
||||
[aria]
|
||||
default-storage-engine=aria
|
||||
93
mysql-test/main/ps_in_func-stack4-pppf.result
Normal file
93
mysql-test/main/ps_in_func-stack4-pppf.result
Normal file
@@ -0,0 +1,93 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
# p1, p2, p3, f1(immediate)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
nrows
|
||||
1
|
||||
CALL p1;
|
||||
nrows
|
||||
1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP PROCEDURE p3;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# p1, p2, p3, f1(immediate+direct)
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
-- A mixture of direct and prepared execution
|
||||
DECLARE nrows INT DEFAULT 0;
|
||||
INSERT INTO t1 VALUES(10);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (20)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(30);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
nrows
|
||||
3
|
||||
CALL p1;
|
||||
nrows
|
||||
3
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
10
|
||||
10
|
||||
20
|
||||
20
|
||||
30
|
||||
30
|
||||
DROP PROCEDURE p3;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
89
mysql-test/main/ps_in_func-stack4-pppf.test
Normal file
89
mysql-test/main/ps_in_func-stack4-pppf.test
Normal file
@@ -0,0 +1,89 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo # p1, p2, p3, f1(immediate)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP PROCEDURE p3;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # p1, p2, p3, f1(immediate+direct)
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
-- A mixture of direct and prepared execution
|
||||
DECLARE nrows INT DEFAULT 0;
|
||||
INSERT INTO t1 VALUES(10);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (20)';
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
INSERT INTO t1 VALUES(30);
|
||||
SET nrows= nrows + ROW_COUNT();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
SELECT nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
|
||||
DROP PROCEDURE p3;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
492
mysql-test/main/ps_in_func-trigger.result
Normal file
492
mysql-test/main/ps_in_func-trigger.result
Normal file
@@ -0,0 +1,492 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# PS commands inside a trigger are rejected at CREATE time
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
EXECUTE IMMEDIATE 'not important';
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
PREPARE stmt FROM 'SET a=1';
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
DEALLOCATE PREPARE stmt;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A stored procedure with PS called from a trigger
|
||||
# The CALL is rejected with "Dynamic SQL is not allowed",
|
||||
# rather than "Not allowed to return a result set from a trigger",
|
||||
# in all contexts, e.g. when:
|
||||
# - a procedure with PS is called from a trigger directly
|
||||
# - a procedure called from a trigger calls another procedure with PS
|
||||
# - a function called from a trigger calls a procedure with PS
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
DROP TRIGGER tr1;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
#
|
||||
# A procedure with PREPARE/DEALLOCATE, i.e. without a possible result set
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SET @a=1';
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
SET @a=NULL;
|
||||
#
|
||||
# A procedure with PS and a result set: PS is reported first
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
SELECT 1;
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TRIGGER tr1;
|
||||
DROP PROCEDURE p3;
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
#
|
||||
# A procedure with PS called through a function
|
||||
#
|
||||
SET @a=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A stored function with PS used from a trigger
|
||||
# Not allowed even in a safe context (e.g. assignment in right hand)
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (12);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TRIGGER tr1;
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (12);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TRIGGER tr1;
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t2 SET a=f1();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (12);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t2 SET a=f1();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (12);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t2;
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# MDEV-40224 SIGSEGV in THD::is_error | Item::save_in_field | Item_trigger_field::set_value
|
||||
#
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW SET NEW.b = f();
|
||||
$$
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP TABLE t1;
|
||||
DROP FUNCTION f;
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE rc INT;
|
||||
SET rc= f1();
|
||||
RETURN rc;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW SET NEW.b = f2()
|
||||
$$
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# MDEV-40285 Assertion DBUG_ASSERT(thd->lock == 0) failed in lock_tables()
|
||||
#
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t1 SET b=f();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE res INT default f1();
|
||||
RETURN res;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t1 SET b=f2();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# MDEV-41013 Assertion `inited == NONE || table->open_by_handler' failed in int handler::ha_external_lock(THD *, int)
|
||||
#
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
IF FALSE THEN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
END IF;
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = f1();
|
||||
UPDATE t1 SET a = a + 1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @x;
|
||||
@x
|
||||
NULL
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
1 2
|
||||
DROP TABLE t1;
|
||||
DROP FUNCTION f1;
|
||||
#
|
||||
# MDEV-41015 SIGSEGV in THD::is_error | Item::save_in_field | Item_trigger_field::set_value on INSERT w/ TRIGGER
|
||||
#
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.a = 999;
|
||||
SET NEW.b = f1();
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A trigger with no PS can be invoked from
|
||||
# a procedure containing PS
|
||||
#
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = NEW.b+100;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET b=3';
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
1 103
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A trigger with no PS can be invoked from
|
||||
# a function containing PS
|
||||
#
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = NEW.b+100;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET b=3';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
SELECT a;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
a
|
||||
1
|
||||
a b
|
||||
1 103
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Testing multi-statement triggers
|
||||
#
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (10,20);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1'; RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.b = f1(); -- fails
|
||||
/* make sure the rest does not get executed, @a remains 100 */
|
||||
SET NEW.a = 1;
|
||||
SET @a=NEW.a;
|
||||
END;
|
||||
$$
|
||||
SET @a= 100;
|
||||
INSERT INTO t1 VALUES (11,21);
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @a;
|
||||
@a
|
||||
100
|
||||
SET @a= NULL;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A procedure with PS + a direct statement invoking a trigger with no PS
|
||||
# It works without errors: the PS in the outer procedure is not a problem
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
SET @a=@a+100;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
INSERT INTO t1 VALUES (1);
|
||||
END;
|
||||
$$
|
||||
CALL p2;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
SELECT @a;
|
||||
@a
|
||||
101
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a=NULL;
|
||||
#
|
||||
# A procedure with PS whose direct statement invokes a trigger
|
||||
# calling this same procedure.
|
||||
# The statement is rejected before any row is written.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
# Create a dummy procedure
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
SET @a=1;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
# Now replace the procedure
|
||||
CREATE OR REPLACE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
INSERT INTO t1 VALUES (1);
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
#
|
||||
# The check is done at prelocking time, before it is known whether
|
||||
# the trigger fires at all. A statement which would not fire the
|
||||
# trigger, because it inserts no rows, is therefore rejected as well.
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
INSERT INTO t1 SELECT a FROM t2;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
# End of 13.1 tests
|
||||
568
mysql-test/main/ps_in_func-trigger.test
Normal file
568
mysql-test/main/ps_in_func-trigger.test
Normal file
@@ -0,0 +1,568 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # PS commands inside a trigger are rejected at CREATE time
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
EXECUTE IMMEDIATE 'not important';
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
PREPARE stmt FROM 'SET a=1';
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE TRIGGER t1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A stored procedure with PS called from a trigger
|
||||
--echo # The CALL is rejected with "Dynamic SQL is not allowed",
|
||||
--echo # rather than "Not allowed to return a result set from a trigger",
|
||||
--echo # in all contexts, e.g. when:
|
||||
--echo # - a procedure with PS is called from a trigger directly
|
||||
--echo # - a procedure called from a trigger calls another procedure with PS
|
||||
--echo # - a function called from a trigger calls a procedure with PS
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1);
|
||||
SELECT * FROM t1;
|
||||
SELECT @a;
|
||||
DROP TRIGGER tr1;
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1);
|
||||
SELECT * FROM t1;
|
||||
SELECT @a;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with PREPARE/DEALLOCATE, i.e. without a possible result set
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'SET @a=1';
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1);
|
||||
SELECT * FROM t1;
|
||||
SELECT @a;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
SET @a=NULL;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with PS and a result set: PS is reported first
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
SELECT 1;
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p3;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1);
|
||||
DROP TRIGGER tr1;
|
||||
DROP PROCEDURE p3;
|
||||
|
||||
SELECT @a;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with PS called through a function
|
||||
--echo #
|
||||
|
||||
SET @a=NULL;
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p3()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
CALL p3;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1);
|
||||
SELECT @a;
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f2;
|
||||
DROP PROCEDURE p3;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A stored function with PS used from a trigger
|
||||
--echo # Not allowed even in a safe context (e.g. assignment in right hand)
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE nrows INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (12);
|
||||
DROP TRIGGER tr1;
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (12);
|
||||
DROP TRIGGER tr1;
|
||||
|
||||
#
|
||||
# With two tables
|
||||
#
|
||||
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t2 SET a=f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (12);
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t2;
|
||||
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t2 SET a=f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (12);
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t2;
|
||||
|
||||
DROP FUNCTION f1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40224 SIGSEGV in THD::is_error | Item::save_in_field | Item_trigger_field::set_value
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW SET NEW.b = f();
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
DROP TABLE t1;
|
||||
DROP FUNCTION f;
|
||||
|
||||
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE rc INT;
|
||||
SET rc= f1();
|
||||
RETURN rc;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW SET NEW.b = f2()
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40285 Assertion DBUG_ASSERT(thd->lock == 0) failed in lock_tables()
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t1 SET b=f();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
# A longer stack
|
||||
CREATE TABLE t1(a INT, b INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE res INT default f1();
|
||||
RETURN res;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE t1 SET b=f2();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1(a) VALUES (1);
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-41013 Assertion `inited == NONE || table->open_by_handler' failed in int handler::ha_external_lock(THD *, int)
|
||||
--echo #
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
IF FALSE THEN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
END IF;
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = f1();
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
UPDATE t1 SET a = a + 1;
|
||||
SELECT @x;
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-41015 SIGSEGV in THD::is_error | Item::save_in_field | Item_trigger_field::set_value on INSERT w/ TRIGGER
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.a = 999;
|
||||
SET NEW.b = f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A trigger with no PS can be invoked from
|
||||
--echo # a procedure containing PS
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = NEW.b+100;
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET b=3';
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A trigger with no PS can be invoked from
|
||||
--echo # a function containing PS
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (1,2);
|
||||
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW SET NEW.b = NEW.b+100;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET b=3';
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
SELECT a;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Testing multi-statement triggers
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT, b INT);
|
||||
INSERT INTO t1 VALUES (10,20);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1'; RETURN 5;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.b = f1(); -- fails
|
||||
/* make sure the rest does not get executed, @a remains 100 */
|
||||
SET NEW.a = 1;
|
||||
SET @a=NEW.a;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
SET @a= 100;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 VALUES (11,21);
|
||||
SELECT @a;
|
||||
SET @a= NULL;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with PS + a direct statement invoking a trigger with no PS
|
||||
--echo # It works without errors: the PS in the outer procedure is not a problem
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
SET @a=@a+100;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p2()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
INSERT INTO t1 VALUES (1);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p2;
|
||||
SELECT * FROM t1;
|
||||
SELECT @a;
|
||||
DROP PROCEDURE p2;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a=NULL;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A procedure with PS whose direct statement invokes a trigger
|
||||
--echo # calling this same procedure.
|
||||
--echo # The statement is rejected before any row is written.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
--echo # Create a dummy procedure
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
SET @a=1;
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
--echo # Now replace the procedure
|
||||
CREATE OR REPLACE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
INSERT INTO t1 VALUES (1);
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # The check is done at prelocking time, before it is known whether
|
||||
--echo # the trigger fires at all. A statement which would not fire the
|
||||
--echo # trigger, because it inserts no rows, is therefore rejected as well.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SET @a=1';
|
||||
END;
|
||||
$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
CALL p1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
INSERT INTO t1 SELECT a FROM t2;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
SET @a= NULL;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
77
mysql-test/main/ps_in_func-view.result
Normal file
77
mysql-test/main/ps_in_func-view.result
Normal file
@@ -0,0 +1,77 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# MDEV-41019 Crash on PS with CREATE VIEW with function calling another PS
|
||||
#
|
||||
#
|
||||
# A VIEW with a stored function containing PS (on its own, or through
|
||||
# another function can be created using direct or prepared execution.
|
||||
# But SELECT queries to such VIEWs are not allowed.
|
||||
#
|
||||
#
|
||||
# VIEW -> f1_with_ps()
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
UPDATE t1 SET a=11;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE VIEW v1 AS SELECT f1() AS c1;
|
||||
SHOW CREATE VIEW v1;
|
||||
View Create View character_set_client collation_connection
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `f1`() AS `c1` latin1 latin1_swedish_ci
|
||||
SELECT * FROM v1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP VIEW v1;
|
||||
EXECUTE IMMEDIATE 'CREATE VIEW v1 AS SELECT f1() AS c1';
|
||||
SHOW CREATE VIEW v1;
|
||||
View Create View character_set_client collation_connection
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `f1`() AS `c1` latin1 latin1_swedish_ci
|
||||
SELECT * FROM v1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP VIEW v1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# VEIW -> f1_without_ps() -> f2_with_ps()
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
UPDATE t1 SET a=11;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE VIEW v1 AS SELECT f2() AS c1;
|
||||
SHOW CREATE VIEW v1;
|
||||
View Create View character_set_client collation_connection
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `f2`() AS `c1` latin1 latin1_swedish_ci
|
||||
SELECT * FROM v1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP VIEW v1;
|
||||
EXECUTE IMMEDIATE 'CREATE VIEW v1 AS SELECT f2() AS c1';
|
||||
SHOW CREATE VIEW v1;
|
||||
View Create View character_set_client collation_connection
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `f2`() AS `c1` latin1 latin1_swedish_ci
|
||||
SELECT * FROM v1;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP VIEW v1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
# End of 13.1 tests
|
||||
87
mysql-test/main/ps_in_func-view.test
Normal file
87
mysql-test/main/ps_in_func-view.test
Normal file
@@ -0,0 +1,87 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-41019 Crash on PS with CREATE VIEW with function calling another PS
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # A VIEW with a stored function containing PS (on its own, or through
|
||||
--echo # another function can be created using direct or prepared execution.
|
||||
--echo # But SELECT queries to such VIEWs are not allowed.
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # VIEW -> f1_with_ps()
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
UPDATE t1 SET a=11;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CREATE VIEW v1 AS SELECT f1() AS c1;
|
||||
SHOW CREATE VIEW v1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM v1;
|
||||
DROP VIEW v1;
|
||||
|
||||
EXECUTE IMMEDIATE 'CREATE VIEW v1 AS SELECT f1() AS c1';
|
||||
SHOW CREATE VIEW v1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM v1;
|
||||
DROP VIEW v1;
|
||||
|
||||
SELECT * FROM t1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # VEIW -> f1_without_ps() -> f2_with_ps()
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 VALUES (10);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'UPDATE t1 SET a=11';
|
||||
UPDATE t1 SET a=11;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CREATE VIEW v1 AS SELECT f2() AS c1;
|
||||
SHOW CREATE VIEW v1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM v1;
|
||||
DROP VIEW v1;
|
||||
|
||||
EXECUTE IMMEDIATE 'CREATE VIEW v1 AS SELECT f2() AS c1';
|
||||
SHOW CREATE VIEW v1;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT * FROM v1;
|
||||
DROP VIEW v1;
|
||||
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
359
mysql-test/main/ps_in_func.result
Normal file
359
mysql-test/main/ps_in_func.result
Normal file
@@ -0,0 +1,359 @@
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# A function with PREPARE/EXECUTE/DEALLOCATE can be used with restrictions
|
||||
# OK when the function is used in an assignment right hand
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
# Assignment to a user variable
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
SET @a=f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
# Routine parameter
|
||||
CALL p1(f1());
|
||||
ERROR 42000: PROCEDURE test.p1 does not exist
|
||||
CREATE PROCEDURE p1(a INT)
|
||||
BEGIN
|
||||
BEGIN END;
|
||||
END;
|
||||
$$
|
||||
CALL p1(f1());
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP PROCEDURE p1;
|
||||
# SELECT list
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
# INTO + user variable
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
SELECT f1() INTO @a;
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @a;
|
||||
@a
|
||||
NULL
|
||||
# INTO + SP variable
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SELECT f1() INTO v;
|
||||
END;
|
||||
$$
|
||||
CALL p1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP PROCEDURE p1;
|
||||
# IF condition
|
||||
BEGIN NOT ATOMIC
|
||||
IF f1() THEN
|
||||
SELECT 1;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
# WHILE condition
|
||||
BEGIN NOT ATOMIC
|
||||
WHILE f1() DO
|
||||
SELECT 1;
|
||||
END WHILE;
|
||||
END;
|
||||
$$
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
# RETURN
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
RETURN f1();
|
||||
END;
|
||||
$$
|
||||
SELECT f2();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
DROP FUNCTION f2;
|
||||
# Assignment to a system variable
|
||||
SELECT @@sql_log_off;
|
||||
@@sql_log_off
|
||||
0
|
||||
SET @@sql_log_off= f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT @@sql_log_off;
|
||||
@@sql_log_off
|
||||
0
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Other tests
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
EXECUTE stmt;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
ERROR HY000: Unknown prepared statement handler (stmt) given to DEALLOCATE PREPARE
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# EXECUTE IMMEDIATE 'CALL ..' + SELECT
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
CREATE PROCEDURE p_sel()
|
||||
BEGIN
|
||||
SELECT a FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_call() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'CALL p_sel()';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_call();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
# second run
|
||||
CALL p();
|
||||
ERROR 0A000: Not allowed to return a result set from a function
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
1
|
||||
2
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_call;
|
||||
DROP PROCEDURE p_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# EXECUTE IMMEDIATE 'CALL ..' + SET
|
||||
#
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
CREATE PROCEDURE p_sel()
|
||||
BEGIN
|
||||
SET @a=10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_call() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'CALL p_sel()';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_call();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p();
|
||||
v
|
||||
1
|
||||
CALL p();
|
||||
v
|
||||
1
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a
|
||||
1
|
||||
2
|
||||
SELECT @a;
|
||||
@a
|
||||
10
|
||||
SET @a=NULL;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_call;
|
||||
DROP PROCEDURE p_sel;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# DML statements
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
PREPARE stmt FROM query;
|
||||
EXECUTE stmt;
|
||||
SET nrows= ROW_COUNT();
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
CALL p1('INSERT INTO t1 VALUES (10),(20),(30)');
|
||||
nrows
|
||||
3
|
||||
a
|
||||
10
|
||||
20
|
||||
30
|
||||
CALL p1('UPDATE t1 SET a=a+1');
|
||||
nrows
|
||||
3
|
||||
a
|
||||
11
|
||||
21
|
||||
31
|
||||
CALL p1('DELETE FROM t1 WHERE a>20');
|
||||
nrows
|
||||
2
|
||||
a
|
||||
11
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Routine versions
|
||||
#
|
||||
# A routine reached by a statement with dynamic SQL is not prelocked,
|
||||
# but its version is still recorded, so a prepared statement or a
|
||||
# stored routine instruction prepared against an older version of the
|
||||
# routine is re-prepared rather than executed from a parse tree which
|
||||
# no longer matches the routine.
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2() RETURNS INT RETURN 2;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE b INT;
|
||||
SET a= f1();
|
||||
SET b= f2();
|
||||
SELECT a, b;
|
||||
END;
|
||||
$$
|
||||
PREPARE stmt FROM 'CALL p1()';
|
||||
EXECUTE stmt;
|
||||
a b
|
||||
1 2
|
||||
# Replace the function which carries the prepared statement
|
||||
CREATE OR REPLACE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 11 INTO @x';
|
||||
RETURN 11;
|
||||
END;
|
||||
$$
|
||||
EXECUTE stmt;
|
||||
a b
|
||||
11 2
|
||||
# Replace the function reached after the dynamic SQL was detected
|
||||
CREATE OR REPLACE FUNCTION f2() RETURNS INT RETURN 22;
|
||||
EXECUTE stmt;
|
||||
a b
|
||||
11 22
|
||||
DEALLOCATE PREPARE stmt;
|
||||
# The same for a directly executed CALL: the SET instructions of p1
|
||||
# were prepared against the previous version of f2.
|
||||
CREATE OR REPLACE FUNCTION f2() RETURNS INT RETURN 222;
|
||||
CALL p1;
|
||||
a b
|
||||
11 222
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
# End of 13.1 tests
|
||||
405
mysql-test/main/ps_in_func.test
Normal file
405
mysql-test/main/ps_in_func.test
Normal file
@@ -0,0 +1,405 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # A function with PREPARE/EXECUTE/DEALLOCATE can be used with restrictions
|
||||
--echo # OK when the function is used in an assignment right hand
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
|
||||
--echo # Assignment to a user variable
|
||||
|
||||
SELECT @a;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SET @a=f1();
|
||||
SELECT @a;
|
||||
|
||||
|
||||
--echo # Routine parameter
|
||||
|
||||
--error ER_SP_DOES_NOT_EXIST
|
||||
CALL p1(f1());
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1(a INT)
|
||||
BEGIN
|
||||
BEGIN END;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1(f1());
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo # SELECT list
|
||||
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
|
||||
|
||||
--echo # INTO + user variable
|
||||
|
||||
SELECT @a;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1() INTO @a;
|
||||
SELECT @a;
|
||||
|
||||
--echo # INTO + SP variable
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SELECT f1() INTO v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CALL p1();
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
--echo # IF condition
|
||||
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
BEGIN NOT ATOMIC
|
||||
IF f1() THEN
|
||||
SELECT 1;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
|
||||
--echo # WHILE condition
|
||||
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
BEGIN NOT ATOMIC
|
||||
WHILE f1() DO
|
||||
SELECT 1;
|
||||
END WHILE;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
|
||||
--echo # RETURN
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f2() RETURNS INT
|
||||
BEGIN
|
||||
RETURN f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f2();
|
||||
DROP FUNCTION f2;
|
||||
|
||||
|
||||
--echo # Assignment to a system variable
|
||||
|
||||
SELECT @@sql_log_off;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SET @@sql_log_off= f1();
|
||||
SELECT @@sql_log_off;
|
||||
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Other tests
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
EXECUTE stmt;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
PREPARE stmt FROM 'INSERT INTO t1 VALUES (10)';
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
--error ER_UNKNOWN_STMT_HANDLER
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT DEFAULT f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
CALL p1;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # EXECUTE IMMEDIATE 'CALL ..' + SELECT
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p_sel()
|
||||
BEGIN
|
||||
SELECT a FROM t1;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_call() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'CALL p_sel()';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_call();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
--echo # second run
|
||||
--error ER_SP_NO_RETSET
|
||||
CALL p();
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_call;
|
||||
DROP PROCEDURE p_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # EXECUTE IMMEDIATE 'CALL ..' + SET
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1(a INT);
|
||||
INSERT INTO t1 VALUES (1),(2);
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p_sel()
|
||||
BEGIN
|
||||
SET @a=10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_call() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'CALL p_sel()';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE v INT DEFAULT f_call();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p();
|
||||
CALL p();
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
SELECT @a;
|
||||
SET @a=NULL;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f_call;
|
||||
DROP PROCEDURE p_sel;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # DML statements
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1(query TEXT) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
PREPARE stmt FROM query;
|
||||
EXECUTE stmt;
|
||||
SET nrows= ROW_COUNT();
|
||||
DEALLOCATE PREPARE stmt;
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1(query TEXT)
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1(query);
|
||||
SELECT nrows;
|
||||
SELECT * FROM t1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1('INSERT INTO t1 VALUES (10),(20),(30)');
|
||||
CALL p1('UPDATE t1 SET a=a+1');
|
||||
CALL p1('DELETE FROM t1 WHERE a>20');
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
DROP TABLE t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Routine versions
|
||||
--echo #
|
||||
|
||||
--echo # A routine reached by a statement with dynamic SQL is not prelocked,
|
||||
--echo # but its version is still recorded, so a prepared statement or a
|
||||
--echo # stored routine instruction prepared against an older version of the
|
||||
--echo # routine is re-prepared rather than executed from a parse tree which
|
||||
--echo # no longer matches the routine.
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 1 INTO @x';
|
||||
RETURN 1;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CREATE FUNCTION f2() RETURNS INT RETURN 2;
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE a INT;
|
||||
DECLARE b INT;
|
||||
SET a= f1();
|
||||
SET b= f2();
|
||||
SELECT a, b;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
PREPARE stmt FROM 'CALL p1()';
|
||||
EXECUTE stmt;
|
||||
|
||||
--echo # Replace the function which carries the prepared statement
|
||||
DELIMITER $$;
|
||||
CREATE OR REPLACE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'SELECT 11 INTO @x';
|
||||
RETURN 11;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
EXECUTE stmt;
|
||||
|
||||
--echo # Replace the function reached after the dynamic SQL was detected
|
||||
CREATE OR REPLACE FUNCTION f2() RETURNS INT RETURN 22;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
--echo # The same for a directly executed CALL: the SET instructions of p1
|
||||
--echo # were prepared against the previous version of f2.
|
||||
CREATE OR REPLACE FUNCTION f2() RETURNS INT RETURN 222;
|
||||
CALL p1;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f2;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
@@ -796,7 +796,7 @@ END;
|
||||
$$
|
||||
ERROR 42000: OPEN..FOR does not support subqueries or stored functions
|
||||
DROP FUNCTION f1;
|
||||
# Dynamic SQL is not allowed in stored functions
|
||||
# Declaring a cursor variable is OK inside a function
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
@@ -804,7 +804,12 @@ DECLARE c CURSOR FOR stmt;
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
SELECT f1();
|
||||
f1()
|
||||
10
|
||||
DROP FUNCTION f1;
|
||||
# Dynamic SQL is allowed in stored functions with restrictions:
|
||||
# OK when the function is used as an assignment right hand
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
@@ -815,7 +820,20 @@ CLOSE c;
|
||||
RETURN v;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
v
|
||||
10
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
# Static OPEN is OK in SF, both for Standard cursors and SYS_REFCURSORs
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
|
||||
@@ -917,10 +917,8 @@ DELIMITER ;$$
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo # Dynamic SQL is not allowed in stored functions
|
||||
|
||||
--echo # Declaring a cursor variable is OK inside a function
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
@@ -929,10 +927,14 @@ BEGIN
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
SELECT f1();
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo # Dynamic SQL is allowed in stored functions with restrictions:
|
||||
--echo # OK when the function is used as an assignment right hand
|
||||
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE FUNCTION f1() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
@@ -944,6 +946,20 @@ BEGIN
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE v INT;
|
||||
SET v= f1();
|
||||
SELECT v;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
--echo # Static OPEN is OK in SF, both for Standard cursors and SYS_REFCURSORs
|
||||
|
||||
@@ -32,11 +32,24 @@ CREATE FUNCTION f1() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
BEGIN
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
CLOSE c0;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= f1();
|
||||
END;
|
||||
$$
|
||||
PREPARE stmt FROM 'SELECT 1,2';
|
||||
CALL p1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
CREATE PROCEDURE p1 AS
|
||||
c0 SYS_REFCURSOR;
|
||||
v0 TEXT;
|
||||
ps_name VARCHAR(30) := 'stmt';
|
||||
@@ -60,8 +73,22 @@ OPEN c0 FOR LOCAL spvar;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
SELECT f1();
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= f1();
|
||||
END;
|
||||
$$
|
||||
CALL p1;
|
||||
ERROR HY000: Unknown prepared statement handler (stmt0) given to OPEN
|
||||
PREPARE stmt0 FROM 'SELECT 1,2';
|
||||
CALL p1;
|
||||
DEALLOCATE PREPARE stmt0;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
CREATE PROCEDURE p1 AS
|
||||
c0 SYS_REFCURSOR;
|
||||
ps_name VARCHAR(10) := 'stmt';
|
||||
BEGIN
|
||||
|
||||
@@ -40,18 +40,34 @@ CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
# OPEN c FOR PREPARE stmt - not allowed in a stored function
|
||||
# OPEN c FOR PREPARE stmt - allowed in a stored function with restrictions
|
||||
# OK when used as an assignment right hand
|
||||
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE FUNCTION f1() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
BEGIN
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
CLOSE c0;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
PREPARE stmt FROM 'SELECT 1,2';
|
||||
CALL p1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
# OPEN c FOR LOCAL var
|
||||
@@ -74,9 +90,10 @@ CALL p1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
|
||||
# OPEN c FOR LOCAL spvar - not allowed in a stored function
|
||||
# OPEN c FOR LOCAL spvar - allowed in a stored function with restrictions
|
||||
# OK when used as an assignment right hand
|
||||
|
||||
DELIMITER $$;
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
CREATE FUNCTION f1() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
spvar VARCHAR(10) DEFAULT 'stmt0';
|
||||
@@ -86,6 +103,23 @@ BEGIN
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
SELECT f1();
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1 AS
|
||||
a INT;
|
||||
BEGIN
|
||||
a:= f1();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
--error ER_UNKNOWN_STMT_HANDLER
|
||||
CALL p1;
|
||||
PREPARE stmt0 FROM 'SELECT 1,2';
|
||||
CALL p1;
|
||||
DEALLOCATE PREPARE stmt0;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f1;
|
||||
|
||||
|
||||
# OPEN c FOR LOCAL var - not all parameters bound
|
||||
|
||||
@@ -335,7 +335,9 @@ begin
|
||||
deallocate prepare stmt;
|
||||
return 1;
|
||||
end|
|
||||
select f1()|
|
||||
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
|
||||
drop function f1|
|
||||
create procedure p1()
|
||||
begin
|
||||
prepare stmt from 'select 1 A';
|
||||
|
||||
@@ -307,12 +307,14 @@ drop procedure p1|
|
||||
# PS execution if another thread drops other SP in between") is fixed.
|
||||
#
|
||||
# b) attempt to deallocate a prepared statement that is being executed
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
create function f1() returns int
|
||||
begin
|
||||
deallocate prepare stmt;
|
||||
return 1;
|
||||
end|
|
||||
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
|
||||
select f1()|
|
||||
drop function f1|
|
||||
|
||||
# b)-2 a crash (#1) spotted by Sergey Petrunia during code review
|
||||
create procedure p1()
|
||||
|
||||
542
mysql-test/suite/binlog/include/binlog_ps_in_func.inc
Normal file
542
mysql-test/suite/binlog/include/binlog_ps_in_func.inc
Normal file
@@ -0,0 +1,542 @@
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # p3(set) - f2(set) - f1(immediate)
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
SHOW CREATE TABLE t1;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1_immediate();
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_set();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p3_set;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_set;
|
||||
DROP FUNCTION f1_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
--echo #
|
||||
--echo # p3(set) - f2(set,immediate) - f1(immediate)
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
SHOW CREATE TABLE t1;
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set_immediate() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1_immediate();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_set_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p3_set;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_set_immediate;
|
||||
DROP FUNCTION f1_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # p3(set) - f2(call,immediate) - p1(immediate)
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
SHOW CREATE TABLE t1;
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p1_immediate()
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_call() RETURNS INT
|
||||
BEGIN
|
||||
CALL p1_immediate();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_call();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
START TRANSACTION;
|
||||
CALL p3_set;
|
||||
COMMIT;
|
||||
SELECT * FROM t1;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_call;
|
||||
DROP PROCEDURE p1_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Package
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
SHOW CREATE TABLE t1;
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1
|
||||
PROCEDURE parse(query TEXT);
|
||||
FUNCTION exec() RETURNS INT;
|
||||
PROCEDURE close();
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1
|
||||
PROCEDURE parse(query TEXT)
|
||||
BEGIN
|
||||
PREPARE stmt FROM query;
|
||||
END;
|
||||
FUNCTION exec() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
PROCEDURE close()
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
START TRANSACTION;
|
||||
CALL pkg1.parse('INSERT INTO t1 VALUES (10)');
|
||||
DELIMITER $$;
|
||||
BEGIN NOT ATOMIC
|
||||
DECLARE nrows INT;
|
||||
SET nrows= pkg1.exec();
|
||||
SELECT nrows;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
COMMIT;
|
||||
|
||||
CALL pkg1.close();
|
||||
SELECT * FROM t1;
|
||||
|
||||
DROP PACKAGE pkg1;
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Package, with SYS_REFCURSOR
|
||||
--echo #
|
||||
|
||||
SET sql_mode=ORACLE;
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
SHOW CREATE TABLE t1;
|
||||
INSERT INTO t1 VALUES (10), (20);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1 AS
|
||||
PROCEDURE parse(query TEXT);
|
||||
FUNCTION exec() RETURN INT;
|
||||
PROCEDURE close();
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1 AS
|
||||
PROCEDURE parse(query TEXT) AS
|
||||
BEGIN
|
||||
PREPARE stmt FROM query;
|
||||
END;
|
||||
FUNCTION exec() RETURN INT AS
|
||||
c0 SYS_REFCURSOR;
|
||||
nrows INT:= 0;
|
||||
v0 INT;
|
||||
BEGIN
|
||||
OPEN c0 FOR PREPARE stmt;
|
||||
LOOP
|
||||
FETCH c0 INTO v0;
|
||||
EXIT WHEN c0%NOTFOUND;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (?)' USING v0+100;
|
||||
nrows := nrows + 1;
|
||||
END LOOP;
|
||||
CLOSE c0;
|
||||
RETURN nrows;
|
||||
END;
|
||||
PROCEDURE close() AS
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL pkg1.parse('SELECT a FROM t1');
|
||||
START TRANSACTION;
|
||||
DELIMITER $$;
|
||||
DECLARE
|
||||
nrows INT;
|
||||
BEGIN
|
||||
SET nrows= pkg1.exec();
|
||||
SELECT nrows;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (200)'; -- Additional INSERT
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
COMMIT;
|
||||
CALL pkg1.close();
|
||||
SELECT * FROM t1;
|
||||
|
||||
DROP PACKAGE pkg1;
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
SET sql_mode=DEFAULT;
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40227 Replication breaks when a function performs multiple INSERT statements with binlog_format=MIXED or STATEMENT
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, v INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (100)';
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (200)';
|
||||
INSERT INTO t1(v) VALUES (100);
|
||||
INSERT INTO t1(v) VALUES (200);
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p()
|
||||
BEGIN
|
||||
DECLARE n INT DEFAULT f();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f;
|
||||
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40318 MIXED binlog format does not switch an unsafe-for-SBR dynamic SQL (via PS) to ROW logging, causing replica divergence
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE td (id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
ts_now DATETIME(6), rnd DOUBLE, uu CHAR(36), usr_var INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_nd(seed INT) RETURNS INT
|
||||
BEGIN
|
||||
SET @uv = seed * 7;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO td (ts_now, rnd, uu, usr_var) '
|
||||
'VALUES (NOW(6), RAND(?), UUID(), @uv)'
|
||||
USING seed;
|
||||
RETURN seed;
|
||||
END $$
|
||||
CREATE PROCEDURE p_nd(seed INT)
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_nd(seed);
|
||||
END $$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p_nd(1);
|
||||
CALL p_nd(2);
|
||||
CALL p_nd(3);
|
||||
CALL p_nd(4);
|
||||
CALL p_nd(5);
|
||||
|
||||
DROP FUNCTION f_nd;
|
||||
DROP PROCEDURE p_nd;
|
||||
DROP TABLE td;
|
||||
|
||||
--let $binlog_file = LAST
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
--echo #
|
||||
--echo # End of MDEV-40318
|
||||
--echo #
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Test functions without PS
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, num INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_without_ps() RETURNS INT
|
||||
BEGIN
|
||||
INSERT INTO t1 (num) VALUES (1);
|
||||
INSERT INTO t1 (num) VALUES (2);
|
||||
RETURN 1;
|
||||
END $$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_without_ps();
|
||||
END $$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
|
||||
INSERT INTO t1 (num) VALUES (11);
|
||||
INSERT INTO t1 (num) VALUES (12);
|
||||
|
||||
DROP FUNCTION f_without_ps;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $MYSQLD_DATADIR= `SELECT @@datadir`
|
||||
--let $binlog_name= query_get_value(SHOW MASTER STATUS, File, 1)
|
||||
--replace_regex /^\# at .*// /.*SET (@@|TIMESTAMP).*// /.* end_log_pos .*// /xid=\d*/xid=<xid>/
|
||||
--exec $MYSQL_BINLOG --force-if-open --base64-output=DECODE-ROWS --verbose --verbose $MYSQLD_DATADIR/$binlog_name
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Test the same functions but with PS
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, num INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_with_ps() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (num) VALUES (1)';
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (num) VALUES (2)';
|
||||
RETURN 1;
|
||||
END $$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_with_ps();
|
||||
END $$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
CALL p1();
|
||||
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (num) VALUES (11)';
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 (num) VALUES (12)';
|
||||
|
||||
DROP FUNCTION f_with_ps;
|
||||
DROP PROCEDURE p1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--let $MYSQLD_DATADIR= `SELECT @@datadir`
|
||||
--let $binlog_name= query_get_value(SHOW MASTER STATUS, File, 1)
|
||||
--replace_regex /^\# at .*// /.*SET (@@|TIMESTAMP).*// /.* end_log_pos .*// /xid=\d*/xid=<xid>/
|
||||
--exec $MYSQL_BINLOG --force-if-open --base64-output=DECODE-ROWS --verbose --verbose $MYSQLD_DATADIR/$binlog_name
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A mixture of functions with and without PS
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
--source include/ps_in_func-mixture-01-create.inc
|
||||
CALL p1();
|
||||
SELECT * FROM t1 ORDER BY val, caller;
|
||||
--source include/ps_in_func-mixture-01-drop.inc
|
||||
|
||||
--let $MYSQLD_DATADIR= `SELECT @@datadir`
|
||||
--let $binlog_name= query_get_value(SHOW MASTER STATUS, File, 1)
|
||||
--replace_regex /^\# at .*// /.*SET (@@|TIMESTAMP).*// /.* end_log_pos .*// /xid=\d*/xid=<xid>/
|
||||
--exec $MYSQL_BINLOG --force-if-open --base64-output=DECODE-ROWS --verbose --verbose $MYSQLD_DATADIR/$binlog_name
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A dynamic statement inside a function with PS fires a trigger,
|
||||
--echo # and the trigger body calls another function.
|
||||
--echo #
|
||||
--echo # The trigger is not logged per statement: the INSERT which fired it
|
||||
--echo # is logged instead, and the replica fires the trigger itself. So the
|
||||
--echo # trigger body must not inherit the per-statement logging mode of the
|
||||
--echo # enclosing function, and the function called from the trigger body
|
||||
--echo # must not start a second binlog event union on top of the trigger's.
|
||||
--echo #
|
||||
|
||||
--source include/reset_master.inc
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_double(a INT) RETURNS INT
|
||||
BEGIN
|
||||
RETURN a * 2;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
INSERT INTO t2 VALUES (f_double(NEW.a));
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_with_ps() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f_with_ps();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p1();
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f_with_ps;
|
||||
DROP TRIGGER tr1;
|
||||
DROP FUNCTION f_double;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--let $MYSQLD_DATADIR= `SELECT @@datadir`
|
||||
--let $binlog_name= query_get_value(SHOW MASTER STATUS, File, 1)
|
||||
--replace_regex /^\# at .*// /.*SET (@@|TIMESTAMP).*// /.* end_log_pos .*// /xid=\d*/xid=<xid>/
|
||||
--exec $MYSQL_BINLOG --force-if-open --base64-output=DECODE-ROWS --verbose --verbose $MYSQLD_DATADIR/$binlog_name
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A dynamic statement inside a function with PS fires a trigger
|
||||
--echo # whose body calls RAND().
|
||||
--echo #
|
||||
--echo # The trigger body is not logged: the INSERT which fired it is logged
|
||||
--echo # and the replica fires the trigger itself. So the rand() call inside
|
||||
--echo # the trigger belongs to the INSERT, and the INSERT must be preceded
|
||||
--echo # by a Rand event carrying the seeds. The same INSERT executed at the
|
||||
--echo # top level and from a plain stored procedure is shown for comparison:
|
||||
--echo # all three must log the seeds the same way.
|
||||
--echo #
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT, r DOUBLE);
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
|
||||
INSERT INTO t2 VALUES (NEW.a, RAND());
|
||||
DELIMITER $$;
|
||||
CREATE PROCEDURE p_plain()
|
||||
BEGIN
|
||||
INSERT INTO t1 VALUES (2);
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f_with_ps() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (3)';
|
||||
RETURN 3;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p_ps()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f_with_ps();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
--echo # Top level statement
|
||||
--source include/reset_master.inc
|
||||
INSERT INTO t1 VALUES (1);
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
--echo # Inside a plain stored procedure
|
||||
--source include/reset_master.inc
|
||||
CALL p_plain();
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
--echo # A dynamic statement inside a function with PS
|
||||
--source include/reset_master.inc
|
||||
CALL p_ps();
|
||||
--source include/show_binlog_events.inc
|
||||
|
||||
SELECT a FROM t2 ORDER BY a;
|
||||
DROP PROCEDURE p_ps;
|
||||
DROP FUNCTION f_with_ps;
|
||||
DROP PROCEDURE p_plain;
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t1, t2;
|
||||
1129
mysql-test/suite/binlog/r/binlog_ps_in_func_mix_innodb.result
Normal file
1129
mysql-test/suite/binlog/r/binlog_ps_in_func_mix_innodb.result
Normal file
File diff suppressed because it is too large
Load Diff
1164
mysql-test/suite/binlog/r/binlog_ps_in_func_mix_myisam.result
Normal file
1164
mysql-test/suite/binlog/r/binlog_ps_in_func_mix_myisam.result
Normal file
File diff suppressed because it is too large
Load Diff
1245
mysql-test/suite/binlog/r/binlog_ps_in_func_row_innodb.result
Normal file
1245
mysql-test/suite/binlog/r/binlog_ps_in_func_row_innodb.result
Normal file
File diff suppressed because it is too large
Load Diff
1281
mysql-test/suite/binlog/r/binlog_ps_in_func_row_myisam.result
Normal file
1281
mysql-test/suite/binlog/r/binlog_ps_in_func_row_myisam.result
Normal file
File diff suppressed because it is too large
Load Diff
1101
mysql-test/suite/binlog/r/binlog_ps_in_func_stm_innodb.result
Normal file
1101
mysql-test/suite/binlog/r/binlog_ps_in_func_stm_innodb.result
Normal file
File diff suppressed because it is too large
Load Diff
1131
mysql-test/suite/binlog/r/binlog_ps_in_func_stm_myisam.result
Normal file
1131
mysql-test/suite/binlog/r/binlog_ps_in_func_stm_myisam.result
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_mixed.inc
|
||||
--source include/have_innodb.inc
|
||||
|
||||
SET default_storage_engine=InnoDB;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
@@ -0,0 +1,8 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_mixed.inc
|
||||
|
||||
SET default_storage_engine=MyISAM;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
@@ -0,0 +1,9 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_row.inc
|
||||
--source include/have_innodb.inc
|
||||
|
||||
SET default_storage_engine=InnoDB;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
@@ -0,0 +1,8 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_row.inc
|
||||
|
||||
SET default_storage_engine=MyISAM;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
12
mysql-test/suite/binlog/t/binlog_ps_in_func_stm_innodb.test
Normal file
12
mysql-test/suite/binlog/t/binlog_ps_in_func_stm_innodb.test
Normal file
@@ -0,0 +1,12 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_statement.inc
|
||||
--source include/have_innodb.inc
|
||||
|
||||
CALL mtr.add_suppression("Statement is unsafe because it uses a system function that may return a different value on the slave");
|
||||
CALL mtr.add_suppression("Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column");
|
||||
|
||||
SET default_storage_engine=InnoDB;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
11
mysql-test/suite/binlog/t/binlog_ps_in_func_stm_myisam.test
Normal file
11
mysql-test/suite/binlog/t/binlog_ps_in_func_stm_myisam.test
Normal file
@@ -0,0 +1,11 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_binlog_format_statement.inc
|
||||
|
||||
CALL mtr.add_suppression("Statement is unsafe because it uses a system function that may return a different value on the slave");
|
||||
CALL mtr.add_suppression("Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column");
|
||||
|
||||
SET default_storage_engine=MyISAM;
|
||||
--source include/binlog_ps_in_func.inc
|
||||
SET default_storage_engine=DEFAULT;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
196
mysql-test/suite/rpl/r/rpl_ps_in_func.result
Normal file
196
mysql-test/suite/rpl/r/rpl_ps_in_func.result
Normal file
@@ -0,0 +1,196 @@
|
||||
include/master-slave.inc
|
||||
[connection master]
|
||||
#
|
||||
# MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
#
|
||||
#
|
||||
# p3(set) - f2(set,immediate) - f1(immediate)
|
||||
#
|
||||
connection master;
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE FUNCTION f1_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set_immediate() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1_immediate();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_set_immediate();
|
||||
END;
|
||||
$$
|
||||
CALL p3_set;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
11
|
||||
connection slave;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
11
|
||||
connection master;
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_set_immediate;
|
||||
DROP FUNCTION f1_immediate;
|
||||
DROP TABLE t1;
|
||||
connection slave;
|
||||
#
|
||||
# Package
|
||||
#
|
||||
connection master;
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE PACKAGE pkg1
|
||||
PROCEDURE parse(query TEXT);
|
||||
FUNCTION exec() RETURNS INT;
|
||||
PROCEDURE close();
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1
|
||||
PROCEDURE parse(query TEXT)
|
||||
BEGIN
|
||||
PREPARE stmt FROM query;
|
||||
END;
|
||||
FUNCTION exec() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
PROCEDURE close()
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
END;
|
||||
$$
|
||||
CALL pkg1.parse('INSERT INTO t1 VALUES (10)');
|
||||
BEGIN NOT ATOMIC
|
||||
DECLARE nrows INT;
|
||||
SET nrows= pkg1.exec();
|
||||
SELECT nrows;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
END;
|
||||
$$
|
||||
nrows
|
||||
1
|
||||
CALL pkg1.close();
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
11
|
||||
connection slave;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
11
|
||||
connection master;
|
||||
DROP PACKAGE pkg1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
10
|
||||
11
|
||||
DROP TABLE t1;
|
||||
connection slave;
|
||||
#
|
||||
# A mixture of function calls with and without PS
|
||||
#
|
||||
connection master;
|
||||
#
|
||||
# A function without PS called differently:
|
||||
# - from a procedure through a function with PS
|
||||
# - from a procedure directly
|
||||
#
|
||||
CREATE TABLE t1 (val VARCHAR(30), caller VARCHAR(128));
|
||||
CREATE FUNCTION f_without_ps(ofs INT, caller VARCHAR(128)) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE caller2 VARCHAR(128) DEFAULT CONCAT(caller,'/f_without_ps');
|
||||
INSERT INTO t1 (val, caller) VALUES (ofs+1, caller2);
|
||||
INSERT INTO t1 (val, caller) VALUES (ofs+2, caller2);
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE FUNCTION f_with_ps(ofs INT, caller VARCHAR(128)) RETURNS INT
|
||||
BEGIN
|
||||
DECLARE query TEXT DEFAULT 'INSERT INTO t1 (val,caller) VALUES (?,?)';
|
||||
DECLARE caller2 VARCHAR(128) DEFAULT CONCAT(caller,'/f_with_ps');
|
||||
DECLARE a INT DEFAULT f_without_ps(ofs, caller2);
|
||||
EXECUTE IMMEDIATE query USING ofs+3, caller2;
|
||||
EXECUTE IMMEDIATE query USING ofs+4, caller2;
|
||||
RETURN 1;
|
||||
END
|
||||
$$
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE query TEXT DEFAULT 'INSERT INTO t1 (val,caller) VALUES (?,?)';
|
||||
DECLARE v1 INT DEFAULT f_with_ps(100, 'p1');
|
||||
DECLARE v2 INT DEFAULT f_without_ps(200, 'p1');
|
||||
EXECUTE IMMEDIATE query USING 301, 'p1';
|
||||
EXECUTE IMMEDIATE query USING 302, 'p1';
|
||||
END $$
|
||||
CALL p1();
|
||||
SELECT * FROM t1 ORDER BY val, caller;
|
||||
val caller
|
||||
101 p1/f_with_ps/f_without_ps
|
||||
102 p1/f_with_ps/f_without_ps
|
||||
103 p1/f_with_ps
|
||||
104 p1/f_with_ps
|
||||
201 p1/f_without_ps
|
||||
202 p1/f_without_ps
|
||||
301 p1
|
||||
302 p1
|
||||
connection slave;
|
||||
connection slave;
|
||||
SELECT * FROM t1 ORDER BY val, caller;
|
||||
val caller
|
||||
101 p1/f_with_ps/f_without_ps
|
||||
102 p1/f_with_ps/f_without_ps
|
||||
103 p1/f_with_ps
|
||||
104 p1/f_with_ps
|
||||
201 p1/f_without_ps
|
||||
202 p1/f_without_ps
|
||||
301 p1
|
||||
302 p1
|
||||
connection master;
|
||||
DROP PROCEDURE p1;
|
||||
DROP FUNCTION f_with_ps;
|
||||
DROP FUNCTION f_without_ps;
|
||||
DROP TABLE t1;
|
||||
connection slave;
|
||||
#
|
||||
# MDEV-40227 Replication breaks when a function performs multiple INSERT statements with binlog_format=MIXED or STATEMENT
|
||||
#
|
||||
connection master;
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, v INT);
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (100)';
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (200)';
|
||||
RETURN 0;
|
||||
END$$
|
||||
CREATE PROCEDURE p() BEGIN DECLARE n INT DEFAULT f(); END$$
|
||||
CALL p();
|
||||
SELECT id, v FROM t1 ORDER BY id;
|
||||
id v
|
||||
1 100
|
||||
2 200
|
||||
connection slave;
|
||||
connection slave;
|
||||
SELECT id, v FROM t1 ORDER BY id;
|
||||
id v
|
||||
1 100
|
||||
2 200
|
||||
connection master;
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
connection slave;
|
||||
# End of 13.1 tests
|
||||
include/rpl_end.inc
|
||||
@@ -0,0 +1,43 @@
|
||||
include/master-slave.inc
|
||||
[connection master]
|
||||
#
|
||||
# MDEV-40318 MIXED binlog format does not switch an unsafe-for-SBR dynamic SQL (via PS) to ROW logging, causing replica divergence
|
||||
#
|
||||
connection master;
|
||||
CREATE TABLE td (id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
ts_now DATETIME(6), rnd DOUBLE, uu CHAR(36), usr_var INT) ENGINE=InnoDB;
|
||||
CREATE FUNCTION f_nd(seed INT) RETURNS INT
|
||||
BEGIN
|
||||
SET @uv = seed * 7;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO td (ts_now, rnd, uu, usr_var) '
|
||||
'VALUES (NOW(6), RAND(?), UUID(), @uv)'
|
||||
USING seed;
|
||||
RETURN seed;
|
||||
END $$
|
||||
CREATE PROCEDURE p_nd(seed INT)
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_nd(seed);
|
||||
END $$
|
||||
CALL p_nd(1);
|
||||
CALL p_nd(2);
|
||||
CALL p_nd(3);
|
||||
CALL p_nd(4);
|
||||
CALL p_nd(5);
|
||||
EXECUTE IMMEDIATE 'INSERT INTO td (ts_now, rnd, uu, usr_var) '
|
||||
'VALUES (NOW(6), RAND(?), UUID(), @uv)'
|
||||
USING 10;
|
||||
SELECT COUNT(*) FROM td;
|
||||
COUNT(*)
|
||||
6
|
||||
connection slave;
|
||||
SELECT COUNT(*) FROM td;
|
||||
COUNT(*)
|
||||
6
|
||||
connection master;
|
||||
DROP PROCEDURE p_nd;
|
||||
DROP FUNCTION f_nd;
|
||||
DROP TABLE td;
|
||||
connection slave;
|
||||
connection master;
|
||||
# End of 13.1 tests
|
||||
include/rpl_end.inc
|
||||
155
mysql-test/suite/rpl/t/rpl_ps_in_func.test
Normal file
155
mysql-test/suite/rpl/t/rpl_ps_in_func.test
Normal file
@@ -0,0 +1,155 @@
|
||||
--source include/master-slave.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39518 Allow prepared statements in stored functions in assignment right hand
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # p3(set) - f2(set,immediate) - f1(immediate)
|
||||
--echo #
|
||||
|
||||
connection master;
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f1_immediate() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (10)';
|
||||
RETURN 10;
|
||||
END;
|
||||
$$
|
||||
CREATE FUNCTION f2_set_immediate() RETURNS INT
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f1_immediate();
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
RETURN nrows;
|
||||
END;
|
||||
$$
|
||||
CREATE PROCEDURE p3_set()
|
||||
BEGIN
|
||||
DECLARE nrows INT;
|
||||
SET nrows= f2_set_immediate();
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL p3_set;
|
||||
SELECT * FROM t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
SELECT * FROM t1;
|
||||
|
||||
connection master;
|
||||
|
||||
DROP PROCEDURE p3_set;
|
||||
DROP FUNCTION f2_set_immediate;
|
||||
DROP FUNCTION f1_immediate;
|
||||
DROP TABLE t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
--echo #
|
||||
--echo # Package
|
||||
--echo #
|
||||
|
||||
connection master;
|
||||
|
||||
CREATE TABLE t1 (a INT);
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE PACKAGE pkg1
|
||||
PROCEDURE parse(query TEXT);
|
||||
FUNCTION exec() RETURNS INT;
|
||||
PROCEDURE close();
|
||||
END;
|
||||
$$
|
||||
CREATE PACKAGE BODY pkg1
|
||||
PROCEDURE parse(query TEXT)
|
||||
BEGIN
|
||||
PREPARE stmt FROM query;
|
||||
END;
|
||||
FUNCTION exec() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE stmt;
|
||||
RETURN ROW_COUNT();
|
||||
END;
|
||||
PROCEDURE close()
|
||||
BEGIN
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END;
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL pkg1.parse('INSERT INTO t1 VALUES (10)');
|
||||
DELIMITER $$;
|
||||
BEGIN NOT ATOMIC
|
||||
DECLARE nrows INT;
|
||||
SET nrows= pkg1.exec();
|
||||
SELECT nrows;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (11)';
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;$$
|
||||
CALL pkg1.close();
|
||||
SELECT * FROM t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
SELECT * FROM t1;
|
||||
|
||||
connection master;
|
||||
|
||||
DROP PACKAGE pkg1;
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
sync_slave_with_master;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A mixture of function calls with and without PS
|
||||
--echo #
|
||||
|
||||
--connection master
|
||||
--source include/ps_in_func-mixture-01-create.inc
|
||||
CALL p1();
|
||||
SELECT * FROM t1 ORDER BY val, caller;
|
||||
--sync_slave_with_master
|
||||
--connection slave
|
||||
SELECT * FROM t1 ORDER BY val, caller;
|
||||
--connection master
|
||||
--source include/ps_in_func-mixture-01-drop.inc
|
||||
--sync_slave_with_master
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40227 Replication breaks when a function performs multiple INSERT statements with binlog_format=MIXED or STATEMENT
|
||||
--echo #
|
||||
|
||||
--connection master
|
||||
|
||||
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, v INT);
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f() RETURNS INT
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (100)';
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1(v) VALUES (200)';
|
||||
RETURN 0;
|
||||
END$$
|
||||
CREATE PROCEDURE p() BEGIN DECLARE n INT DEFAULT f(); END$$
|
||||
DELIMITER ;$$
|
||||
CALL p();
|
||||
SELECT id, v FROM t1 ORDER BY id;
|
||||
--sync_slave_with_master
|
||||
--connection slave
|
||||
SELECT id, v FROM t1 ORDER BY id;
|
||||
--connection master
|
||||
DROP PROCEDURE p;
|
||||
DROP FUNCTION f;
|
||||
DROP TABLE t1;
|
||||
--sync_slave_with_master
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
|
||||
--source include/rpl_end.inc
|
||||
59
mysql-test/suite/rpl/t/rpl_ps_in_func_mix_or_row_innodb.test
Normal file
59
mysql-test/suite/rpl/t/rpl_ps_in_func_mix_or_row_innodb.test
Normal file
@@ -0,0 +1,59 @@
|
||||
--source include/have_innodb.inc
|
||||
--source include/have_binlog_format_mixed_or_row.inc
|
||||
--source include/master-slave.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-40318 MIXED binlog format does not switch an unsafe-for-SBR dynamic SQL (via PS) to ROW logging, causing replica divergence
|
||||
--echo #
|
||||
|
||||
--connection master
|
||||
CREATE TABLE td (id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
ts_now DATETIME(6), rnd DOUBLE, uu CHAR(36), usr_var INT) ENGINE=InnoDB;
|
||||
|
||||
DELIMITER $$;
|
||||
CREATE FUNCTION f_nd(seed INT) RETURNS INT
|
||||
BEGIN
|
||||
SET @uv = seed * 7;
|
||||
EXECUTE IMMEDIATE 'INSERT INTO td (ts_now, rnd, uu, usr_var) '
|
||||
'VALUES (NOW(6), RAND(?), UUID(), @uv)'
|
||||
USING seed;
|
||||
RETURN seed;
|
||||
END $$
|
||||
CREATE PROCEDURE p_nd(seed INT)
|
||||
BEGIN
|
||||
DECLARE r INT DEFAULT f_nd(seed);
|
||||
END $$
|
||||
DELIMITER ;$$
|
||||
|
||||
CALL p_nd(1);
|
||||
CALL p_nd(2);
|
||||
CALL p_nd(3);
|
||||
CALL p_nd(4);
|
||||
CALL p_nd(5);
|
||||
|
||||
EXECUTE IMMEDIATE 'INSERT INTO td (ts_now, rnd, uu, usr_var) '
|
||||
'VALUES (NOW(6), RAND(?), UUID(), @uv)'
|
||||
USING 10;
|
||||
|
||||
SELECT COUNT(*) FROM td;
|
||||
--sync_slave_with_master
|
||||
SELECT COUNT(*) FROM td;
|
||||
|
||||
# Compare records on the master and the slave
|
||||
--exec $MYSQL_DUMP --compact --order-by-primary --skip-extended-insert --no-create-info test >$MYSQLTEST_VARDIR/tmp/MDEV-40318-master.sql
|
||||
--exec $MYSQL_DUMP_SLAVE --compact --order-by-primary --skip-extended-insert --no-create-info test >$MYSQLTEST_VARDIR/tmp/MDEV-40318-slave.sql
|
||||
--diff_files $MYSQLTEST_VARDIR/tmp/MDEV-40318-master.sql $MYSQLTEST_VARDIR/tmp/MDEV-40318-slave.sql
|
||||
--remove_file $MYSQLTEST_VARDIR/tmp/MDEV-40318-master.sql
|
||||
--remove_file $MYSQLTEST_VARDIR/tmp/MDEV-40318-slave.sql
|
||||
|
||||
--connection master
|
||||
DROP PROCEDURE p_nd;
|
||||
DROP FUNCTION f_nd;
|
||||
DROP TABLE td;
|
||||
--sync_slave_with_master
|
||||
--connection master
|
||||
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
|
||||
--source include/rpl_end.inc
|
||||
@@ -1798,9 +1798,9 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
DBUG_ASSERT(thd->transaction->stmt.ha_list == NULL ||
|
||||
trans == &thd->transaction->stmt);
|
||||
|
||||
DBUG_ASSERT(!thd->in_sub_stmt);
|
||||
DBUG_ASSERT(!thd->in_sub_stmt_ps_unsafe());
|
||||
|
||||
if (thd->in_sub_stmt)
|
||||
if (thd->in_sub_stmt_ps_unsafe())
|
||||
{
|
||||
/*
|
||||
Since we don't support nested statement transactions in 5.0,
|
||||
@@ -2349,7 +2349,7 @@ int ha_rollback_trans(THD *thd, bool all)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (thd->in_sub_stmt)
|
||||
if (thd->in_sub_stmt_ps_unsafe())
|
||||
{
|
||||
DBUG_ASSERT(0);
|
||||
/*
|
||||
@@ -3270,8 +3270,8 @@ void commit_checkpoint_notify_ha(void *cookie)
|
||||
bool ha_rollback_to_savepoint_can_release_mdl(THD *thd)
|
||||
{
|
||||
Ha_trx_info *ha_info;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction->stmt :
|
||||
&thd->transaction->all);
|
||||
THD_TRANS *trans= thd->in_sub_stmt_ps_unsafe() ?
|
||||
&thd->transaction->stmt : &thd->transaction->all;
|
||||
|
||||
DBUG_ENTER("ha_rollback_to_savepoint_can_release_mdl");
|
||||
|
||||
@@ -3295,8 +3295,8 @@ bool ha_rollback_to_savepoint_can_release_mdl(THD *thd)
|
||||
int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
{
|
||||
int error=0;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction->stmt :
|
||||
&thd->transaction->all);
|
||||
THD_TRANS *trans= thd->in_sub_stmt_ps_unsafe() ?
|
||||
&thd->transaction->stmt : &thd->transaction->all;
|
||||
Ha_trx_info *ha_info, *ha_info_next;
|
||||
|
||||
DBUG_ENTER("ha_rollback_to_savepoint");
|
||||
@@ -3333,11 +3333,11 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
if (WSREP(thd) && ht->flags & HTON_WSREP_REPLICATION)
|
||||
{
|
||||
WSREP_DEBUG("ha_rollback_to_savepoint: run before_rollbackha_rollback_trans hook");
|
||||
(void) wsrep_before_rollback(thd, !thd->in_sub_stmt);
|
||||
(void) wsrep_before_rollback(thd, !thd->in_sub_stmt_ps_unsafe());
|
||||
|
||||
}
|
||||
#endif // WITH_WSREP
|
||||
if ((err= ht->rollback(thd, !thd->in_sub_stmt)))
|
||||
if ((err= ht->rollback(thd, !thd->in_sub_stmt_ps_unsafe())))
|
||||
{ // cannot happen
|
||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||
error=1;
|
||||
@@ -3346,7 +3346,7 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
if (WSREP(thd) && ht->flags & HTON_WSREP_REPLICATION)
|
||||
{
|
||||
WSREP_DEBUG("ha_rollback_to_savepoint: run after_rollback hook");
|
||||
(void) wsrep_after_rollback(thd, !thd->in_sub_stmt);
|
||||
(void) wsrep_after_rollback(thd, !thd->in_sub_stmt_ps_unsafe());
|
||||
}
|
||||
#endif // WITH_WSREP
|
||||
status_var_increment(thd->status_var.ha_rollback_count);
|
||||
@@ -3380,8 +3380,8 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
}
|
||||
#endif /* WITH_WSREP */
|
||||
int error=0;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction->stmt :
|
||||
&thd->transaction->all);
|
||||
THD_TRANS *trans= thd->in_sub_stmt_ps_unsafe() ?
|
||||
&thd->transaction->stmt : &thd->transaction->all;
|
||||
Ha_trx_info *ha_info= trans->ha_list;
|
||||
DBUG_ENTER("ha_savepoint");
|
||||
|
||||
@@ -8133,7 +8133,7 @@ static int wsrep_after_row(THD *thd)
|
||||
rollback current statement transaction. See comment in ha_commit_trans()
|
||||
call for more information.
|
||||
*/
|
||||
if (!thd->in_sub_stmt)
|
||||
if (!thd->in_sub_stmt_ps_unsafe())
|
||||
trans_rollback_stmt(thd) || trans_rollback(thd);
|
||||
my_message(ER_ERROR_DURING_COMMIT, "wsrep_max_ws_rows exceeded", MYF(0));
|
||||
DBUG_RETURN(ER_ERROR_DURING_COMMIT);
|
||||
|
||||
74
sql/item.cc
74
sql/item.cc
@@ -3089,12 +3089,76 @@ Item_sp::execute_impl(THD *thd, Item **args, uint arg_count)
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
If the stored function is in a safe PS context, such as in the right
|
||||
hand of an assignment:
|
||||
SET spvar= f1();
|
||||
then pass SUB_STMT_PS_SAFE_CONTEXT into reset_sub_statement_state(),
|
||||
to allow execution of prepared statements inside the function.
|
||||
|
||||
Note, SUB_STMT_PS_SAFE_CONTEXT does more than just allowing PS.
|
||||
It also switches the statements inside the function to the
|
||||
procedure-alike execution mode: they are not pre-locked by the caller,
|
||||
they open and lock their tables themselves and they commit the statement
|
||||
transaction at the end.
|
||||
This is only correct when the current statement really did not pre-lock
|
||||
anything, that is when some routine reachable from the current statement
|
||||
contains dynamic SQL, so LEX::contains_dynamic_sql() is set and
|
||||
Query_tables_list::requires_prelocking() returned false.
|
||||
|
||||
If the current statement is pre-locked (the usual case: no dynamic SQL
|
||||
anywhere in the call stack), then the tables used by the function were
|
||||
opened and locked by the caller, and the caller is in the middle of its
|
||||
own statement. We don't pass SUB_STMT_PS_SAFE_CONTEXT in this case.
|
||||
See the test covering MDEV-40914 in main/ps_in_func*.test.
|
||||
*/
|
||||
uint sub_stmt_safe_context=
|
||||
!thd->in_sub_stmt_ps_unsafe() &&
|
||||
thd->lex->contains_dynamic_sql() /*PS reachable: no prelocking*/ &&
|
||||
is_in_ps_safe_context() ?
|
||||
SUB_STMT_PS_SAFE_CONTEXT : 0;
|
||||
|
||||
/*
|
||||
A function with dynamic SQL (directly, or in the callees) is not pre-locked,
|
||||
see Query_tables_list::requires_prelocking(). So the calling statement did
|
||||
not enter prelocked mode and thd->lock belongs to the caller alone.
|
||||
|
||||
Outside of a safe PS context a dynamic statement inside the function is
|
||||
rejected by THD::error_if_in_sub_stmt_ps_unsafe(), but the rejected
|
||||
statement still goes through the usual end-of-statement cleanup:
|
||||
close_thread_tables() does not see thd->locked_tables_mode, takes the top
|
||||
level unlock path and releases the caller's lock while the caller is
|
||||
still reading from its tables:
|
||||
SELECT * FROM t1 WHERE a=f1(); -- f1() contains EXECUTE IMMEDIATE
|
||||
Raise the same error the inner statement would raise, but before the
|
||||
function is entered and any table state is touched.
|
||||
|
||||
Note, the condition tests the statement, not this particular function,
|
||||
so a function called next to a dynamic one is rejected as well:
|
||||
SELECT * FROM t1 WHERE a=f1() AND b=f2(); -- only f1() has a PS, yet
|
||||
-- f2() is rejected too
|
||||
|
||||
Callers without tables of their own (SELECT f1(), or an assignment right
|
||||
hand side) have no lock to lose and are not affected. Under LOCK TABLES
|
||||
the caller does hold a lock, but locked_tables_mode is set, so
|
||||
close_thread_tables() leaves it alone. Note, thd->lock is non-NULL even
|
||||
for a statement without tables, hence the table_count check.
|
||||
*/
|
||||
if (!sub_stmt_safe_context && !thd->locked_tables_mode &&
|
||||
thd->lock && thd->lock->table_count &&
|
||||
thd->lex->error_if_contains_dynamic_sql())
|
||||
{
|
||||
thd->security_ctx= save_security_ctx;
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
Disable the binlogging if this is not a SELECT statement. If this is a
|
||||
SELECT, leave binlogging on, so execute_function() code writes the
|
||||
function call into binlog.
|
||||
*/
|
||||
thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION);
|
||||
thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION |
|
||||
sub_stmt_safe_context);
|
||||
|
||||
/*
|
||||
If this function is an aggregate function, we want to initialise the
|
||||
@@ -10889,10 +10953,16 @@ bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it)
|
||||
|
||||
int err_code= item->save_in_field(field, 0);
|
||||
|
||||
/*
|
||||
A routine with dynamic SQL is rejected before the trigger is invoked
|
||||
(open_and_process_routine(), Item_sp::execute_impl()), so the evaluation
|
||||
above cannot have closed the caller's tables: field->table is still valid.
|
||||
*/
|
||||
DBUG_ASSERT(thd->open_tables != nullptr);
|
||||
field->table->copy_blobs= copy_blobs_saved;
|
||||
field->set_has_explicit_value();
|
||||
|
||||
return err_code < 0;
|
||||
return err_code < 0 || thd->is_error()/*e.g. PS command failed*/;
|
||||
}
|
||||
|
||||
|
||||
|
||||
26
sql/item.h
26
sql/item.h
@@ -735,7 +735,9 @@ enum class item_base_t : item_flags_t
|
||||
// Must be evaluated using val_bool().
|
||||
// Note, not all items used as a search
|
||||
// condition set this flag yet.
|
||||
AT_TOP_LEVEL= (1<<6) // At top (AND) level of item tree
|
||||
AT_TOP_LEVEL= (1<<6), // At top (AND) level of item tree
|
||||
IS_IN_PS_SAFE_CONTEXT= (1<<7) // Appears bare in PS safe context,
|
||||
// such as assignment right hand.
|
||||
};
|
||||
|
||||
|
||||
@@ -1345,6 +1347,19 @@ public:
|
||||
{
|
||||
set_maybe_null(maybe_null_arg);
|
||||
}
|
||||
|
||||
/*
|
||||
Mark that this item is evaluated in a context where a stored function
|
||||
is allowed to execute prepared statements, e.g. an assignment right
|
||||
hand side. The flag is only ever consulted by Item_func_sp, see
|
||||
Item_sp::execute_impl(). A container item overrides this to opt its
|
||||
children in, see Item_row::set_in_ps_safe_context().
|
||||
*/
|
||||
virtual void set_in_ps_safe_context()
|
||||
{
|
||||
base_flags|= item_base_t::IS_IN_PS_SAFE_CONTEXT;
|
||||
}
|
||||
|
||||
/*
|
||||
Mark the item that it is a top level item, or part of a top level AND item,
|
||||
for WHERE and ON clauses:
|
||||
@@ -2993,6 +3008,11 @@ protected:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void set_in_ps_safe_context_args()
|
||||
{
|
||||
for (uint i= 0; i < arg_count; i++)
|
||||
args[i]->set_in_ps_safe_context();
|
||||
}
|
||||
public:
|
||||
Item_args(void)
|
||||
:args(NULL), arg_count(0)
|
||||
@@ -6236,6 +6256,10 @@ public:
|
||||
delete sp_result_field;
|
||||
sp_result_field= NULL;
|
||||
}
|
||||
virtual bool is_in_ps_safe_context() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LEX_CSTRING func_name_cstring(THD *thd, bool is_package_function) const;
|
||||
void cleanup();
|
||||
bool sp_check_access(THD *thd);
|
||||
|
||||
@@ -4215,6 +4215,10 @@ public:
|
||||
|
||||
virtual ~Item_func_sp() = default;
|
||||
|
||||
bool is_in_ps_safe_context() const override
|
||||
{
|
||||
return bool(base_flags & item_base_t::IS_IN_PS_SAFE_CONTEXT);
|
||||
}
|
||||
void update_used_tables() override;
|
||||
|
||||
void cleanup() override;
|
||||
|
||||
@@ -96,6 +96,12 @@ public:
|
||||
bool null_inside() override { return with_null; };
|
||||
void bring_value() override;
|
||||
|
||||
void set_in_ps_safe_context() override
|
||||
{
|
||||
Item::set_in_ps_safe_context();
|
||||
Item_args::set_in_ps_safe_context_args();
|
||||
}
|
||||
|
||||
Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond)
|
||||
override
|
||||
{
|
||||
|
||||
@@ -2976,8 +2976,13 @@ int Sroutine_hash_entry::sp_cache_routine(THD *thd, sp_head **sp) const
|
||||
CALL. The assert below should be unambiguous: the first element
|
||||
in sroutines_list has an MDL lock unless it's a top-level call, or a
|
||||
trigger, but triggers can't occur here.
|
||||
A statement which reaches a routine containing dynamic SQL is not
|
||||
pre-locked and takes no MDL on any of its routines at all, so none of
|
||||
the elements has a ticket in that case, see open_and_process_routine().
|
||||
*/
|
||||
DBUG_ASSERT(mdl_request.ticket || this == thd->lex->sroutines_list.first);
|
||||
DBUG_ASSERT(mdl_request.ticket ||
|
||||
this == thd->lex->sroutines_list.first ||
|
||||
thd->lex->contains_dynamic_sql());
|
||||
|
||||
return m_handler->sp_cache_routine(thd, &name, sp);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,10 @@ bool Item_splocal_row_field::append_for_log(THD *thd, String *str)
|
||||
- sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might
|
||||
result in multiple result sets being sent back.
|
||||
- sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE,
|
||||
EXECUTE, DEALLOCATE.
|
||||
EXECUTE, DEALLOCATE, EXECUTE IMMEDIATE.
|
||||
- sp_head::MULTI_RESULTS_DYNAMIC: added if 'cmd' is EXECUTE or
|
||||
EXECUTE IMMEDIATE, i.e. a command that might result in a result set
|
||||
being sent back, depending on the prepared statement being executed.
|
||||
*/
|
||||
|
||||
uint
|
||||
@@ -185,7 +188,13 @@ sp_get_flags_for_command(LEX *lex)
|
||||
|
||||
switch (lex->sql_command) {
|
||||
case SQLCOM_SELECT:
|
||||
if (lex->result && !lex->analyze_stmt)
|
||||
/*
|
||||
Note, during prepared statement execution lex->result
|
||||
is already set even for a simple SELECT query (without INTO).
|
||||
*/
|
||||
if (lex->result &&
|
||||
!dynamic_cast<const select_send*>(lex->result) &&
|
||||
!lex->analyze_stmt)
|
||||
{
|
||||
flags= 0; /* This is a SELECT with INTO clause */
|
||||
break;
|
||||
@@ -250,13 +259,17 @@ sp_get_flags_for_command(LEX *lex)
|
||||
break;
|
||||
/*
|
||||
EXECUTE statement may return a result set, but doesn't have to.
|
||||
We can't, however, know it in advance, and therefore must add
|
||||
this statement here. This is ok, as is equivalent to a result-set
|
||||
statement within an IF condition.
|
||||
We can't, however, know it in advance. So let's mark it with
|
||||
MULTI_RESULTS_DYNAMIC, so a CALL of a routine using EXECUTE
|
||||
requires the client to support CLIENT_MULTI_RESULTS.
|
||||
Note, MULTI_RESULTS is not used here on purpose: it would make
|
||||
dynamic SQL not allowed in stored *FUNCTIONs* at all. Returning
|
||||
a result set from a function is instead caught at run time,
|
||||
in Prepared_statement::execute_loop().
|
||||
*/
|
||||
case SQLCOM_EXECUTE:
|
||||
case SQLCOM_EXECUTE_IMMEDIATE:
|
||||
flags= sp_head::MULTI_RESULTS | sp_head::CONTAINS_DYNAMIC_SQL;
|
||||
flags= sp_head::MULTI_RESULTS_DYNAMIC | sp_head::CONTAINS_DYNAMIC_SQL;
|
||||
break;
|
||||
case SQLCOM_PREPARE:
|
||||
case SQLCOM_DEALLOCATE_PREPARE:
|
||||
@@ -1817,6 +1830,9 @@ sp_head::execute_trigger(THD *thd,
|
||||
}
|
||||
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
||||
|
||||
if (thd->lex->error_if_contains_dynamic_sql())
|
||||
DBUG_RETURN(true);
|
||||
|
||||
/*
|
||||
Prepare arena and memroot for objects which lifetime is whole
|
||||
duration of trigger call (sp_rcontext, it's tables and items,
|
||||
@@ -1885,6 +1901,17 @@ bool sp_package::instantiate_if_needed(THD *thd)
|
||||
*/
|
||||
if (thd->in_sub_stmt)
|
||||
{
|
||||
/*
|
||||
Unlike a CALL of a procedure with dynamic SQL, which is allowed from
|
||||
a stored function (see Sql_cmd_call::execute()), the package
|
||||
initialization section is not executed in a PS safe context:
|
||||
it is implicitly invoked on the first reference to any package routine.
|
||||
So dynamic SQL in the initialization section is rejected in a stored
|
||||
function context as well.
|
||||
*/
|
||||
if (error_if_contains_dynamic_sql())
|
||||
goto err;
|
||||
|
||||
const char *where= (thd->in_sub_stmt & SUB_STMT_TRIGGER ?
|
||||
"trigger" : "function");
|
||||
if (is_not_allowed_in_function(where))
|
||||
@@ -2017,12 +2044,36 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
|
||||
}
|
||||
(*func_ctx)->set_inited_param_count(arg_no);
|
||||
|
||||
/*
|
||||
Item_sp::execute_impl() earlier decided on the PS safe context, before the
|
||||
arguments were evaluated. Now evaluating an argument can leave the caller
|
||||
in the middle of its own statement:
|
||||
SET spvar= f1((SELECT a FROM t1)); -- t1 is open and locked
|
||||
SET spvar= f1(g1()); -- g1() was PS unsafe, so it left
|
||||
-- its statement transaction open
|
||||
Reject dynamic SQL here, before the function is entered, like
|
||||
Item_sp::execute_impl() does outside of an assignment right hand:
|
||||
rejecting it from inside the function would run the cleanup of the
|
||||
rejected statement on the state of the caller.
|
||||
*/
|
||||
if ((thd->in_sub_stmt & SUB_STMT_PS_SAFE_CONTEXT) &&
|
||||
thd->stmt_state_conflicts_with_ps() &&
|
||||
thd->lex->error_if_contains_dynamic_sql())
|
||||
{
|
||||
err_status= TRUE;
|
||||
goto err_with_cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
If row-based binlogging, we don't need to binlog the function's call, let
|
||||
each substatement be binlogged its way.
|
||||
If we're in an SP call stack containing a function with dynamic SQL,
|
||||
do per-statement logging.
|
||||
*/
|
||||
need_binlog_call= mysql_bin_log.is_open() &&
|
||||
(thd->variables.option_bits & OPTION_BIN_LOG) &&
|
||||
!(thd->variables.option_bits &
|
||||
OPTION_BIN_LOG_IN_FUNC) &&
|
||||
!thd->is_current_stmt_binlog_format_row();
|
||||
|
||||
/*
|
||||
|
||||
@@ -182,7 +182,16 @@ public:
|
||||
HAS_COLUMN_TYPE_REFS= 8192,
|
||||
/* Set if has FETCH GROUP NEXT ROW instr. Used to ensure that only
|
||||
functions with AGGREGATE keyword use the instr. */
|
||||
HAS_AGGREGATE_INSTR= 16384
|
||||
HAS_AGGREGATE_INSTR= 16384,
|
||||
/*
|
||||
Is set if a routine has EXECUTE or EXECUTE IMMEDIATE, which
|
||||
may (but does not have to) return a result set to the client.
|
||||
Unlike MULTI_RESULTS, this flag does not disqualify a routine
|
||||
from being used as a stored function: a dynamic statement returning
|
||||
a result set in a function context is detected at run time
|
||||
in Prepared_statement::execute_loop().
|
||||
*/
|
||||
MULTI_RESULTS_DYNAMIC= 32768
|
||||
};
|
||||
|
||||
sp_package *m_parent;
|
||||
@@ -656,6 +665,9 @@ public:
|
||||
bool modifies_data() const
|
||||
{ return m_flags & MODIFIES_DATA; }
|
||||
|
||||
bool contains_dynamic_sql() const
|
||||
{ return m_flags & CONTAINS_DYNAMIC_SQL; }
|
||||
|
||||
inline uint instructions()
|
||||
{ return (uint)m_instr.elements; }
|
||||
|
||||
@@ -957,12 +969,14 @@ public:
|
||||
Check if this stored routine contains statements disallowed
|
||||
in a stored function or trigger, and set an appropriate error message
|
||||
if this is the case.
|
||||
|
||||
Note, CONTAINS_DYNAMIC_SQL is not tested here: whether dynamic SQL is
|
||||
allowed depends on the context rather than on the routine itself.
|
||||
It is checked later at run time rather than at parse time.
|
||||
*/
|
||||
bool is_not_allowed_in_function(const char *where)
|
||||
{
|
||||
if (m_flags & CONTAINS_DYNAMIC_SQL)
|
||||
my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "Dynamic SQL");
|
||||
else if (m_flags & MULTI_RESULTS)
|
||||
if (m_flags & MULTI_RESULTS)
|
||||
my_error(ER_SP_NO_RETSET, MYF(0), where);
|
||||
else if (m_flags & HAS_SET_AUTOCOMMIT_STMT)
|
||||
my_error(ER_SP_CANT_SET_AUTOCOMMIT, MYF(0));
|
||||
@@ -974,11 +988,19 @@ public:
|
||||
my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH");
|
||||
|
||||
return MY_TEST(m_flags &
|
||||
(CONTAINS_DYNAMIC_SQL | MULTI_RESULTS |
|
||||
(MULTI_RESULTS |
|
||||
HAS_SET_AUTOCOMMIT_STMT | HAS_COMMIT_OR_ROLLBACK |
|
||||
HAS_SQLCOM_RESET | HAS_SQLCOM_FLUSH));
|
||||
}
|
||||
|
||||
bool error_if_contains_dynamic_sql() const
|
||||
{
|
||||
if (!(m_flags & CONTAINS_DYNAMIC_SQL))
|
||||
return false;
|
||||
raise_error_dynamic_sql_not_allowed();
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
int show_routine_code(THD *thd);
|
||||
#endif
|
||||
|
||||
@@ -429,7 +429,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
{
|
||||
m_lex->unit.cleanup();
|
||||
/* Here we also commit or rollback the current statement. */
|
||||
if (! thd->in_sub_stmt)
|
||||
if (!thd->in_sub_stmt_ps_unsafe())
|
||||
{
|
||||
thd->get_stmt_da()->set_overwrite_status(true);
|
||||
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
|
||||
@@ -438,13 +438,38 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
close_thread_tables(thd);
|
||||
thd_proc_info(thd, 0);
|
||||
|
||||
if (! thd->in_sub_stmt)
|
||||
/*
|
||||
The same condition as for the statement transaction above: a statement
|
||||
inside a stored function entered in a safe PS context ends like a
|
||||
statement inside a stored procedure does. Testing !in_sub_stmt here
|
||||
would end the statement only half way: its statement transaction would
|
||||
be committed, yet its metadata locks would not be released but
|
||||
accumulate for the rest of the function.
|
||||
*/
|
||||
if (!thd->in_sub_stmt_ps_unsafe())
|
||||
{
|
||||
if (thd->transaction_rollback_request)
|
||||
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
|
||||
{
|
||||
trans_rollback_implicit(thd);
|
||||
thd->release_transactional_locks();
|
||||
}
|
||||
else if (thd->transaction_rollback_request)
|
||||
{
|
||||
/*
|
||||
A rollback was requested by an engine (e.g. on a deadlock) while
|
||||
we are inside a stored function. Leave the request set: the
|
||||
rollback is done by the statement which called the function, in
|
||||
the finish: block of mysql_execute_command().
|
||||
|
||||
It cannot be done here. THD::reset_sub_statement_state() saved
|
||||
THD::variables::option_bits and restore_sub_statement_state()
|
||||
restores them, so clearing OPTION_BEGIN in trans_rollback_implicit()
|
||||
would be undone on the way out of the function. The caller would
|
||||
continue with OPTION_BEGIN set and no transaction, and from then on
|
||||
it would keep the metadata locks of every statement it runs.
|
||||
*/
|
||||
thd->mdl_context.release_statement_locks();
|
||||
}
|
||||
else if (! thd->in_multi_stmt_transaction_mode())
|
||||
thd->release_transactional_locks();
|
||||
else
|
||||
|
||||
230
sql/sql_base.cc
230
sql/sql_base.cc
@@ -2688,7 +2688,7 @@ int
|
||||
Locked_tables_list::unlock_locked_tables(THD *thd)
|
||||
{
|
||||
int error;
|
||||
DBUG_ASSERT(!thd->in_sub_stmt &&
|
||||
DBUG_ASSERT(!thd->in_sub_stmt_ps_unsafe() &&
|
||||
!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
|
||||
/*
|
||||
Sic: we must be careful to not close open tables if
|
||||
@@ -3921,7 +3921,41 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
|
||||
case MDL_key::FUNCTION:
|
||||
case MDL_key::PROCEDURE:
|
||||
{
|
||||
sp_head *sp;
|
||||
sp_head *sp= NULL;
|
||||
if (thd->lex->contains_dynamic_sql())
|
||||
{
|
||||
/*
|
||||
Some routine in the prelocking set of this statement contains
|
||||
dynamic SQL, so the statement is executed without prelocking
|
||||
(see Query_tables_list::requires_prelocking()).
|
||||
Do not put MDL locks on the routines and do not collect their
|
||||
tables: every routine opens (and locks) its tables itself, on a
|
||||
per-statement basis, at its execution time.
|
||||
The routines are still cached, like it's done for a top level CALL,
|
||||
see the top level CALL branch at the end of this "case".
|
||||
Caching is not optional here: Item_func_sp::fix_fields() resolves
|
||||
the routine with sp_find_routine(cache_only=true), so a routine
|
||||
which is not in the sp cache by now is reported as non existing:
|
||||
CREATE PROCEDURE p1()
|
||||
BEGIN
|
||||
DECLARE r ROW(a INT, b INT);
|
||||
SET r= ROW(f1(), f2()); -- f1() contains a prepared statement
|
||||
END; -- f2() must be cached too
|
||||
*/
|
||||
if (rt->sp_cache_routine(thd, &sp))
|
||||
DBUG_RETURN(TRUE);
|
||||
/*
|
||||
Remember the version of the routine in the parse tree, like the
|
||||
non-dynamic path below does, with the same top level CALL
|
||||
exception.
|
||||
*/
|
||||
if ((rt != prelocking_ctx->sroutines_list.first ||
|
||||
mdl_type != MDL_key::PROCEDURE) &&
|
||||
check_and_update_routine_version(thd, rt, sp))
|
||||
DBUG_RETURN(TRUE);
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
Try to get MDL lock on the routine.
|
||||
Note that we do not take locks on top-level CALLs as this can
|
||||
@@ -3943,10 +3977,41 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
|
||||
if (rt->sp_cache_routine(thd, &sp))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/* Remember the version of the routine in the parse tree. */
|
||||
/*
|
||||
Remember the version of the routine in the parse tree.
|
||||
This is done before the dynamic SQL test below: a routine with
|
||||
dynamic SQL is not collected into the prelocking set, but its
|
||||
version still has to be recorded, so that a prepared statement or
|
||||
a stored routine instruction which was prepared against an older
|
||||
version of the routine is re-prepared rather than executed from
|
||||
a parse tree which no longer matches the routine.
|
||||
*/
|
||||
if (check_and_update_routine_version(thd, rt, sp))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if (sp && sp->contains_dynamic_sql())
|
||||
{
|
||||
/*
|
||||
The routine contains dynamic SQL. It can be:
|
||||
- a routine used in a statement, e.g.
|
||||
SET spvar= f1(); -- f1() contains a prepared statement
|
||||
- or a routine reachable from another routine, e.g.
|
||||
SET spvar= f1(); -- f1() calls p2()
|
||||
-- p2() contains a prepared statement
|
||||
In both cases the tables used by the dynamic statements are
|
||||
not known in advance, so the statement cannot be prelocked.
|
||||
Mark the currently executed statement's LEX as containing
|
||||
dynamic SQL: the routines will open (and lock) their tables
|
||||
themselves, on a per-statement basis, like a PROCEDURE does.
|
||||
Note, the prelocking algorithm could already add some tables
|
||||
to the table list (e.g. the tables of the routine which called
|
||||
the routine being processed). They are removed by the caller,
|
||||
see open_tables().
|
||||
*/
|
||||
thd->lex->set_contains_dynamic_sql();
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
/* 'sp' is NULL when there is no such routine. */
|
||||
if (sp)
|
||||
{
|
||||
@@ -4017,6 +4082,22 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
|
||||
DML we always use triggers together with their tables, and thus don't
|
||||
need to take separate metadata locks on them.
|
||||
*/
|
||||
|
||||
/*
|
||||
A trigger requires prelocking, while a statement which reaches a
|
||||
routine containing dynamic SQL is executed without prelocking
|
||||
(see Query_tables_list::requires_prelocking()), so the two cannot be
|
||||
combined:
|
||||
INSERT INTO t1 VALUES (1); -- t1 has a trigger, and the statement
|
||||
-- also calls f1(), which contains a
|
||||
-- prepared statement
|
||||
This rejects the trigger entries which are processed after the
|
||||
dynamic SQL was detected. The entries processed before it are
|
||||
rejected by the sroutines_list_contains_trigger() test in
|
||||
open_tables(), once the whole prelocking set has been walked.
|
||||
*/
|
||||
if (thd->lex->error_if_contains_dynamic_sql())
|
||||
DBUG_RETURN(TRUE);
|
||||
break;
|
||||
default:
|
||||
/* Impossible type value. */
|
||||
@@ -4025,6 +4106,25 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if the prelocking set of a statement contains a trigger.
|
||||
*/
|
||||
|
||||
static
|
||||
bool sroutines_list_contains_trigger(const Query_tables_list *prelocking_ctx)
|
||||
{
|
||||
for (Sroutine_hash_entry *rt=
|
||||
(Sroutine_hash_entry*) prelocking_ctx->sroutines_list.first;
|
||||
rt; rt= rt->next)
|
||||
{
|
||||
if (rt->mdl_request.key.mdl_namespace() == MDL_key::TRIGGER)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
If we are not already in prelocked mode and extended table list is not
|
||||
yet built we might have to build the prelocking set for this statement.
|
||||
@@ -4636,6 +4736,37 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Close everything open_tables() has opened so far and rewind to the
|
||||
beginning of both the table list and the prelocking set, so that the
|
||||
caller can restart from its "restart" label.
|
||||
|
||||
@param recover Pass true when rewinding because opening a table failed:
|
||||
the failure has to be recovered from (e.g. by waiting for
|
||||
a conflicting metadata lock) before the tables are
|
||||
reopened. Pass false when the rewind is a deliberate
|
||||
decision of the caller.
|
||||
|
||||
@retval true Error, reported. The caller must not restart.
|
||||
@retval false Success, the caller can restart.
|
||||
*/
|
||||
|
||||
static bool rewind_to_restart(THD *thd, TABLE_LIST **start,
|
||||
Open_table_context *ot_ctx,
|
||||
Sroutine_hash_entry ***sroutine_to_open,
|
||||
bool recover)
|
||||
{
|
||||
close_tables_for_reopen(thd, start, ot_ctx->start_of_statement_svp(), true);
|
||||
if (recover && ot_ctx->recover_from_failed_open())
|
||||
return true;
|
||||
/* Re-open temporary tables after close_tables_for_reopen(). */
|
||||
if (thd->open_temporary_tables(*start))
|
||||
return true;
|
||||
*sroutine_to_open= &thd->lex->sroutines_list.first;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Open all tables in list
|
||||
|
||||
@@ -4685,6 +4816,13 @@ bool open_tables(THD *thd, const DDL_options_st &options,
|
||||
bool error= FALSE;
|
||||
bool some_routine_modifies_data= FALSE;
|
||||
bool has_prelocking_list;
|
||||
/*
|
||||
Set to true after the prelocking set has been thrown away because
|
||||
a routine containing dynamic SQL was found. Used to make sure this
|
||||
can happen only once per statement, see the comment at the
|
||||
LEX::contains_dynamic_sql() check in the routine loop below.
|
||||
*/
|
||||
bool dynamic_sql_prelocking_set_dropped= false;
|
||||
DBUG_ENTER("open_tables");
|
||||
|
||||
/* Data access in XA transaction is only allowed when it is active. */
|
||||
@@ -4879,18 +5017,11 @@ restart:
|
||||
/* F.ex. deadlock happened */
|
||||
if (ot_ctx.can_recover_from_failed_open())
|
||||
{
|
||||
close_tables_for_reopen(thd, start,
|
||||
ot_ctx.start_of_statement_svp(),
|
||||
true);
|
||||
if (ot_ctx.recover_from_failed_open())
|
||||
goto error;
|
||||
|
||||
/* Re-open temporary tables after close_tables_for_reopen(). */
|
||||
if (thd->open_temporary_tables(*start))
|
||||
if (rewind_to_restart(thd, start, &ot_ctx, &sroutine_to_open,
|
||||
true))
|
||||
goto error;
|
||||
|
||||
error= FALSE;
|
||||
sroutine_to_open= &thd->lex->sroutines_list.first;
|
||||
std::this_thread::yield();
|
||||
goto restart;
|
||||
}
|
||||
@@ -4901,6 +5032,69 @@ restart:
|
||||
*/
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (thd->lex->contains_dynamic_sql() &&
|
||||
thd->lex->first_not_own_table())
|
||||
{
|
||||
if (dynamic_sql_prelocking_set_dropped)
|
||||
{
|
||||
/*
|
||||
We have already thrown the prelocking set away once, yet the
|
||||
new pass has collected tables into it again. This cannot be
|
||||
caused by the routines: once LEX::contains_dynamic_sql() is
|
||||
set, open_and_process_routine() does not collect their tables
|
||||
any more. So it is the statement itself which requires
|
||||
prelocking, e.g. because its tables have triggers or foreign
|
||||
keys with a cascading action:
|
||||
UPDATE t1 SET a=f1(); -- t1 is referenced by a foreign key
|
||||
-- f1() contains a prepared statement
|
||||
Such a statement cannot be executed without prelocking, while
|
||||
a routine containing dynamic SQL cannot be prelocked. Reject
|
||||
it with a diagnostic instead of restarting over and over.
|
||||
*/
|
||||
raise_error_dynamic_sql_not_allowed();
|
||||
error= TRUE;
|
||||
goto error;
|
||||
}
|
||||
/*
|
||||
We have just found out that the statement calls a routine
|
||||
containing dynamic SQL, so it cannot be prelocked
|
||||
(see Query_tables_list::requires_prelocking()), while the
|
||||
prelocking algorithm has already collected some tables into
|
||||
the table list. Throw the prelocking set away and start from
|
||||
the beginning. The new pass does not collect the tables of the
|
||||
routines any more, as LEX::contains_dynamic_sql() is now set.
|
||||
*/
|
||||
dynamic_sql_prelocking_set_dropped= true;
|
||||
if ((error= rewind_to_restart(thd, start, &ot_ctx,
|
||||
&sroutine_to_open, false)))
|
||||
goto error;
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The trigger test in open_and_process_routine() can be passed before
|
||||
LEX::contains_dynamic_sql() is set by a routine which is processed
|
||||
later in the loop above, e.g.:
|
||||
INSERT INTO t1 VALUES (1); -- t1 has a trigger which calls p1()
|
||||
-- p1() contains a prepared statement
|
||||
Test the trigger elements of the prelocking set again, now when the
|
||||
whole set has been processed. Doing it here, before the tables are
|
||||
locked and any row is written
|
||||
- makes a directly executed statement fail in the same way as a PS
|
||||
- makes transactional and non-transactional engines work the same way
|
||||
|
||||
E.g. a statement which ends up not firing the trigger, such as:
|
||||
INSERT INTO t1 SELECT a FROM t2; -- t2 is empty
|
||||
is rejected as well.
|
||||
*/
|
||||
if (thd->lex->contains_dynamic_sql() &&
|
||||
sroutines_list_contains_trigger(thd->lex))
|
||||
{
|
||||
raise_error_dynamic_sql_not_allowed();
|
||||
error= TRUE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if ((error= prelocking_strategy->handle_end(thd)))
|
||||
@@ -6064,6 +6258,18 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, uint flags)
|
||||
*/
|
||||
if (! thd->locked_tables_mode)
|
||||
{
|
||||
if ((thd->in_sub_stmt & SUB_STMT_TRIGGER) &&
|
||||
thd->lex->error_if_contains_dynamic_sql())
|
||||
{
|
||||
/*
|
||||
We're executing a trigger which calls a stored function
|
||||
with prepared statements:
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t1
|
||||
FOR EACH ROW SET NEW.b = sf_with_ps();
|
||||
This combination is not allowed.
|
||||
*/
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
|
||||
TABLE **start,**ptr;
|
||||
bool found_first_not_own= 0;
|
||||
|
||||
@@ -2503,6 +2503,13 @@ void THD::reset_globals()
|
||||
net.thd= 0;
|
||||
}
|
||||
|
||||
|
||||
bool THD::stmt_state_conflicts_with_ps() const
|
||||
{
|
||||
return lex->query_tables != nullptr || !transaction->stmt.is_empty();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Cleanup after query.
|
||||
|
||||
@@ -2542,13 +2549,33 @@ void THD::cleanup_after_query()
|
||||
Reset RAND_USED so that detection of calls to rand() will save random
|
||||
seeds if needed by the slave.
|
||||
|
||||
Do not reset RAND_USED if inside a stored function or trigger because
|
||||
only the call to these operations is logged. Thus only the calling
|
||||
The reset is needed for:
|
||||
(1) Standalone statements
|
||||
(2) Statements inside a stored procedure
|
||||
(3) Statements inside a stored function with PS, which is called
|
||||
in the assignment right hand, as it's logged per-statement.
|
||||
|
||||
Do not reset RAND_USED if inside a stored function which does not meet (3),
|
||||
or inside a trigger because
|
||||
only the call to these operations is logged. Thus only the calling
|
||||
statement needs to detect rand() calls made by its substatements. These
|
||||
substatements must not set RAND_USED to 0 because it would remove the
|
||||
detection of rand() by the calling statement.
|
||||
detection of rand() by the calling statement.
|
||||
|
||||
THD::in_sub_stmt_ps_unsafe() is false in exactly (1),(2),(3): either
|
||||
we are not in a substatement at all, or we are in a stored function
|
||||
entered in a safe PS context. Note, OPTION_BIN_LOG_IN_FUNC alone is not
|
||||
enough here. It stays set for the whole call stack below such a function,
|
||||
including a trigger fired by one of its statements:
|
||||
EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (1)' -- inside a function
|
||||
-- with dynamic SQL
|
||||
The INSERT is logged per statement and the replica fires t1's triggers
|
||||
itself, so a rand() call in a trigger body belongs to the INSERT. Clearing
|
||||
RAND_USED at the end of the trigger's statement would log the INSERT
|
||||
without the preceding Rand event, and the replica would use different
|
||||
random values.
|
||||
*/
|
||||
if (!in_sub_stmt) /* stored functions and triggers are a special case */
|
||||
if (!in_sub_stmt_ps_unsafe()/*(1),(2),(3)*/)
|
||||
{
|
||||
/* Forget those values, for next binlogger: */
|
||||
stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
|
||||
@@ -6311,7 +6338,33 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup,
|
||||
if ((!lex->requires_prelocking() || is_update_query(lex->sql_command)) &&
|
||||
!is_current_stmt_binlog_format_row())
|
||||
{
|
||||
variables.option_bits&= ~OPTION_BIN_LOG;
|
||||
if ((new_state & SUB_STMT_FUNCTION) &&
|
||||
(lex->contains_dynamic_sql() ||
|
||||
(variables.option_bits & OPTION_BIN_LOG_IN_FUNC)))
|
||||
{
|
||||
/*
|
||||
We are entering a stored function, and:
|
||||
- Some routine reachable from the calling statement contains
|
||||
dynamic SQL, or
|
||||
- We are already inside a function which had this flag set
|
||||
Do not unset the OPTION_BIN_LOG flag - let's do per-statement
|
||||
logging rather than `SELECT f1()` style logging.
|
||||
|
||||
Note, the new_state test is essential. A trigger is not logged
|
||||
per statement: the statement which fired it is logged instead, and
|
||||
the replica fires the trigger again when it applies that statement.
|
||||
Retaining OPTION_BIN_LOG here would log the trigger body as well,
|
||||
so the trigger's changes would be applied twice on the replica.
|
||||
It would also start a nested binlog event union: the union started
|
||||
for the trigger is still open, and a stored function called from
|
||||
the trigger body would call start_union_events() a second time,
|
||||
which either hits its "!do_union" assert or, in a release build,
|
||||
silently discards the trigger's accumulated union state.
|
||||
*/
|
||||
variables.option_bits|= OPTION_BIN_LOG_IN_FUNC;
|
||||
}
|
||||
else
|
||||
variables.option_bits&= ~OPTION_BIN_LOG;
|
||||
}
|
||||
|
||||
if ((backup->option_bits & OPTION_BIN_LOG) &&
|
||||
@@ -6321,7 +6374,12 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup,
|
||||
|
||||
/* Disable result sets */
|
||||
client_capabilities &= ~CLIENT_MULTI_RESULTS;
|
||||
in_sub_stmt|= new_state;
|
||||
/*
|
||||
If p1() calls f1() in a PS-safe way, but then f1() calls f2() in
|
||||
a PS-unsafe way then f2() cannot contain prepared statement.
|
||||
So we cannot inherit the SUB_STMT_PS_SAFE_CONTEXT flag from p1() to f1().
|
||||
*/
|
||||
in_sub_stmt= (in_sub_stmt & ~SUB_STMT_PS_SAFE_CONTEXT) | new_state;
|
||||
cuted_fields= 0;
|
||||
transaction->savepoints= 0;
|
||||
first_successful_insert_id_in_cur_stmt= 0;
|
||||
|
||||
@@ -2429,6 +2429,7 @@ public:
|
||||
#define SUB_STMT_FUNCTION 2
|
||||
#define SUB_STMT_STAT_TABLES 4
|
||||
#define SUB_STMT_BEFORE_TRIGGER 8
|
||||
#define SUB_STMT_PS_SAFE_CONTEXT 16 // e.g. func used in assignment right hand
|
||||
|
||||
class Sub_statement_state
|
||||
{
|
||||
@@ -3626,6 +3627,67 @@ public:
|
||||
|
||||
/* <> 0 if we are inside of trigger or stored function. */
|
||||
uint in_sub_stmt;
|
||||
|
||||
/*
|
||||
Check if THD::in_sub_stmt indicates that we're in a "real" substatement.
|
||||
A PS-safe function call is not considered as a real substatement - it
|
||||
behaves more like a procedure call in terms of table locking and
|
||||
transaction handling.
|
||||
|
||||
- Case 1a: in_sub_stmt==0
|
||||
The current evaluation is at the top level.
|
||||
We're not in a substatement. Return false.
|
||||
|
||||
- Case 1b: in_sub_stmt==0
|
||||
The current evaluation is inside a stored procedure
|
||||
and there are no stored functions or triggers in the call stack.
|
||||
I.e. the call stack consists only of stored procedure calls.
|
||||
We're not in a substatement. Return false.
|
||||
|
||||
- Case 2: in_sub_stmt==(SUB_STMT_FUNCTION | SUB_STMT_PS_SAFE_CONTEXT)
|
||||
The current evaluation is inside a stored function f1() and the function
|
||||
f1() is used in a safe context (e.g. in an assignment right hand).
|
||||
We're not in a "real" substatement. Return false.
|
||||
|
||||
- Case 3: in_sub_stmt is something else
|
||||
We're in a "real" substatement. Return true.
|
||||
*/
|
||||
bool in_sub_stmt_ps_unsafe() const
|
||||
{
|
||||
return in_sub_stmt &&
|
||||
(in_sub_stmt != (SUB_STMT_FUNCTION | SUB_STMT_PS_SAFE_CONTEXT));
|
||||
}
|
||||
|
||||
/*
|
||||
Reject a dynamic SQL statement (PREPARE, EXECUTE, DEALLOCATE PREPARE,
|
||||
EXECUTE IMMEDIATE) which is executed in a "real" substatement, i.e. in
|
||||
a trigger, or in a stored function called outside of a safe PS context.
|
||||
See in_sub_stmt_ps_unsafe() above.
|
||||
|
||||
Note, the error message argument is hardcoded to "Dynamic SQL", so this
|
||||
is not a general purpose substatement test. Call it only from the
|
||||
dynamic SQL statements; use in_sub_stmt_ps_unsafe() elsewhere.
|
||||
|
||||
@retval true the statement was rejected, an error was raised
|
||||
@retval false the statement can continue
|
||||
*/
|
||||
bool error_if_in_sub_stmt_ps_unsafe() const
|
||||
{
|
||||
if (!in_sub_stmt_ps_unsafe())
|
||||
return false;
|
||||
raise_error_dynamic_sql_not_allowed();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if the current statement is in the middle of something that a
|
||||
nested prepared statement would destroy: a non-empty LEX::query_tables
|
||||
(the whole list, not only the own tables), whose tables
|
||||
Prepared_statement::prepare() would close, or a statement transaction,
|
||||
which it assumes to be empty.
|
||||
*/
|
||||
bool stmt_state_conflicts_with_ps() const;
|
||||
|
||||
/* True when opt_userstat_running is set at start of query */
|
||||
bool userstat_running;
|
||||
/*
|
||||
@@ -5464,7 +5526,8 @@ public:
|
||||
tests fail and so force them to propagate the
|
||||
lex->binlog_row_based_if_mixed upwards to the caller.
|
||||
*/
|
||||
if ((wsrep_binlog_format(variables.binlog_format) == BINLOG_FORMAT_MIXED) && (in_sub_stmt == 0))
|
||||
if ((wsrep_binlog_format(variables.binlog_format) == BINLOG_FORMAT_MIXED) &&
|
||||
!in_sub_stmt_ps_unsafe())
|
||||
set_current_stmt_binlog_format_row();
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
@@ -24,6 +24,20 @@
|
||||
#include "mysql_com.h" /* MYSQL_ERRMSG_SIZE */
|
||||
#include "my_time.h" /* MYSQL_TIME */
|
||||
#include "decimal.h"
|
||||
#include <mysqld_error.h> /* ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG */
|
||||
|
||||
/*
|
||||
Raise ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG for a dynamic SQL statement
|
||||
(PREPARE, EXECUTE, DEALLOCATE PREPARE, EXECUTE IMMEDIATE) which cannot be
|
||||
executed in the current context. Shared by the error_if_*() helpers in THD,
|
||||
in Query_tables_list and in sp_head, and by the direct rejections in
|
||||
open_tables(), so that the wording is decided in one place.
|
||||
*/
|
||||
static inline void raise_error_dynamic_sql_not_allowed()
|
||||
{
|
||||
my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "Dynamic SQL");
|
||||
}
|
||||
|
||||
|
||||
class THD;
|
||||
class my_decimal;
|
||||
|
||||
@@ -4198,6 +4198,7 @@ void Query_tables_list::reset_query_tables_list(bool init)
|
||||
sroutines_list_own_elements= 0;
|
||||
binlog_stmt_flags= 0;
|
||||
stmt_accessed_table_flag= 0;
|
||||
m_contains_dynamic_sql= false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4723,6 +4724,18 @@ TABLE_LIST *LEX::unlink_first_table(bool *link_to_local)
|
||||
TABLE_LIST *first;
|
||||
if ((first= query_tables))
|
||||
{
|
||||
/*
|
||||
query_tables_own_last must never point into a table which is no
|
||||
longer in the list. The first table is an own table of the statement
|
||||
(e.g. the view being created in CREATE VIEW), so if it was the last
|
||||
own table, make the own table list empty. Leaving the pointer at
|
||||
&first->next_global would make a later chop_off_not_own_tables()
|
||||
remove wrong tables, or even reset query_tables to NULL. See
|
||||
link_first_table_back() for the symmetric adjustment.
|
||||
*/
|
||||
if (query_tables_own_last == &first->next_global)
|
||||
query_tables_own_last= &query_tables;
|
||||
|
||||
/*
|
||||
Exclude from global table list
|
||||
*/
|
||||
@@ -4838,6 +4851,14 @@ void LEX::link_first_table_back(TABLE_LIST *first,
|
||||
query_tables_last= &first->next_global;
|
||||
query_tables= first;
|
||||
|
||||
/*
|
||||
'first' is an own table of the statement, so if the own table list
|
||||
was empty, it now consists of 'first'. See unlink_first_table()
|
||||
for the symmetric adjustment.
|
||||
*/
|
||||
if (query_tables_own_last == &query_tables)
|
||||
query_tables_own_last= &first->next_global;
|
||||
|
||||
if (link_to_local)
|
||||
{
|
||||
first->next_local= first_select_lex()->table_list.first;
|
||||
@@ -7102,13 +7123,15 @@ bool LEX::is_trigger_new_or_old_reference(const LEX_CSTRING *name) const
|
||||
}
|
||||
|
||||
|
||||
void LEX::sp_variable_declarations_init(THD *thd, int nvars)
|
||||
bool LEX::sp_variable_declarations_init(THD *thd, int nvars)
|
||||
{
|
||||
sp_variable *spvar= spcont->get_last_context_variable();
|
||||
|
||||
sphead->reset_lex(thd);
|
||||
DBUG_ASSERT(this == thd->lex); // Both callers reach us as Lex->
|
||||
if (sphead->reset_lex(thd))
|
||||
return true;
|
||||
spcont->declare_var_boundary(nvars);
|
||||
thd->lex->init_last_field(&spvar->field_def, &spvar->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7145,6 +7168,15 @@ bool LEX::sp_variable_declarations_set_default(THD *thd, int nvars,
|
||||
|
||||
bool last= i + 1 == (uint) nvars;
|
||||
spvar->default_value= dflt_value_item;
|
||||
/*
|
||||
If the expression dflt_value_item is used in a DEFAULT clause of a
|
||||
variable initialization, then it's in a safe PS context,
|
||||
like an assignment right hand. So if dflt_value_item is a stored
|
||||
function then it will be able to execute prepared statements:
|
||||
DECLARE spvar INT DEFAULT f1(); -- OK to use PS inside f1()
|
||||
*/
|
||||
dflt_value_item->set_in_ps_safe_context();
|
||||
|
||||
/* The last instruction is responsible for freeing LEX. */
|
||||
sp_instr_set *is= new (thd->mem_root)
|
||||
sp_instr_set(sphead->instructions(),
|
||||
@@ -8439,7 +8471,13 @@ bool LEX::sp_body_finalize_function(THD *thd)
|
||||
|
||||
bool LEX::sp_body_finalize_trigger(THD *thd)
|
||||
{
|
||||
return sphead->is_not_allowed_in_function("trigger") ||
|
||||
/*
|
||||
Unlike a stored function, a trigger body is never executed in a context
|
||||
which is safe for prepared statements. So dynamic SQL is not allowed in
|
||||
a trigger at all and is rejected at CREATE TRIGGER time.
|
||||
*/
|
||||
return sphead->error_if_contains_dynamic_sql() ||
|
||||
sphead->is_not_allowed_in_function("trigger") ||
|
||||
sp_body_finalize_procedure(thd);
|
||||
}
|
||||
|
||||
@@ -9773,6 +9811,15 @@ bool LEX::set_variable(const Lex_ident_sys_st *name, Item *item,
|
||||
sp_pcontext *ctx;
|
||||
const Sp_rcontext_handler *rh;
|
||||
sp_variable *spv= find_variable(name, &ctx, &rh);
|
||||
if (item && spv)
|
||||
{
|
||||
/*
|
||||
This is an assignment statement. If the right hand is a stored
|
||||
function, it will be able to execute prepared statements:
|
||||
SET spvar= f1(); -- Ok to use PS inside f1()
|
||||
*/
|
||||
item->set_in_ps_safe_context();
|
||||
}
|
||||
return spv ? sphead->set_local_variable(thd, ctx, rh, spv, item, this, true,
|
||||
expr_str) :
|
||||
set_system_variable(option_type, name, item);
|
||||
@@ -9792,6 +9839,16 @@ bool LEX::set_variable(const Lex_ident_sys_st *name1,
|
||||
sp_variable *spv;
|
||||
if (spcont && (spv= find_variable(name1, &ctx, &rh)))
|
||||
{
|
||||
if (item)
|
||||
{
|
||||
/*
|
||||
SET sp_row_var.a= f1(); -- Ok to use PS inside f1()
|
||||
See also comments in similar places in:
|
||||
- sp_variable_declarations_set_default()
|
||||
- set_variable()
|
||||
*/
|
||||
item->set_in_ps_safe_context();
|
||||
}
|
||||
if (spv->field_def.is_table_rowtype_ref() ||
|
||||
spv->field_def.is_cursor_rowtype_ref())
|
||||
return sphead->set_local_variable_row_field_by_name(thd, ctx,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user