From 46dc7bdf1d243614b8ad3a00e20d7b840a8d3973 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Sat, 8 Oct 2016 13:06:15 +0400 Subject: [PATCH] MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions MDEV-10867 PREPARE..EXECUTE is not consistent about non-ASCII characters --- .../binlog_tests/ctype_ucs_binlog.result | 212 ++++++++++++++++++ .../extra/binlog_tests/ctype_ucs_binlog.test | 37 +++ mysql-test/r/ctype_ucs.result | 55 ++++- mysql-test/r/ctype_utf8mb4.result | 22 ++ mysql-test/r/ps.result | 145 ++++++++++++ .../binlog/r/binlog_row_ctype_ucs.result | 183 +++++++++++++++ .../binlog/r/binlog_stm_ctype_ucs.result | 171 ++++++++++++++ mysql-test/t/ctype_ucs.test | 34 ++- mysql-test/t/ctype_utf8mb4.test | 15 ++ mysql-test/t/ps.test | 140 ++++++++++++ sql/sql_lex.h | 10 +- sql/sql_parse.cc | 1 + sql/sql_prepare.cc | 180 ++++++++------- sql/sql_yacc.yy | 20 +- 14 files changed, 1120 insertions(+), 105 deletions(-) create mode 100644 mysql-test/extra/binlog_tests/ctype_ucs_binlog.result diff --git a/mysql-test/extra/binlog_tests/ctype_ucs_binlog.result b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.result new file mode 100644 index 00000000000..c358cfcd4f2 --- /dev/null +++ b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.result @@ -0,0 +1,212 @@ +SET TIMESTAMP=10000; +create table t2 (c char(30)) charset=ucs2; +set @v=convert('abc' using ucs2); +reset master; +insert into t2 values (@v); +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # User var # # @`v`=_ucs2 X'006100620063' COLLATE ucs2_general_ci +master-bin.000001 # Query # # use `test`; insert into t2 values (@v) +master-bin.000001 # Query # # COMMIT +flush logs; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +ROLLBACK/*!*/; +BEGIN +/*!*/; +SET @`v`:=_ucs2 X'006100620063' COLLATE `ucs2_general_ci`/*!*/; +use `test`/*!*/; +SET TIMESTAMP=10000/*!*/; +SET @@session.pseudo_thread_id=999999999/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/; +SET @@session.sql_mode=1342177280/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +insert into t2 values (@v) +/*!*/; +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +drop table t2; +# +# Start of 10.2 tests +# +# +# MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +FLUSH LOGS; +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8); +EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''ä(i1)'')'; +EXECUTE IMMEDIATE CONVERT('INSERT INTO t1 VALUES (''ä(i2)'')' USING ucs2); +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(i3)'')' USING ucs2); +EXECUTE IMMEDIATE @stmt; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (''ä(p1)'')'; +EXECUTE stmt; +PREPARE stmt FROM CONVERT('INSERT INTO t1 VALUES (''ä(p2)'')' USING ucs2); +EXECUTE stmt; +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(p3)'')' USING ucs2); +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +SELECT * FROM t1; +a +ä(i1) +ä(i2) +ä(i3) +ä(p1) +ä(p2) +ä(p3) +DROP TABLE t1; +FLUSH LOGS; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 700101 6:46:40 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Gtid list [#-#-#] +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000002 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000003 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100101 SET @@session.skip_parallel_replication=0*//*!*/; +/*!100001 SET @@session.gtid_domain_id=#*//*!*/; +/*!100001 SET @@session.server_id=#*//*!*/; +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +use `test`/*!*/; +SET TIMESTAMP=10000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/; +SET @@session.sql_mode=1342177280/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C utf8 *//*!*/; +SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8) +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i1)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i2)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i3)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p1)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p2)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p3)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +DROP TABLE `t1` /* generated by server */ +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Rotate to master-bin.000004 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +# +# End of 10.2 tests +# diff --git a/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test index 733ad05b0be..819aaa87a8f 100644 --- a/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test +++ b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test @@ -20,3 +20,40 @@ let $MYSQLD_DATADIR= `select @@datadir`; drop table t2; # End of 4.1 tests + + +--echo # +--echo # Start of 10.2 tests +--echo # + +--echo # +--echo # MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # + +FLUSH LOGS; +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8); +EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''ä(i1)'')'; +EXECUTE IMMEDIATE CONVERT('INSERT INTO t1 VALUES (''ä(i2)'')' USING ucs2); +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(i3)'')' USING ucs2); +EXECUTE IMMEDIATE @stmt; + +PREPARE stmt FROM 'INSERT INTO t1 VALUES (''ä(p1)'')'; +EXECUTE stmt; +PREPARE stmt FROM CONVERT('INSERT INTO t1 VALUES (''ä(p2)'')' USING ucs2); +EXECUTE stmt; +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(p3)'')' USING ucs2); +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SELECT * FROM t1; +DROP TABLE t1; +FLUSH LOGS; +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /# at [0-9]*/# at #/ /(exec_time=|end_log_pos |Xid = |thread_id=|server id |table id |mapped to number )[0-9]+/\1#/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /GTID [0-9]+-[0-9]+-[0-9]+/GTID #-#-#/ /Gtid list [[][0-9]+-[0-9]+-[0-9]+[\]]/Gtid list [#-#-#]/ /session[.](gtid_domain_id|server_id|gtid_seq_no)=[0-9]+/session.\1=#/ +--exec $MYSQL_BINLOG --base64-output=decode-rows -vv $MYSQLD_DATADIR/master-bin.000003 + +--echo # +--echo # End of 10.2 tests +--echo # diff --git a/mysql-test/r/ctype_ucs.result b/mysql-test/r/ctype_ucs.result index 91860cde1db..9902a91a2bf 100644 --- a/mysql-test/r/ctype_ucs.result +++ b/mysql-test/r/ctype_ucs.result @@ -6230,7 +6230,60 @@ COLLATION("a") ucs2_general_ci SET @stmt='SELECT COLLATION("a")'; EXECUTE IMMEDIATE @stmt; -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 '' at line 1 +COLLATION("a") +ucs2_general_ci +# +# MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +SET NAMES utf8, collation_connection=ucs2_bin; +SET @stmt='SELECT COLLATION(''a'')'; +EXECUTE IMMEDIATE @stmt; +COLLATION('a') +ucs2_bin +SET NAMES utf8, character_set_connection=ucs2; +SET @stmt='SELECT COLLATION(''a'')'; +EXECUTE IMMEDIATE @stmt; +COLLATION('a') +ucs2_general_ci +EXECUTE IMMEDIATE CONCAT('SELECT ''a'' FROM DUAL'); +a +a +SELECT HEX('aä') FROM DUAL; +HEX('aä') +006100E4 +EXECUTE IMMEDIATE 'SELECT HEX(''aä'') FROM DUAL'; +HEX('aä') +006100E4 +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM DUAL'); +HEX('aä') +006100E4 +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', 'DUAL'); +HEX('aä') +006100E4 +PREPARE stmt FROM 'SELECT HEX(''aä'') FROM DUAL'; +EXECUTE stmt; +HEX('aä') +006100E4 +DEALLOCATE PREPARE stmt; +SET @table='DUAL'; +SELECT HEX(@table); +HEX(@table) +004400550041004C +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', @table); +HEX('aä') +006100E4 +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', CONVERT(@table USING utf8)); +HEX('aä') +006100E4 +SET @stmt='SELECT HEX(''aä'') FROM DUAL'; +EXECUTE IMMEDIATE @stmt; +HEX('aä') +006100E4 +PREPARE stmt FROM @stmt; +EXECUTE stmt; +HEX('aä') +006100E4 +DEALLOCATE PREPARE stmt; # # End of 10.2 tests # diff --git a/mysql-test/r/ctype_utf8mb4.result b/mysql-test/r/ctype_utf8mb4.result index c75b76964a2..98c9c7f6f8b 100644 --- a/mysql-test/r/ctype_utf8mb4.result +++ b/mysql-test/r/ctype_utf8mb4.result @@ -4014,5 +4014,27 @@ DROP TABLE t1; # SET STORAGE_ENGINE=Default; # +# MDEV-10867 PREPARE..EXECUTE is not consistent about non-ASCII characters +# +SET NAMES utf8mb4; +SELECT '😎' AS c; +c +😎 +SET @src='SELECT ''😎'' AS c'; +PREPARE stmt FROM @src; +EXECUTE stmt; +c +😎 +EXECUTE IMMEDIATE @src; +c +😎 +PREPARE stmt FROM 'SELECT ''😎'' AS c'; +EXECUTE stmt; +c +😎 +EXECUTE IMMEDIATE 'SELECT ''😎'' AS c'; +c +😎 +# # End of 10.2 tests # diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index cf2cbce8f93..5150ee6c2cd 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -4502,5 +4502,150 @@ DROP FUNCTION get_status_var; # End of MDEV-10585 EXECUTE IMMEDIATE statement # # +# MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +# +# Testing erroneous and diallowed prepare source +# +EXECUTE IMMEDIATE CONCAT(_latin1'SELECT 1 AS c FROM ', _latin2 'DUAL'); +ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin2_general_ci,COERCIBLE) for operation 'concat' +PREPARE stmt FROM CONCAT(_latin1'SELECT 1 AS c FROM ', _latin2 'DUAL'); +ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin2_general_ci,COERCIBLE) for operation 'concat' +EXECUTE IMMEDIATE (SELECT 'SELECT 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 'SELECT 'SELECT 1')' at line 1 +PREPARE stmt FROM (SELECT 'SELECT 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 'SELECT 'SELECT 1')' at line 1 +EXECUTE IMMEDIATE a; +ERROR 42S22: Unknown column 'a' in 'field list' +PREPARE stmt FROM a; +ERROR 42S22: Unknown column 'a' in 'field list' +EXECUTE IMMEDIATE NULL; +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 'NULL' at line 1 +PREPARE stmt FROM NULL; +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 'NULL' at line 1 +EXECUTE IMMEDIATE CONCAT(NULL); +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 'NULL' at line 1 +PREPARE stmt FROM CONCAT(NULL); +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 'NULL' at line 1 +EXECUTE IMMEDIATE ? USING 'SELECT 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 '? USING 'SELECT 1'' at line 1 +EXECUTE IMMEDIATE 10; +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 '10' at line 1 +EXECUTE IMMEDIATE TIME'10:20:30'; +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 '10:20:30' at line 1 +EXECUTE IMMEDIATE ROW('SELECT 1','SELECT 2'); +ERROR 21000: Operand should contain 1 column(s) +EXECUTE IMMEDIATE MAX('SELECT 1 AS c'); +ERROR HY000: Invalid use of group function +EXECUTE IMMEDIATE DEFAULT(a); +ERROR 42S22: Unknown column 'a' in 'field list' +EXECUTE IMMEDIATE VALUES(a); +ERROR 42S22: Unknown column 'a' in 'field list' +CREATE FUNCTION f1() RETURNS VARCHAR(64) RETURN 't1'; +EXECUTE IMMEDIATE f1(); +ERROR 42000: EXECUTE IMMEDIATE does not support subqueries or stored functions +PREPARE stmt FROM f1(); +ERROR 42000: PREPARE..FROM does not support subqueries or stored functions +DROP FUNCTION f1; +EXECUTE IMMEDIATE non_existent(); +ERROR 42000: EXECUTE IMMEDIATE does not support subqueries or stored functions +# +# Testing literals in prepare source +# +EXECUTE IMMEDIATE N'SELECT 1 AS c'; +c +1 +EXECUTE IMMEDIATE _latin1'SELECT 1 AS c'; +c +1 +EXECUTE IMMEDIATE 'SELECT ' '1' ' AS c' ' FROM ' 'DUAL'; +c +1 +EXECUTE IMMEDIATE 0x53454C4543542031 /*This is 'SELECT 1'*/; +1 +1 +# +# Testing user variables in prepare source +# +SET @stmt='SELECT 1 AS c FROM DUAL'; +EXECUTE IMMEDIATE @stmt; +c +1 +PREPARE stmt FROM @stmt; +EXECUTE stmt; +c +1 +DEALLOCATE PREPARE stmt; +SET @table_name='DUAL'; +EXECUTE IMMEDIATE CONCAT('SELECT 1 AS a FROM ', @table_name); +a +1 +PREPARE stmt FROM CONCAT('SELECT 1 AS a FROM ', @table_name); +EXECUTE stmt; +a +1 +DEALLOCATE PREPARE stmt; +# +# Testing SP parameters and variables in prepare source +# +CREATE PROCEDURE p1(table_name VARCHAR(64)) +BEGIN +EXECUTE IMMEDIATE CONCAT('SELECT 1 AS c FROM ', table_name); +END; +$$ +CALL p1('DUAL'); +c +1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE table_name VARCHAR(64) DEFAULT 'DUAL'; +EXECUTE IMMEDIATE CONCAT('SELECT 1 AS c FROM ', table_name); +END; +$$ +CALL p1(); +c +1 +DROP PROCEDURE p1; +# +# Testing complex expressions +# +EXECUTE IMMEDIATE CONVERT('SELECT 1 AS c' USING utf8); +c +1 +EXECUTE IMMEDIATE CAST('SELECT 1 AS c' AS CHAR); +c +1 +EXECUTE IMMEDIATE _latin1'SELECT 1 AS c' COLLATE latin1_bin; +c +1 +EXECUTE IMMEDIATE (((('SELECT 1 AS c')))); +c +1 +EXECUTE IMMEDIATE CASE WHEN 1>2 THEN 'SELECT 1 AS c' ELSE 'SELECT 2 AS c' END; +c +2 +EXECUTE IMMEDIATE TRIM('SELECT 1 AS c'); +c +1 +EXECUTE IMMEDIATE SUBSTRING('SELECT 1 AS c' FROM 1); +c +1 +EXECUTE IMMEDIATE COALESCE(NULL, 'SELECT 1 AS c'); +c +1 +# +# Testing SET STATEMENT and system variables +# +CREATE TABLE t1 (a INT); +SET STATEMENT max_sort_length=1025 FOR EXECUTE IMMEDIATE CONCAT('INSERT INTO t1 VALUES (', @@max_sort_length, ')'); +SELECT * FROM t1; +a +1025 +DROP TABLE t1; +# +# End of MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +# # End of 10.2 tests # diff --git a/mysql-test/suite/binlog/r/binlog_row_ctype_ucs.result b/mysql-test/suite/binlog/r/binlog_row_ctype_ucs.result index 6a8e18ae5bf..848ef7c5e49 100644 --- a/mysql-test/suite/binlog/r/binlog_row_ctype_ucs.result +++ b/mysql-test/suite/binlog/r/binlog_row_ctype_ucs.result @@ -34,3 +34,186 @@ ROLLBACK /* added by mysqlbinlog */; /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; drop table t2; +# +# Start of 10.2 tests +# +# +# MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +FLUSH LOGS; +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8); +EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''ä(i1)'')'; +EXECUTE IMMEDIATE CONVERT('INSERT INTO t1 VALUES (''ä(i2)'')' USING ucs2); +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(i3)'')' USING ucs2); +EXECUTE IMMEDIATE @stmt; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (''ä(p1)'')'; +EXECUTE stmt; +PREPARE stmt FROM CONVERT('INSERT INTO t1 VALUES (''ä(p2)'')' USING ucs2); +EXECUTE stmt; +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(p3)'')' USING ucs2); +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +SELECT * FROM t1; +a +ä(i1) +ä(i2) +ä(i3) +ä(p1) +ä(p2) +ä(p3) +DROP TABLE t1; +FLUSH LOGS; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 700101 6:46:40 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Gtid list [#-#-#] +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000002 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000003 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100101 SET @@session.skip_parallel_replication=0*//*!*/; +/*!100001 SET @@session.gtid_domain_id=#*//*!*/; +/*!100001 SET @@session.server_id=#*//*!*/; +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +use `test`/*!*/; +SET TIMESTAMP=10000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/; +SET @@session.sql_mode=1342177280/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C utf8 *//*!*/; +SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8) +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(i1)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(i2)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(i3)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(p1)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(p2)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number # +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F +### INSERT INTO `test`.`t1` +### SET +### @1='ä(p3)' /* VARSTRING(30) meta=30 nullable=1 is_null=0 */ +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +DROP TABLE `t1` /* generated by server */ +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Rotate to master-bin.000004 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +# +# End of 10.2 tests +# diff --git a/mysql-test/suite/binlog/r/binlog_stm_ctype_ucs.result b/mysql-test/suite/binlog/r/binlog_stm_ctype_ucs.result index a3bcf6cd79d..c358cfcd4f2 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_ctype_ucs.result +++ b/mysql-test/suite/binlog/r/binlog_stm_ctype_ucs.result @@ -39,3 +39,174 @@ ROLLBACK /* added by mysqlbinlog */; /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; drop table t2; +# +# Start of 10.2 tests +# +# +# MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +# +FLUSH LOGS; +SET NAMES utf8; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8); +EXECUTE IMMEDIATE 'INSERT INTO t1 VALUES (''ä(i1)'')'; +EXECUTE IMMEDIATE CONVERT('INSERT INTO t1 VALUES (''ä(i2)'')' USING ucs2); +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(i3)'')' USING ucs2); +EXECUTE IMMEDIATE @stmt; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (''ä(p1)'')'; +EXECUTE stmt; +PREPARE stmt FROM CONVERT('INSERT INTO t1 VALUES (''ä(p2)'')' USING ucs2); +EXECUTE stmt; +SET @stmt=CONVERT('INSERT INTO t1 VALUES (''ä(p3)'')' USING ucs2); +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +SELECT * FROM t1; +a +ä(i1) +ä(i2) +ä(i3) +ä(p1) +ä(p2) +ä(p3) +DROP TABLE t1; +FLUSH LOGS; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 700101 6:46:40 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Gtid list [#-#-#] +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000002 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Binlog checkpoint master-bin.000003 +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100101 SET @@session.skip_parallel_replication=0*//*!*/; +/*!100001 SET @@session.gtid_domain_id=#*//*!*/; +/*!100001 SET @@session.server_id=#*//*!*/; +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +use `test`/*!*/; +SET TIMESTAMP=10000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/; +SET @@session.sql_mode=1342177280/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C utf8 *//*!*/; +SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8) +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i1)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i2)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(i3)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p1)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p2)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +BEGIN +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +INSERT INTO t1 VALUES ('ä(p3)') +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +COMMIT +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX GTID #-#-# ddl +/*!100001 SET @@session.gtid_seq_no=#*//*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=10000/*!*/; +DROP TABLE `t1` /* generated by server */ +/*!*/; +# at # +#700101 6:46:40 server id # end_log_pos # CRC32 XXX Rotate to master-bin.000004 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +# +# End of 10.2 tests +# diff --git a/mysql-test/t/ctype_ucs.test b/mysql-test/t/ctype_ucs.test index 2090f35fa70..cad82222ac7 100644 --- a/mysql-test/t/ctype_ucs.test +++ b/mysql-test/t/ctype_ucs.test @@ -980,12 +980,40 @@ let $coll_pad='ucs2_bin'; SET character_set_connection=ucs2; EXECUTE IMMEDIATE 'SELECT COLLATION("a")'; -# We don't support character sets with mbminlen>1 in the parser yet -# Returning "syntax error" is fine SET @stmt='SELECT COLLATION("a")'; ---error ER_PARSE_ERROR EXECUTE IMMEDIATE @stmt; +--echo # +--echo # MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # +SET NAMES utf8, collation_connection=ucs2_bin; +SET @stmt='SELECT COLLATION(''a'')'; +EXECUTE IMMEDIATE @stmt; + +SET NAMES utf8, character_set_connection=ucs2; +SET @stmt='SELECT COLLATION(''a'')'; +EXECUTE IMMEDIATE @stmt; + +EXECUTE IMMEDIATE CONCAT('SELECT ''a'' FROM DUAL'); + +SELECT HEX('aä') FROM DUAL; +EXECUTE IMMEDIATE 'SELECT HEX(''aä'') FROM DUAL'; +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM DUAL'); +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', 'DUAL'); +PREPARE stmt FROM 'SELECT HEX(''aä'') FROM DUAL'; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @table='DUAL'; +SELECT HEX(@table); +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', @table); +EXECUTE IMMEDIATE CONCAT('SELECT HEX(''aä'') FROM ', CONVERT(@table USING utf8)); +SET @stmt='SELECT HEX(''aä'') FROM DUAL'; +EXECUTE IMMEDIATE @stmt; +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + --echo # --echo # End of 10.2 tests --echo # diff --git a/mysql-test/t/ctype_utf8mb4.test b/mysql-test/t/ctype_utf8mb4.test index 12fb5455e20..1b34a8176f3 100644 --- a/mysql-test/t/ctype_utf8mb4.test +++ b/mysql-test/t/ctype_utf8mb4.test @@ -1964,6 +1964,21 @@ let $coll='utf8mb4_nopad_bin'; let $coll_pad='utf8mb4_bin'; --source include/ctype_pad_all_engines.inc +--echo # +--echo # MDEV-10867 PREPARE..EXECUTE is not consistent about non-ASCII characters +--echo # +SET NAMES utf8mb4; +SELECT '😎' AS c; + +SET @src='SELECT ''😎'' AS c'; +PREPARE stmt FROM @src; +EXECUTE stmt; +EXECUTE IMMEDIATE @src; + +PREPARE stmt FROM 'SELECT ''😎'' AS c'; +EXECUTE stmt; +EXECUTE IMMEDIATE 'SELECT ''😎'' AS c'; + --echo # --echo # End of 10.2 tests --echo # diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 89db0a91bd0..cd483093632 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -4045,6 +4045,146 @@ DROP FUNCTION get_status_var; --echo # End of MDEV-10585 EXECUTE IMMEDIATE statement --echo # +--echo # +--echo # MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # + +--echo # +--echo # Testing erroneous and diallowed prepare source +--echo # + +--error ER_CANT_AGGREGATE_2COLLATIONS +EXECUTE IMMEDIATE CONCAT(_latin1'SELECT 1 AS c FROM ', _latin2 'DUAL'); +--error ER_CANT_AGGREGATE_2COLLATIONS +PREPARE stmt FROM CONCAT(_latin1'SELECT 1 AS c FROM ', _latin2 'DUAL'); + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE (SELECT 'SELECT 1'); +--error ER_PARSE_ERROR +PREPARE stmt FROM (SELECT 'SELECT 1'); + +--error ER_BAD_FIELD_ERROR +EXECUTE IMMEDIATE a; +--error ER_BAD_FIELD_ERROR +PREPARE stmt FROM a; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE NULL; +--error ER_PARSE_ERROR +PREPARE stmt FROM NULL; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE CONCAT(NULL); +--error ER_PARSE_ERROR +PREPARE stmt FROM CONCAT(NULL); + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE ? USING 'SELECT 1'; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE 10; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE TIME'10:20:30'; + +--error ER_OPERAND_COLUMNS +EXECUTE IMMEDIATE ROW('SELECT 1','SELECT 2'); + +--error ER_INVALID_GROUP_FUNC_USE +EXECUTE IMMEDIATE MAX('SELECT 1 AS c'); + +--error ER_BAD_FIELD_ERROR +EXECUTE IMMEDIATE DEFAULT(a); + +--error ER_BAD_FIELD_ERROR +EXECUTE IMMEDIATE VALUES(a); + + +CREATE FUNCTION f1() RETURNS VARCHAR(64) RETURN 't1'; +--error ER_SUBQUERIES_NOT_SUPPORTED +EXECUTE IMMEDIATE f1(); +--error ER_SUBQUERIES_NOT_SUPPORTED +PREPARE stmt FROM f1(); +DROP FUNCTION f1; + +--error ER_SUBQUERIES_NOT_SUPPORTED +EXECUTE IMMEDIATE non_existent(); + + +--echo # +--echo # Testing literals in prepare source +--echo # +EXECUTE IMMEDIATE N'SELECT 1 AS c'; +EXECUTE IMMEDIATE _latin1'SELECT 1 AS c'; +EXECUTE IMMEDIATE 'SELECT ' '1' ' AS c' ' FROM ' 'DUAL'; +EXECUTE IMMEDIATE 0x53454C4543542031 /*This is 'SELECT 1'*/; + +--echo # +--echo # Testing user variables in prepare source +--echo # + +SET @stmt='SELECT 1 AS c FROM DUAL'; +EXECUTE IMMEDIATE @stmt; +PREPARE stmt FROM @stmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @table_name='DUAL'; +EXECUTE IMMEDIATE CONCAT('SELECT 1 AS a FROM ', @table_name); +PREPARE stmt FROM CONCAT('SELECT 1 AS a FROM ', @table_name); +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +--echo # +--echo # Testing SP parameters and variables in prepare source +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1(table_name VARCHAR(64)) +BEGIN + EXECUTE IMMEDIATE CONCAT('SELECT 1 AS c FROM ', table_name); +END; +$$ +DELIMITER ;$$ +CALL p1('DUAL'); +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE table_name VARCHAR(64) DEFAULT 'DUAL'; + EXECUTE IMMEDIATE CONCAT('SELECT 1 AS c FROM ', table_name); +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; + + +--echo # +--echo # Testing complex expressions +--echo # +EXECUTE IMMEDIATE CONVERT('SELECT 1 AS c' USING utf8); +EXECUTE IMMEDIATE CAST('SELECT 1 AS c' AS CHAR); +EXECUTE IMMEDIATE _latin1'SELECT 1 AS c' COLLATE latin1_bin; +EXECUTE IMMEDIATE (((('SELECT 1 AS c')))); +EXECUTE IMMEDIATE CASE WHEN 1>2 THEN 'SELECT 1 AS c' ELSE 'SELECT 2 AS c' END; +EXECUTE IMMEDIATE TRIM('SELECT 1 AS c'); +EXECUTE IMMEDIATE SUBSTRING('SELECT 1 AS c' FROM 1); +EXECUTE IMMEDIATE COALESCE(NULL, 'SELECT 1 AS c'); + +--echo # +--echo # Testing SET STATEMENT and system variables +--echo # +CREATE TABLE t1 (a INT); +SET STATEMENT max_sort_length=1025 FOR EXECUTE IMMEDIATE CONCAT('INSERT INTO t1 VALUES (', @@max_sort_length, ')'); +SELECT * FROM t1; +DROP TABLE t1; + +--echo # +--echo # End of MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # + --echo # --echo # End of 10.2 tests --echo # diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 7cfe6448756..63feef92445 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2720,13 +2720,8 @@ public: TABLE_LIST *create_last_non_select_table; /* Prepared statements SQL syntax:*/ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ - /* - Prepared statement query text or name of variable that holds the - prepared statement (in PREPARE ... queries) - */ - LEX_STRING prepared_stmt_code; - /* If true, prepared_stmt_code is a name of variable that holds the query */ - bool prepared_stmt_code_is_varref; + /* PREPARE or EXECUTE IMMEDIATE source expression */ + Item *prepared_stmt_code; /* Names of user variables holding parameters (in EXECUTE) */ List prepared_stmt_params; sp_head *sphead; @@ -3007,6 +3002,7 @@ public: void set_last_field_type(const Lex_field_type_st &type); bool set_bincmp(CHARSET_INFO *cs, bool bin); + bool get_dynamic_sql_string(LEX_CSTRING *dst, String *buffer); bool prepared_stmt_params_fix_fields(THD *thd) { // Fix Items in the EXECUTE..USING list diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 69637f87b8e..f4ac33d77fa 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -704,6 +704,7 @@ void init_update_queries(void) CF_CAN_GENERATE_ROW_EVENTS | CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_EXECUTE_IMMEDIATE]= CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_COMPOUND]= CF_CAN_GENERATE_ROW_EVENTS; /* diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 99000573fb8..ac398338679 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2601,95 +2601,99 @@ end: } /** - Get an SQL statement text from a user variable or from plain text. + Get an SQL statement from an item in lex->prepared_stmt_code. - If the statement is plain text, just assign the - pointers, otherwise allocate memory in thd->mem_root and copy - the contents of the variable, possibly with character - set conversion. + This function can return pointers to very different memory classes: + - a static string "NULL", if the item returned NULL + - the result of prepare_stmt_code->val_str(), if no conversion was needed + - a thd->mem_root allocated string with the result of + prepare_stmt_code->val_str() converted to @@collation_connection, + if conversion was needed - @param[in] lex main lex - @param[out] query_len length of the SQL statement (is set only - in case of success) + The caller must dispose the result before the life cycle of "buffer" ends. + As soon as buffer's destructor is called, the value is not valid any more! - @retval - non-zero success - @retval - 0 in case of error (out of memory) + mysql_sql_stmt_prepare() and mysql_sql_stmt_execute_immediate() + call get_dynamic_sql_string() and then call respectively + Prepare_statement::prepare() and Prepare_statment::execute_immediate(), + who store the returned result into its permanent location using + alloc_query(). "buffer" is still not destructed at that time. + + @param[out] dst the result is stored here + @param[inout] buffer + + @retval false on success + @retval true on error (out of memory) */ -static const char *get_dynamic_sql_string(LEX *lex, uint *query_len) +bool LEX::get_dynamic_sql_string(LEX_CSTRING *dst, String *buffer) { - THD *thd= lex->thd; - char *query_str= 0; + if (prepared_stmt_code->fix_fields(thd, NULL) || + prepared_stmt_code->check_cols(1)) + return true; - if (lex->prepared_stmt_code_is_varref) + const String *str= prepared_stmt_code->val_str(buffer); + if (prepared_stmt_code->null_value) { - /* This is PREPARE stmt FROM or EXECUTE IMMEDIATE @var. */ - String str; - CHARSET_INFO *to_cs= thd->variables.collation_connection; - bool needs_conversion; - user_var_entry *entry; - String *var_value= &str; - uint32 unused, len; /* - Convert @var contents to string in connection character set. Although - it is known that int/real/NULL value cannot be a valid query we still - convert it for error messages to be uniform. + Prepare source was NULL, so we need to set "str" to + something reasonable to get a readable error message during parsing */ - if ((entry= - (user_var_entry*)my_hash_search(&thd->user_vars, - (uchar*)lex->prepared_stmt_code.str, - lex->prepared_stmt_code.length)) - && entry->value) - { - bool is_var_null; - var_value= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC); - /* - NULL value of variable checked early as entry->value so here - we can't get NULL in normal conditions - */ - DBUG_ASSERT(!is_var_null); - if (!var_value) - goto end; - } - else - { - /* - variable absent or equal to NULL, so we need to set variable to - something reasonable to get a readable error message during parsing - */ - str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1); - } - - needs_conversion= String::needs_conversion(var_value->length(), - var_value->charset(), to_cs, - &unused); - - len= (needs_conversion ? var_value->length() * to_cs->mbmaxlen : - var_value->length()); - if (!(query_str= (char*) alloc_root(thd->mem_root, len+1))) - goto end; - - if (needs_conversion) - { - uint dummy_errors; - len= copy_and_convert(query_str, len, to_cs, var_value->ptr(), - var_value->length(), var_value->charset(), - &dummy_errors); - } - else - memcpy(query_str, var_value->ptr(), var_value->length()); - query_str[len]= '\0'; // Safety (mostly for debug) - *query_len= len; + dst->str= "NULL"; + dst->length= 4; + return false; } - else + + /* + Character set conversion notes: + + 1) When PREPARE or EXECUTE IMMEDIATE are used with string literals: + PREPARE stmt FROM 'SELECT ''str'''; + EXECUTE IMMEDIATE 'SELECT ''str'''; + it's very unlikely that any conversion will happen below, because + @@character_set_client and @@collation_connection are normally + set to the same CHARSET_INFO pointer. + + In tricky environments when @@collation_connection is set to something + different from @@character_set_client, double conversion may happen: + - When the parser scans the string literal + (sql_yacc.yy rules "prepare_src" -> "expr" -> ... -> "text_literal") + it will convert 'str' from @@character_set_client to + @@collation_connection. + - Then in the code below will convert 'str' from @@collation_connection + back to @@character_set_client. + + 2) When PREPARE or EXECUTE IMMEDIATE is used with a user variable, + it should work about the same way, because user variables are usually + assigned like this: + SET @str='str'; + and thus have the same character set with string literals. + + 3) When PREPARE or EXECUTE IMMEDIATE is used with some + more complex expression, conversion will depend on this expression. + For example, a concatenation of string literals: + EXECUTE IMMEDIATE 'SELECT * FROM'||'t1'; + should work the same way with just a single literal, + so no conversion normally. + */ + CHARSET_INFO *to_cs= thd->variables.character_set_client; + + uint32 unused; + if (String::needs_conversion(str->length(), str->charset(), to_cs, &unused)) { - query_str= lex->prepared_stmt_code.str; - *query_len= lex->prepared_stmt_code.length; + if (!(dst->str= sql_strmake_with_convert(thd, str->ptr(), str->length(), + str->charset(), UINT_MAX32, + to_cs, &dst->length))) + { + dst->length= 0; + return true; + } + DBUG_ASSERT(dst->length <= UINT_MAX32); + return false; } -end: - return query_str; + dst->str= str->ptr(); + dst->length= str->length(); + return false; } @@ -2712,8 +2716,7 @@ void mysql_sql_stmt_prepare(THD *thd) LEX *lex= thd->lex; LEX_STRING *name= &lex->prepared_stmt_name; Prepared_statement *stmt; - const char *query; - uint query_len= 0; + LEX_CSTRING query; DBUG_ENTER("mysql_sql_stmt_prepare"); if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) @@ -2731,7 +2734,12 @@ void mysql_sql_stmt_prepare(THD *thd) stmt->deallocate(); } - if (! (query= get_dynamic_sql_string(lex, &query_len)) || + /* + It's important for "buffer" not to be destructed before stmt->prepare()! + See comments in get_dynamic_sql_string(). + */ + StringBuffer<256> buffer; + if (lex->get_dynamic_sql_string(&query, &buffer) || ! (stmt= new Prepared_statement(thd))) { DBUG_VOID_RETURN; /* out of memory */ @@ -2752,7 +2760,7 @@ void mysql_sql_stmt_prepare(THD *thd) DBUG_VOID_RETURN; } - if (stmt->prepare(query, query_len)) + if (stmt->prepare(query.str, (uint) query.length)) { /* Statement map deletes the statement on erase */ thd->stmt_map.erase(stmt); @@ -2771,8 +2779,7 @@ void mysql_sql_stmt_execute_immediate(THD *thd) { LEX *lex= thd->lex; Prepared_statement *stmt; - const char *query; - uint query_len= 0; + LEX_CSTRING query; DBUG_ENTER("mysql_sql_stmt_execute_immediate"); if (lex->prepared_stmt_params_fix_fields(thd)) @@ -2781,15 +2788,20 @@ void mysql_sql_stmt_execute_immediate(THD *thd) /* Prepared_statement is quite large, let's allocate it on the heap rather than on the stack. + + It's important for "buffer" not to be destructed + before stmt->execute_immediate(). + See comments in get_dynamic_sql_string(). */ - if (!(query= get_dynamic_sql_string(lex, &query_len)) || + StringBuffer<256> buffer; + if (lex->get_dynamic_sql_string(&query, &buffer) || !(stmt= new Prepared_statement(thd))) DBUG_VOID_RETURN; // out of memory // See comments on thd->free_list in mysql_sql_stmt_execute() Item *free_list_backup= thd->free_list; thd->free_list= NULL; - (void) stmt->execute_immediate(query, query_len); + (void) stmt->execute_immediate(query.str, (uint) query.length); thd->free_items(); thd->free_list= free_list_backup; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 2b0e722a2b1..6b2dbca75a9 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2223,23 +2223,20 @@ prepare: PREPARE_SYM ident FROM prepare_src { LEX *lex= thd->lex; + if (lex->table_or_sp_used()) + my_yyabort_error((ER_SUBQUERIES_NOT_SUPPORTED, MYF(0), + "PREPARE..FROM")); lex->sql_command= SQLCOM_PREPARE; lex->prepared_stmt_name= $2; } ; prepare_src: - TEXT_STRING_sys + { Lex->expr_allows_subselect= false; } + expr { - LEX *lex= thd->lex; - lex->prepared_stmt_code= $1; - lex->prepared_stmt_code_is_varref= FALSE; - } - | '@' ident_or_text - { - LEX *lex= thd->lex; - lex->prepared_stmt_code= $2; - lex->prepared_stmt_code_is_varref= TRUE; + Lex->prepared_stmt_code= $2; + Lex->expr_allows_subselect= true; } ; @@ -2254,6 +2251,9 @@ execute: {} | EXECUTE_SYM IMMEDIATE_SYM prepare_src { + if (Lex->table_or_sp_used()) + my_yyabort_error((ER_SUBQUERIES_NOT_SUPPORTED, MYF(0), + "EXECUTE IMMEDIATE")); Lex->sql_command= SQLCOM_EXECUTE_IMMEDIATE; } execute_using