1
0
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:
Alexander Barkov
2026-05-26 10:11:41 +04:00
parent b2a8c2234d
commit bfabe0e530
106 changed files with 23159 additions and 86 deletions

View 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 ;$$

View File

@@ -0,0 +1,4 @@
DROP PROCEDURE p1;
DROP FUNCTION f_with_ps;
DROP FUNCTION f_without_ps;
DROP TABLE t1;

View 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

View 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

View File

@@ -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
#

View File

@@ -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 #

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1 @@
--loose-innodb_lock_wait_timeout=1 --loose-innodb_rollback_on_timeout

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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=

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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=

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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';

View File

@@ -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()

View 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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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);

View File

@@ -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*/;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -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();
/*

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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