diff --git a/mysql-test/main/delete_returning.result b/mysql-test/main/delete_returning.result index 847f810de630c..5329e9676b428 100644 --- a/mysql-test/main/delete_returning.result +++ b/mysql-test/main/delete_returning.result @@ -227,3 +227,10 @@ show status like "Rows_sent"; Variable_name Value Rows_sent 3 drop table t1; +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +DELETE FROM t1 RETURNING a INTO @a; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context +ANALYZE DELETE FROM t1 RETURNING a INTO @a; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context diff --git a/mysql-test/main/delete_returning.test b/mysql-test/main/delete_returning.test index cdfb48e843fe4..51946fc5d93c9 100644 --- a/mysql-test/main/delete_returning.test +++ b/mysql-test/main/delete_returning.test @@ -183,3 +183,13 @@ flush status; delete from t1 where a between 1 and 3 returning a,b; show status like "Rows_sent"; drop table t1; + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +DELETE FROM t1 RETURNING a INTO @a; + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +ANALYZE DELETE FROM t1 RETURNING a INTO @a; diff --git a/mysql-test/main/insert_returning.result b/mysql-test/main/insert_returning.result index 9c1233d7fa8ff..b8f1121a8f049 100644 --- a/mysql-test/main/insert_returning.result +++ b/mysql-test/main/insert_returning.result @@ -705,3 +705,8 @@ show status like "Rows_sent"; Variable_name Value Rows_sent 6 drop table t1; +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +INSERT INTO t1 VALUES (10) RETURNING a INTO @a; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context diff --git a/mysql-test/main/insert_returning.test b/mysql-test/main/insert_returning.test index 28bc0e495d743..932cbf36f56bf 100644 --- a/mysql-test/main/insert_returning.test +++ b/mysql-test/main/insert_returning.test @@ -412,3 +412,10 @@ insert into t1 values (1,2),(2,4) returning a,b; insert into t1 select seq,seq from seq_10_to_13 returning a,b; show status like "Rows_sent"; drop table t1; + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +INSERT INTO t1 VALUES (10) RETURNING a INTO @a; diff --git a/mysql-test/main/replace_returning.result b/mysql-test/main/replace_returning.result index fef7aaddaa3fc..736bf05a747ed 100644 --- a/mysql-test/main/replace_returning.result +++ b/mysql-test/main/replace_returning.result @@ -260,3 +260,10 @@ ERROR 21000: Subquery returns more than 1 row REPLACE INTO t2(id2,val2) SELECT t1.* FROM t1 WHERE id1=2 RETURNING t1.*; ERROR 42S02: Unknown table 'test.t1' DROP TABLE t1,t2; +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +REPLACE t1 SET a=a+1 RETURNING a INTO @a; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context +ANALYZE REPLACE INTO t1 VALUES (a=11, 'b11') RETURNING a,b INTO @a, @b; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context diff --git a/mysql-test/main/replace_returning.test b/mysql-test/main/replace_returning.test index 8b8a1c24e3e01..5b6e47a5e83b3 100644 --- a/mysql-test/main/replace_returning.test +++ b/mysql-test/main/replace_returning.test @@ -190,3 +190,14 @@ REPLACE INTO t2(id2,val2) SELECT t1.* FROM t1 WHERE id1=2 RETURNING t1.*; DROP TABLE t1,t2; + + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +REPLACE t1 SET a=a+1 RETURNING a INTO @a; + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +ANALYZE REPLACE INTO t1 VALUES (a=11, 'b11') RETURNING a,b INTO @a, @b; diff --git a/mysql-test/main/select_into_row.result b/mysql-test/main/select_into_row.result new file mode 100644 index 0000000000000..98315c114d124 --- /dev/null +++ b/mysql-test/main/select_into_row.result @@ -0,0 +1,140 @@ +# +# MDEV-40790 SELECT INTO row_type_of.field crashes the server +# +# Into an explicit ROW variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into an explicit ROW field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into an explicit ROW field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP TABLE t1; +# Into a `ROW TYPE OF table1` variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF table1` field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF table1` field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +CALL p1; +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +END; +$$ +CALL p1; +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP PROCEDURE p1; +DROP TABLE t1; +# End of 12.3 tests diff --git a/mysql-test/main/select_into_row.test b/mysql-test/main/select_into_row.test new file mode 100644 index 0000000000000..79127eca21b6b --- /dev/null +++ b/mysql-test/main/select_into_row.test @@ -0,0 +1,174 @@ +--echo # +--echo # MDEV-40790 SELECT INTO row_type_of.field crashes the server +--echo # + + +--echo # Into an explicit ROW variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into an explicit ROW field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into an explicit ROW field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # End of 12.3 tests diff --git a/mysql-test/main/update_returning.result b/mysql-test/main/update_returning.result new file mode 100644 index 0000000000000..c97cdec06920f --- /dev/null +++ b/mysql-test/main/update_returning.result @@ -0,0 +1,633 @@ +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +# +# UPDATE..RETURNING..INTO without INTO +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +CREATE PROCEDURE p1() +BEGIN +UPDATE t1 SET a=a+1 WHERE a>=20 RETURNING a,b; +END; +$$ +CALL p1; +a b +21 b20 +CALL p1; +a b +22 b20 +CALL p1; +a b +23 b20 +DROP PROCEDURE p1; +DROP TABLE t1; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +UPDATE t1 SET a=OLD_VALUE(a); +UPDATE t1 SET a=OLD_VALUE(a)+1; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '+1' at line 1 +UPDATE t1 SET a=1+OLD_VALUE(a); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '(a)' at line 1 +DROP TABLE t1; +# +# ANALYZE UPDATE .. RETURNING .. INTO ..; +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +ANALYZE UPDATE t1 t1 SET a=11, b='b11' RETURNING a,b INTO @a, @b; +id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 1 1.00 100.00 100.00 +SELECT @a,@b; +@a @b +NULL NULL +DROP TABLE t1; +# +# Multi-table UPDATEs +# +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +UPDATE t1,t2 SET t1.a=t1.a+1 WHERE t1.a=t2.b AND t1.a=10 RETURNING t1.a INTO @a; +ERROR 42000: This version of MariaDB doesn't yet support 'RETURNING for multi-table UPDATE' +UPDATE t1 SET a=a+1 WHERE a=(SELECT MAX(a) FROM t1) RETURNING a INTO @a; +ERROR HY000: 'RETURNING..INTO' is not allowed in this context +DROP TABLE t1,t2; +# +# UPDATE..RETURNING..INTO for user variables +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +SET @va=NULL; +SET @vb=NULL; +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO @va, @vb; +SELECT @va, @vb; +SET @va=NULL; +SET @vb=NULL; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT @va, @vb; +@va @vb +NULL NULL +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT @va, @vb; +@va @vb +NULL NULL +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT @va, @vb; +@va @vb +NULL NULL +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT @va, @vb; +@va @vb +11 b10 +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT @va, @vb; +@va @vb +12 b10 +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT @va, @vb; +@va @vb +13 b10 +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +@va @vb +31 b30 +CALL p1(30); +@va @vb +32 b30 +CALL p1(30); +@va @vb +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for SP variables +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE va INT; +DECLARE vb VARCHAR(10); +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO va,vb; +SELECT va, vb; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +va vb +31 b30 +CALL p1(30); +va vb +32 b30 +CALL p1(30); +va vb +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO row_var.not_existing_field +# +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1_row_explicit() +BEGIN +DECLARE r0 ROW(a INT, b VARCHAR(10)); +UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; +SELECT r0.a, r0.b; +END; +$$ +ERROR HY000: Row variable 'r0' does not have a field 'b_not_existing' +DROP TABLE t1; +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1_row_type_of_table() +BEGIN +DECLARE r0 ROW TYPE OF t1; +UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; +SELECT r0.a, r0.b; +END; +$$ +CALL p1_row_type_of_table; +ERROR HY000: Row variable 'r0' does not have a field 'b_not_existing' +DROP PROCEDURE p1_row_type_of_table; +DROP TABLE t1; +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1_row_type_of_cursor() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; +SELECT r0.a, r0.b; +END; +END; +$$ +CALL p1_row_type_of_cursor; +ERROR HY000: Row variable 'r0' does not have a field 'b_not_existing' +DROP PROCEDURE p1_row_type_of_cursor; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a ROW variable +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW(a INT, b VARCHAR(10)); +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a ROW variable fields +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW(a INT, b VARCHAR(10)); +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec.a, vrec.b; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a `ROW TYPE OF table1` variable +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW TYPE OF t1; +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a `ROW TYPE OF table1` variable fields +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW TYPE OF t1; +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec.a, vrec.b; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a `ROW TYPE OF cursor1` variable +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW TYPE OF c1; +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO for a `ROW TYPE OF cursor1` variable fields +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CREATE PROCEDURE p1(pa INT) +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE vrec ROW TYPE OF c1; +UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO vrec.a, vrec.b; +SELECT vrec.a, vrec.b; +END; +END; +$$ +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +CALL p1(100); +ERROR 02000: No data - zero rows fetched, selected, or processed +SELECT * FROM t1; +a b +10 b10 +20 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +11 b10 +21 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +12 b10 +22 b20 +30 b30 +CALL p1(0); +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a b +13 b10 +23 b20 +30 b30 +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +vrec.a vrec.b +31 b30 +CALL p1(30); +vrec.a vrec.b +32 b30 +CALL p1(30); +vrec.a vrec.b +33 b30 +SELECT * FROM t1; +a b +10 b10 +20 b20 +33 b30 +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/mysql-test/main/update_returning.test b/mysql-test/main/update_returning.test new file mode 100644 index 0000000000000..9000ac35aa913 --- /dev/null +++ b/mysql-test/main/update_returning.test @@ -0,0 +1,270 @@ +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--echo # +--echo # UPDATE..RETURNING..INTO without INTO +--echo # + +# First of all check that the patch for MDEV-39563 did not break +# anything in the usual UPDATE..RETURNING, without INTO. +# The second execution is important to test. + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + UPDATE t1 SET a=a+1 WHERE a>=20 RETURNING a,b; +END; +$$ +DELIMITER ;$$ +CALL p1; +CALL p1; +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +# +# Make sure that already `lex->m_sql_cmd != nullptr` when +# Sql_cmd_update::set_with_old_value_items() is called +# while parsing OLD_VALUE(). +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10); +UPDATE t1 SET a=OLD_VALUE(a); +--error ER_PARSE_ERROR +UPDATE t1 SET a=OLD_VALUE(a)+1; +--error ER_PARSE_ERROR +UPDATE t1 SET a=1+OLD_VALUE(a); +DROP TABLE t1; + + +--echo # +--echo # ANALYZE UPDATE .. RETURNING .. INTO ..; +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +ANALYZE UPDATE t1 t1 SET a=11, b='b11' RETURNING a,b INTO @a, @b; +SELECT @a,@b; +DROP TABLE t1; + + +--echo # +--echo # Multi-table UPDATEs +--echo # + +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +--error ER_NOT_SUPPORTED_YET +UPDATE t1,t2 SET t1.a=t1.a+1 WHERE t1.a=t2.b AND t1.a=10 RETURNING t1.a INTO @a; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +UPDATE t1 SET a=a+1 WHERE a=(SELECT MAX(a) FROM t1) RETURNING a INTO @a; +DROP TABLE t1,t2; + + +--echo # +--echo # UPDATE..RETURNING..INTO for user variables +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +DELIMITER $$; +CREATE PROCEDURE p1(pa INT) +BEGIN + SET @va=NULL; + SET @vb=NULL; + UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO @va, @vb; + SELECT @va, @vb; + SET @va=NULL; + SET @vb=NULL; +END; +$$ +DELIMITER ;$$ + +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +SELECT @va, @vb; +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +SELECT @va, @vb; +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +SELECT @va, @vb; +SELECT * FROM t1; + +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT @va, @vb; +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT @va, @vb; +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT @va, @vb; +SELECT * FROM t1; + +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +CALL p1(30); +CALL p1(30); +SELECT * FROM t1; + +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..RETURNING..INTO for SP variables +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +DELIMITER $$; +CREATE PROCEDURE p1(pa INT) +BEGIN + DECLARE va INT; + DECLARE vb VARCHAR(10); + UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO va,vb; + SELECT va, vb; +END; +$$ +DELIMITER ;$$ + +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +SELECT * FROM t1; + +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; + +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); +CALL p1(30); +CALL p1(30); +CALL p1(30); +SELECT * FROM t1; + +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..RETURNING..INTO row_var.not_existing_field +--echo # + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CREATE PROCEDURE p1_row_explicit() +BEGIN + DECLARE r0 ROW(a INT, b VARCHAR(10)); + UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1_row_type_of_table() +BEGIN + DECLARE r0 ROW TYPE OF t1; + UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1_row_type_of_table; +DROP PROCEDURE p1_row_type_of_table; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1_row_type_of_cursor() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + UPDATE t1 SET a=a+1 RETURNING a,b INTO r0.a,r0.b_not_existing; + SELECT r0.a, r0.b; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1_row_type_of_cursor; +DROP PROCEDURE p1_row_type_of_cursor; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..RETURNING..INTO for a ROW variable +--echo # + +--let $TYPE= ROW(a INT, b VARCHAR(10)) +--let $INTO= vrec +--source update_returning_into_row_var.inc + +--echo # +--echo # UPDATE..RETURNING..INTO for a ROW variable fields +--echo # + +--let $TYPE= ROW(a INT, b VARCHAR(10)) +--let $INTO= vrec.a, vrec.b +--source update_returning_into_row_var.inc + +--echo # +--echo # UPDATE..RETURNING..INTO for a `ROW TYPE OF table1` variable +--echo # + +--let $TYPE= ROW TYPE OF t1 +--let $INTO= vrec +--source update_returning_into_row_var.inc + +--echo # +--echo # UPDATE..RETURNING..INTO for a `ROW TYPE OF table1` variable fields +--echo # + +--let $TYPE= ROW TYPE OF t1 +--let $INTO= vrec.a, vrec.b +--source update_returning_into_row_var.inc + +--echo # +--echo # UPDATE..RETURNING..INTO for a `ROW TYPE OF cursor1` variable +--echo # + +--let $TYPE= ROW TYPE OF c1 +--let $INTO= vrec +--source update_returning_into_row_var.inc + +--echo # +--echo # UPDATE..RETURNING..INTO for a `ROW TYPE OF cursor1` variable fields +--echo # + +--let $TYPE= ROW TYPE OF c1 +--let $INTO= vrec.a, vrec.b +--source update_returning_into_row_var.inc diff --git a/mysql-test/main/update_returning_into_row_var.inc b/mysql-test/main/update_returning_into_row_var.inc new file mode 100644 index 0000000000000..1fb7dfd784f5e --- /dev/null +++ b/mysql-test/main/update_returning_into_row_var.inc @@ -0,0 +1,47 @@ +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); + +DELIMITER $$; +eval CREATE PROCEDURE p1(pa INT) +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE vrec $TYPE; + UPDATE t1 SET a=a+1 WHERE a>=pa RETURNING a,b INTO $INTO; + SELECT vrec.a, vrec.b; + END; +END; +$$ +DELIMITER ;$$ + +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +--error ER_SP_FETCH_NO_DATA +CALL p1(100); +SELECT * FROM t1; + +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; +--error ER_TOO_MANY_ROWS +CALL p1(0); +SELECT * FROM t1; + +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'),(30,'b30'); + +CALL p1(30); +CALL p1(30); +CALL p1(30); +SELECT * FROM t1; + +DROP PROCEDURE p1; +DROP TABLE t1; + +--let $TYPE= +--let $INTO= diff --git a/mysql-test/suite/binlog/r/binlog_update_returning_into_mixed.result b/mysql-test/suite/binlog/r/binlog_update_returning_into_mixed.result new file mode 100644 index 0000000000000..731cad6ebbeaf --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_update_returning_into_mixed.result @@ -0,0 +1,31 @@ +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +UPDATE t1 SET a=a+1 RETURNING a INTO @a; +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +DROP TABLE t1; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT, b VARCHAR(10)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # INSERT INTO t1 VALUES (10,'b10') +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # UPDATE t1 SET a=a+1 RETURNING a INTO @a +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Update_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # UPDATE t1 SET a=a+1 RETURNING a INTO @a1 +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Update_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ diff --git a/mysql-test/suite/binlog/r/binlog_update_returning_into_row.result b/mysql-test/suite/binlog/r/binlog_update_returning_into_row.result new file mode 100644 index 0000000000000..731cad6ebbeaf --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_update_returning_into_row.result @@ -0,0 +1,31 @@ +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +UPDATE t1 SET a=a+1 RETURNING a INTO @a; +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +DROP TABLE t1; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT, b VARCHAR(10)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # INSERT INTO t1 VALUES (10,'b10') +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # UPDATE t1 SET a=a+1 RETURNING a INTO @a +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Update_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # UPDATE t1 SET a=a+1 RETURNING a INTO @a1 +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Update_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ diff --git a/mysql-test/suite/binlog/r/binlog_update_returning_into_stmt.result b/mysql-test/suite/binlog/r/binlog_update_returning_into_stmt.result new file mode 100644 index 0000000000000..3bb9aa571290e --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_update_returning_into_stmt.result @@ -0,0 +1,21 @@ +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +UPDATE t1 SET a=a+1 RETURNING a INTO @a; +ERROR HY000: Incorrect usage of --binlog-format=statement and RETURNING INTO +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +EXECUTE stmt; +ERROR HY000: Incorrect usage of --binlog-format=statement and RETURNING INTO +DEALLOCATE PREPARE stmt; +DROP TABLE t1; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT, b VARCHAR(10)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (10,'b10') +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ diff --git a/mysql-test/suite/binlog/t/binlog_update_returning_into_mixed.test b/mysql-test/suite/binlog/t/binlog_update_returning_into_mixed.test new file mode 100644 index 0000000000000..9bfa520fcb6e3 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_update_returning_into_mixed.test @@ -0,0 +1,23 @@ +--source include/have_log_bin.inc +--source include/have_binlog_format_row.inc + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--disable_query_log +reset master; # get rid of previous tests binlog +--enable_query_log + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +UPDATE t1 SET a=a+1 RETURNING a INTO @a; + +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +DROP TABLE t1; + +--let $binlog_file = LAST +source include/show_binlog_events.inc; diff --git a/mysql-test/suite/binlog/t/binlog_update_returning_into_row.test b/mysql-test/suite/binlog/t/binlog_update_returning_into_row.test new file mode 100644 index 0000000000000..9bfa520fcb6e3 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_update_returning_into_row.test @@ -0,0 +1,23 @@ +--source include/have_log_bin.inc +--source include/have_binlog_format_row.inc + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--disable_query_log +reset master; # get rid of previous tests binlog +--enable_query_log + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +UPDATE t1 SET a=a+1 RETURNING a INTO @a; + +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +DROP TABLE t1; + +--let $binlog_file = LAST +source include/show_binlog_events.inc; diff --git a/mysql-test/suite/binlog/t/binlog_update_returning_into_stmt.test b/mysql-test/suite/binlog/t/binlog_update_returning_into_stmt.test new file mode 100644 index 0000000000000..0c6881a91e5ea --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_update_returning_into_stmt.test @@ -0,0 +1,25 @@ +--source include/have_log_bin.inc +--source include/have_binlog_format_statement.inc + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--disable_query_log +reset master; # get rid of previous tests binlog +--enable_query_log + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +--error ER_WRONG_USAGE +UPDATE t1 SET a=a+1 RETURNING a INTO @a; + +PREPARE stmt FROM 'UPDATE t1 SET a=a+1 RETURNING a INTO @a1'; +--error ER_WRONG_USAGE +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +DROP TABLE t1; + +--let $binlog_file = LAST +source include/show_binlog_events.inc; diff --git a/mysql-test/suite/rpl/r/rpl_update_returning_mixed_or_row.result b/mysql-test/suite/rpl/r/rpl_update_returning_mixed_or_row.result new file mode 100644 index 0000000000000..3d549f87422ee --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_update_returning_mixed_or_row.result @@ -0,0 +1,30 @@ +include/master-slave.inc +[connection master] +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20),(30); +connection slave; +connection master; +BEGIN NOT ATOMIC +DECLARE va INT; +UPDATE t1 SET a=a+1 RETURNING a INTO va; +END; +$$ +ERROR 42000: Result consisted of more than one row +SELECT * FROM t1; +a +11 +21 +30 +connection slave; +SELECT * FROM t1; +a +11 +21 +30 +connection master; +DROP TABLE t1; +# End of 13.1 tests +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_update_returning_mixed_or_row.test b/mysql-test/suite/rpl/t/rpl_update_returning_mixed_or_row.test new file mode 100644 index 0000000000000..24aa4d1fb6e5b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_update_returning_mixed_or_row.test @@ -0,0 +1,30 @@ +--source include/have_binlog_format_mixed_or_row.inc +--source include/master-slave.inc + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20),(30); +--sync_slave_with_master + +--connection master +DELIMITER $$; +--error ER_TOO_MANY_ROWS +BEGIN NOT ATOMIC + DECLARE va INT; + UPDATE t1 SET a=a+1 RETURNING a INTO va; +END; +$$ +DELIMITER ;$$ +SELECT * FROM t1; +--sync_slave_with_master +SELECT * FROM t1; + +--connection master +DROP TABLE t1; + +--echo # End of 13.1 tests + +--source include/rpl_end.inc diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.result b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.result new file mode 100644 index 0000000000000..524e08a53b8aa --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.result @@ -0,0 +1,180 @@ +SET sql_mode=ORACLE; +# +# MDEV-39563 Implement UPDATE ... RETURNING ... INTO +# +# +# UPDATE..RETURNING..INTO into an array +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +CREATE PROCEDURE p1 AS +TYPE assoc0_t IS TABLE OF NUMBER INDEX BY INTEGER; +a0 assoc0_t; +BEGIN +UPDATE t1 SET a=2 RETURNING a INTO a0; +END; +$$ +CALL p1; +ERROR 21000: The used SELECT statements have a different number of columns +DROP PROCEDURE p1; +DROP TABLE t1; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +CREATE PROCEDURE p1 AS +TYPE assoc0_t IS TABLE OF NUMBER INDEX BY INTEGER; +a0 assoc0_t; +BEGIN +UPDATE t1 SET a=2 RETURNING a,a+1 INTO a0; +END; +$$ +CALL p1; +ERROR 21000: The used SELECT statements have a different number of columns +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO into a scalar element +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +UPDATE t1 SET a=5 WHERE a=4 RETURNING a INTO marks(200); +SELECT marks(200); +END; +$$ +marks(200) +5 +DROP TABLE t1; +# +# UPDATE..INTO..RETURNING into a non-scalar element +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +CREATE PROCEDURE p1 AS +TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); +TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; +a0 assoc0_t; +BEGIN +UPDATE t1 SET a=30,b='b30' WHERE a=20 RETURNING a,b INTO a0(30); +SELECT a0(30).a, a0(30).b; +END; +$$ +CALL p1(); +a0(30).a a0(30).b +30 b30 +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..INTO..RETURNING into a field of a non-scalar element +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +CREATE PROCEDURE p1(create_element BOOL, select_element BOOL) AS +TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); +TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; +a0 assoc0_t; +BEGIN +IF (create_element) THEN +a0(20):= rec0_t(20,'b20'); +END IF; +UPDATE t1 SET a=a+1,b='b21' WHERE a>=20 RETURNING a,b INTO a0(20).a,a0(20).b; +IF (select_element) THEN +SELECT a0(20).a, a0(20).b; +END IF; +END; +$$ +CALL p1(TRUE,TRUE); +a0(20).a a0(20).b +21 b21 +CALL p1(FALSE,FALSE); +ERROR HY000: Element not found with key '20' +CALL p1(FALSE,TRUE); +ERROR HY000: Element not found with key '20' +DROP PROCEDURE p1; +DROP TABLE t1; +# +# UPDATE..RETURNING..INTO assoc_array_of_scalars('key').field +# Fails during the CREATE time. +# +CREATE TABLE t1 (a VARCHAR(20)); +CREATE PROCEDURE p1 IS +TYPE assoc_t IS TABLE OF VARCHAR2(20) INDEX BY INT; +assoc assoc_t; +BEGIN +UPDATE t1 SET a= 'a10' RETURNING a INTO assoc(10).last_name; +END; +$$ +ERROR HY000: Illegal parameter data type varchar for operation '`assoc`(..).`last_name`' +DROP TABLE t1; +# +# SELECT scalar INTO non-existent variable with key +# +CREATE TABLE t1 (a VARCHAR(10)); +DECLARE +BEGIN +UPDATE t1 SET a='Mike' RETURNING a INTO missing_var('Serg').first_name; +END; +$$ +ERROR 42000: Undeclared variable: missing_var +DROP TABLE t1; +# +# UPDATE..INTO..RETURNING: field on scalar element +# +CREATE TABLE t1 (a INT); +DECLARE +TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; +marks marks_t; +BEGIN +marks(200):= 88; +UPDATE t1 SET a=201 RETURNING a INTO marks(200).non_exist; +END; +$$ +ERROR HY000: Illegal parameter data type double for operation '`marks`(..).`non_exist`' +DROP TABLE t1; +# +# UPDATE..INTO..RETURNING into a non-existent field of a non-scalar element +# +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +CREATE PROCEDURE p1() AS +TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); +TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; +a0 assoc0_t; +BEGIN +UPDATE t1 SET a=a+1,b='b21' WHERE a>=20 RETURNING a,b INTO a0(20).xa,a0(20).xb; +END; +$$ +ERROR 42S22: Unknown column 'xa' in 'a0' +DROP TABLE t1; +# +# Add tests with assoc array here when we add support for +# {INSERT|REPLACE|DELETE}..RETURNING..INTO +# +CREATE PROCEDURE p1() AS +TYPE assoc0_t IS TABLE OF INT INDEX BY INT; +a0 assoc0_t; +BEGIN +INSERT INTO t1 VALUES (10) RETURNING a INTO a0; +END; +$$ +ERROR HY000: 'RETURNING..INTO' is not allowed in this context +CREATE PROCEDURE p1() AS +TYPE assoc0_t IS TABLE OF INT INDEX BY INT; +a0 assoc0_t; +BEGIN +REPLACE INTO t1 SET a=10 RETURNING a INTO a0; +END; +$$ +ERROR HY000: 'RETURNING..INTO' is not allowed in this context +CREATE PROCEDURE p1() AS +TYPE assoc0_t IS TABLE OF INT INDEX BY INT; +a0 assoc0_t; +BEGIN +DELETE FROM t1 RETURNING a INTO a0; +END; +$$ +ERROR HY000: 'RETURNING..INTO' is not allowed in this context diff --git a/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.test b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.test new file mode 100644 index 0000000000000..d14ec8fba1041 --- /dev/null +++ b/plugin/type_assoc_array/mysql-test/type_assoc_array/sp-assoc-array-update-returning.test @@ -0,0 +1,225 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-39563 Implement UPDATE ... RETURNING ... INTO +--echo # + +--echo # +--echo # UPDATE..RETURNING..INTO into an array +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE assoc0_t IS TABLE OF NUMBER INDEX BY INTEGER; + a0 assoc0_t; +BEGIN + UPDATE t1 SET a=2 RETURNING a INTO a0; +END; +$$ +DELIMITER ;$$ +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE assoc0_t IS TABLE OF NUMBER INDEX BY INTEGER; + a0 assoc0_t; +BEGIN + UPDATE t1 SET a=2 RETURNING a,a+1 INTO a0; +END; +$$ +DELIMITER ;$$ +--error ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..RETURNING..INTO into a scalar element +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +INSERT INTO t1 VALUES (4); +DELIMITER $$; +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + UPDATE t1 SET a=5 WHERE a=4 RETURNING a INTO marks(200); + SELECT marks(200); +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # UPDATE..INTO..RETURNING into a non-scalar element +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +DELIMITER $$; +CREATE PROCEDURE p1 AS + TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); + TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; + a0 assoc0_t; +BEGIN + UPDATE t1 SET a=30,b='b30' WHERE a=20 RETURNING a,b INTO a0(30); + SELECT a0(30).a, a0(30).b; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..INTO..RETURNING into a field of a non-scalar element +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +DELIMITER $$; +CREATE PROCEDURE p1(create_element BOOL, select_element BOOL) AS + TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); + TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; + a0 assoc0_t; +BEGIN + IF (create_element) THEN + a0(20):= rec0_t(20,'b20'); + END IF; + UPDATE t1 SET a=a+1,b='b21' WHERE a>=20 RETURNING a,b INTO a0(20).a,a0(20).b; + IF (select_element) THEN + SELECT a0(20).a, a0(20).b; + END IF; +END; +$$ +DELIMITER ;$$ +CALL p1(TRUE,TRUE); +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +CALL p1(FALSE,FALSE); +--error ER_ASSOC_ARRAY_ELEM_NOT_FOUND +CALL p1(FALSE,TRUE); +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # UPDATE..RETURNING..INTO assoc_array_of_scalars('key').field +--echo # Fails during the CREATE time. +--echo # +CREATE TABLE t1 (a VARCHAR(20)); +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +CREATE PROCEDURE p1 IS + TYPE assoc_t IS TABLE OF VARCHAR2(20) INDEX BY INT; + assoc assoc_t; +BEGIN + UPDATE t1 SET a= 'a10' RETURNING a INTO assoc(10).last_name; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # SELECT scalar INTO non-existent variable with key +--echo # + +CREATE TABLE t1 (a VARCHAR(10)); +DELIMITER $$; +--error ER_SP_UNDECLARED_VAR +DECLARE +BEGIN + UPDATE t1 SET a='Mike' RETURNING a INTO missing_var('Serg').first_name; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # UPDATE..INTO..RETURNING: field on scalar element +--echo # +CREATE TABLE t1 (a INT); +DELIMITER $$; +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +DECLARE + TYPE marks_t IS TABLE OF NUMBER INDEX BY INTEGER; + marks marks_t; +BEGIN + marks(200):= 88; + UPDATE t1 SET a=201 RETURNING a INTO marks(200).non_exist; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # UPDATE..INTO..RETURNING into a non-existent field of a non-scalar element +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'),(20,'b20'); +DELIMITER $$; +--error ER_BAD_FIELD_ERROR +CREATE PROCEDURE p1() AS + TYPE rec0_t IS RECORD (a INT, b VARCHAR(10)); + TYPE assoc0_t IS TABLE OF rec0_t INDEX BY INT; + a0 assoc0_t; +BEGIN + UPDATE t1 SET a=a+1,b='b21' WHERE a>=20 RETURNING a,b INTO a0(20).xa,a0(20).xb; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # +--echo # Add tests with assoc array here when we add support for +--echo # {INSERT|REPLACE|DELETE}..RETURNING..INTO +--echo # + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE PROCEDURE p1() AS + TYPE assoc0_t IS TABLE OF INT INDEX BY INT; + a0 assoc0_t; +BEGIN + INSERT INTO t1 VALUES (10) RETURNING a INTO a0; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE PROCEDURE p1() AS + TYPE assoc0_t IS TABLE OF INT INDEX BY INT; + a0 assoc0_t; +BEGIN + REPLACE INTO t1 SET a=10 RETURNING a INTO a0; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +CREATE PROCEDURE p1() AS + TYPE assoc0_t IS TABLE OF INT INDEX BY INT; + a0 assoc0_t; +BEGIN + DELETE FROM t1 RETURNING a INTO a0; +END; +$$ +DELIMITER ;$$ diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 5dec16c308157..ee386b9395613 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -688,6 +688,20 @@ int sp_rcontext::set_variable_row_field(THD *thd, uint var_idx, uint field_idx, } +int sp_rcontext::set_variable_row_field_by_name(THD *thd, uint var_idx, + const Lex_ident_sys_st &field_name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_row_field"); + DBUG_ASSERT(value); + uint field_idx= 0; + if (find_row_field_by_name_or_error(&field_idx, var_idx, field_name)) + DBUG_RETURN(true); + Virtual_tmp_table *vtable= virtual_tmp_table_for_row(var_idx); + DBUG_RETURN(thd->sp_eval_expr(vtable->field[field_idx], value)); +} + + int sp_rcontext::set_variable_row(THD *thd, uint var_idx, List &items) { DBUG_ENTER("sp_rcontext::set_variable_row"); diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 7cf68d7c94935..2927311d55ddc 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -209,6 +209,9 @@ class sp_rcontext : public Sql_alloc int set_variable(THD *thd, uint var_idx, Item **value); int set_variable_row_field(THD *thd, uint var_idx, uint field_idx, Item **value); + int set_variable_row_field_by_name(THD *thd, uint var_idx, + const Lex_ident_sys_st &field_name, + Item **value); int set_variable_row(THD *thd, uint var_idx, List &items); int set_variable_composite_field_by_key(THD *thd, diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h index bc5215cbbc66b..a7a7ded68bc27 100644 --- a/sql/sql_cmd.h +++ b/sql/sql_cmd.h @@ -180,6 +180,7 @@ class Sql_cmd : public Sql_alloc struct LEX; class select_result; +class select_dumpvar; class Prelocking_strategy; class DML_prelocking_strategy; class Protocol; @@ -217,6 +218,12 @@ class Sql_cmd_dml : public Sql_cmd */ virtual bool is_data_change_stmt() const { return true; } + /** + @brief Set the RETURNING INTO result + @return true on error, false on success + */ + virtual bool set_returning_into_result(select_dumpvar *res); + /** @brief Perform context analysis of the statement @param thd global context the processed statement diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 6e1c6df4715b7..684389f350122 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -14279,6 +14279,24 @@ LEX::parse_optimizer_hints(const Lex_comment_st &hints_str) return {false, new (thd->mem_root) Optimizer_hint_parser_output(std::move(hints))}; } +#include "sql_class.h" + +bool LEX::set_returning_into_result(select_dumpvar *res) +{ + if (!res) + return false; // No INTO + + // The order of rows is not predicable, like in SELECT..LIMIT + set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); + + if (Sql_cmd_dml *dml= dynamic_cast(m_sql_cmd)) + return dml->set_returning_into_result(res); + + // A DML statement implemented without Sql_cmd yet + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), "RETURNING..INTO"); + return true; +} + /* @brief diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5e0258ee42bf3..7eb2a7f83caa6 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -845,6 +845,7 @@ typedef class st_select_lex_node SELECT_LEX_NODE; */ class THD; class select_result; +class select_dumpvar; class JOIN; class select_unit; class Procedure; @@ -3980,6 +3981,7 @@ struct LEX: public Query_tables_list DBUG_RETURN(select_lex); } + bool set_returning_into_result(select_dumpvar *res); void handle_parsed_optimizer_hints_in_last_select(); void resolve_optimizer_hints(); bool discard_optimizer_hints_in_last_select(); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index dc9e7b6113333..d6a7528554469 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -34933,6 +34933,13 @@ static void MYSQL_DML_DONE(THD *thd, int rc, ha_rows found, ha_rows changed) } +bool Sql_cmd_dml::set_returning_into_result(select_dumpvar *res) +{ + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), "RETURNING..INTO"); + return true; +} + + /* @brief Perform actions needed before locking tables for a DML statement diff --git a/sql/sql_type_row.cc b/sql/sql_type_row.cc index f09d0fd36f32a..e747f15d6f629 100644 --- a/sql/sql_type_row.cc +++ b/sql/sql_type_row.cc @@ -136,6 +136,32 @@ class my_var_sp_row_field: public my_var_sp }; +class my_var_sp_row_field_by_name: public my_var_sp +{ + const Lex_ident_sys_st m_field_name; +public: + my_var_sp_row_field_by_name(const Lex_ident_sys_st &varname, + const sp_rcontext_addr &varaddr, + const Lex_ident_sys_st &field_name, + sp_head *s) + :my_var_sp(varname, varaddr, + &type_handler_double/*Not really used*/, s), + m_field_name(field_name) + { } + bool check_assignability(THD *thd, const List &select_list, + bool *assign_as_row) const override + { + *assign_as_row= false; + return select_list.elements == 1; + } + bool set(THD *thd, Item *item) override + { + return get_rcontext(thd->spcont)-> + set_variable_row_field_by_name(thd, offset(), m_field_name, &item); + } +}; + + my_var *Type_handler_row::make_outvar(THD *thd, const Lex_ident_sys_st &name, const sp_rcontext_addr &addr, @@ -160,11 +186,19 @@ my_var *Type_handler_row::make_outvar_field(THD *thd, DBUG_ASSERT(t); DBUG_ASSERT(t->type_handler() == this); + if (t->field_def.is_table_rowtype_ref() || + t->field_def.is_cursor_rowtype_ref()) + { + if (validate_only) + return nullptr; + return new (thd->mem_root) my_var_sp_row_field_by_name(name, addr, + field, sphead); + + } uint row_field_offset; if (!t->find_row_field(&name, &field, &row_field_offset)) { - DBUG_ASSERT(0); - my_error(ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD, MYF(0), name.str, field.str); + DBUG_ASSERT(thd->is_error()); return NULL; } if (validate_only) // e.g. EXPLAIN SELECT .. INTO spvar_row.field; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 847d073b48d78..53702d963f1cd 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -3042,6 +3042,22 @@ bool multi_update::send_eof() } +bool Sql_cmd_update::set_returning_into_result(select_dumpvar *res) +{ + /* + Due to the implementation, OLD_VALUE() would return the new value + into the INTO target (not the old value). Disallow it. + */ + if (m_with_old_value_items) + { + my_error(ER_WRONG_USAGE, MYF(0), "RETURNING OLD_VALUE(..)", "INTO"); + return true; + } + returning_result= res; + return false; +} + + /** @brief Check whether conversion to multi-table update is prohibited @@ -3286,6 +3302,7 @@ bool Sql_cmd_update::prepare_inner(THD *thd) bool Sql_cmd_update::execute_inner(THD *thd) { bool res= 0; + bool set_returning_result_to_null= false; Running_stmt_guard guard(thd, active_dml_stmt::UPDATING_STMT); if (!multitable) @@ -3295,25 +3312,58 @@ bool Sql_cmd_update::execute_inner(THD *thd) /* This is UPDATE ... RETURNING. It will return output to the client */ if (thd->lex->analyze_stmt) { + /* + Here we can have queries like: + ANALYZE UPDATE t1 SET a=a+1 RETURNING a; + ANALYZE UPDATE t1 SET a=a+1 RETURNING a INTO @a; + If there is an INTO clause, let's forget and ignore it. + */ + DBUG_ASSERT(returning_result == nullptr || + dynamic_cast(returning_result)); if (!(returning_result= new (thd->mem_root) select_send_analyze(thd))) { return true; } + set_returning_result_to_null= true; save_protocol= thd->protocol; thd->protocol= new Protocol_discard(thd); } else { - if (!(returning_result= new - (thd->mem_root) select_send(thd))) + if (!returning_result) { - return true; + // UPDATE t1 SET a=a+1 RETURNING a; + if (!(returning_result= new (thd->mem_root) select_send(thd))) + return true; + set_returning_result_to_null= true; + } + else + { + // UPDATE t1 SET a=a+1 RETURNING a INTO @a; + DBUG_ASSERT(dynamic_cast(returning_result)); + if (mysql_bin_log.is_open() && + (thd->variables.option_bits & OPTION_BIN_LOG) && + thd->variables.binlog_format == BINLOG_FORMAT_STMT) + { + my_error(ER_WRONG_USAGE, MYF(0), "--binlog-format=statement", + "RETURNING INTO"); + return true; + } } } if (thd->lex->has_returning()) (void) returning_result->prepare(thd->lex->returning()->returning_list, NULL); } } + else + { + // RETURNING INTO is not supported for multi-table UPDATEs + if (dynamic_cast(returning_result)) + { + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), "RETURNING..INTO"); + return true; + } + } thd->get_stmt_da()->reset_current_row_for_warning(1); if (!multitable) @@ -3346,6 +3396,18 @@ bool Sql_cmd_update::execute_inner(THD *thd) } } + if (set_returning_result_to_null) + { + /* + returning_result could be assigned to a select_send or + select_send_analyze instance above. + Let's set it to null again, to have a new + select_send/select_send_analyze instance created on the second + and further executions. + */ + returning_result= nullptr; + } + if (result) { /* In single table case, this->updated set by update_single_table */ diff --git a/sql/sql_update.h b/sql/sql_update.h index f663fbc99b537..579ee11c1272a 100644 --- a/sql/sql_update.h +++ b/sql/sql_update.h @@ -46,16 +46,19 @@ class Sql_cmd_update final : public Sql_cmd_dml { public: ha_rows found{0}, updated{0}; - Sql_cmd_update(bool multitable_arg) - : orig_multitable(multitable_arg), multitable(multitable_arg), + Sql_cmd_update() + : m_sql_command_code(SQLCOM_UPDATE), multitable(false), + m_with_old_value_items(false), returning_result(nullptr), save_protocol(nullptr) {} enum_sql_command sql_command_code() const override { - return orig_multitable ? SQLCOM_UPDATE_MULTI : SQLCOM_UPDATE; + return m_sql_command_code; } + bool set_returning_into_result(select_dumpvar *res) override; + DML_prelocking_strategy *get_dml_prelocking_strategy() override { return &multiupdate_prelocking_strategy; @@ -65,8 +68,16 @@ class Sql_cmd_update final : public Sql_cmd_dml bool is_multitable() const { return multitable; } + void set_sql_command_code(enum_sql_command cmd) + { + DBUG_ASSERT(cmd == SQLCOM_UPDATE || cmd == SQLCOM_UPDATE_MULTI); + m_sql_command_code= cmd; + }; + void set_as_multitable() { multitable= true; } + void set_with_old_value_items() { m_with_old_value_items= true; } + void get_dml_stat (ha_rows &found, ha_rows &changed) override { @@ -97,8 +108,13 @@ class Sql_cmd_update final : public Sql_cmd_dml */ bool update_single_table(THD *thd); - /* Original value of the 'multitable' flag set by constructor */ - const bool orig_multitable; + /* + Either SQLCMD_UPDATE or SQLCMD_UPDATE_MULTI. + Single-table updates transformed into multi-table updates + preserve SQLCMD_UPDATE: + UPDATE t1 SET a=a+1 WHERE a=(SELECT MAX(a) FROM t1); + */ + enum_sql_command m_sql_command_code; /* True if the statement is a multi-table update or converted to such. @@ -106,6 +122,9 @@ class Sql_cmd_update final : public Sql_cmd_dml is supposed to be converted to multi-table update. */ bool multitable; + + bool m_with_old_value_items; // Has OLD_VALUE(col) + select_result *returning_result; /* The prelocking strategy used when opening the used tables */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c2fe9974c42df..0bd33f51fcfe9 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1898,7 +1898,10 @@ rule: handler_rkey_function handler_read_or_scan single_multi opt_wild opt_and - select_var_list select_var_list_init help + select_var_list select_var_list_init + into_destination_select_var_list_init + opt_into_select_var_list_init + help opt_extended_describe shutdown opt_format_json prepare execute deallocate @@ -10052,6 +10055,10 @@ expr: my_error(ER_WRONG_USAGE, MYF(0), "OLD_VALUE", "non-UPDATE"); MYSQL_YYABORT; } + Sql_cmd_update *update= dynamic_cast( + lex->m_sql_cmd); + DBUG_ASSERT(update); + update->set_with_old_value_items(); $$= new (thd->mem_root) Item_old_field(thd, &lex->current_select->context, @@ -13992,13 +13999,22 @@ into_destination: MYSQL_YYABORT; } } - | select_var_list_init + | into_destination_select_var_list_init + ; + +into_destination_select_var_list_init: + select_var_list_init { Lex->uncacheable(UNCACHEABLE_SIDEEFFECT); status_var_increment(thd->status_var.feature_into_variable); } ; +opt_into_select_var_list_init: + /* empty */ { } + | INTO into_destination_select_var_list_init { } + ; + /* DO statement */ @@ -14522,17 +14538,20 @@ update: Lex->first_select_lex()->master_unit()->set_with_clause($1); if ($1) $1->attach_to(Lex->first_select_lex()); + if (!(Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_update())) + MYSQL_YYABORT; } opt_low_priority opt_ignore update_table_list SET update_list { - bool is_multiupdate= false; - LEX *lex= Lex; SELECT_LEX *slex= Lex->first_select_lex(); if (slex->table_list.elements > 1) { Lex->sql_command= SQLCOM_UPDATE_MULTI; - is_multiupdate= true; + Sql_cmd_update *cmd= static_cast(Lex->m_sql_cmd); + DBUG_ASSERT(cmd); + cmd->set_sql_command_code(SQLCOM_UPDATE_MULTI); + cmd->set_as_multitable(); } else if (slex->get_table_list()->derived) { @@ -14541,9 +14560,6 @@ update: slex->get_table_list()->alias.str, "UPDATE"); MYSQL_YYABORT; } - if (!(lex->m_sql_cmd= - new (thd->mem_root) Sql_cmd_update(is_multiupdate))) - MYSQL_YYABORT; /* In case of multi-update setting write lock for all tables may be too pessimistic. We will decrease lock level if possible @@ -14808,6 +14824,12 @@ opt_returning: std::swap(thd->lex->returning()->returning_list, thd->lex->returning()->item_list); } + opt_into_select_var_list_init + { + if (Lex->set_returning_into_result( + dynamic_cast(Lex->result))) + MYSQL_YYABORT; + } ; opt_wild: