summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2016-10-08 13:06:15 +0400
committerAlexander Barkov <bar@mariadb.org>2016-10-08 13:06:15 +0400
commit46dc7bdf1d243614b8ad3a00e20d7b840a8d3973 (patch)
tree619432d02b01aea29b393fc684961cc4916a640d
parente1a212ebbcea6d51a4bc1fe672bc6ff392477a39 (diff)
downloadmariadb-git-46dc7bdf1d243614b8ad3a00e20d7b840a8d3973.tar.gz
MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions
MDEV-10867 PREPARE..EXECUTE is not consistent about non-ASCII characters
-rw-r--r--mysql-test/extra/binlog_tests/ctype_ucs_binlog.result212
-rw-r--r--mysql-test/extra/binlog_tests/ctype_ucs_binlog.test37
-rw-r--r--mysql-test/r/ctype_ucs.result55
-rw-r--r--mysql-test/r/ctype_utf8mb4.result22
-rw-r--r--mysql-test/r/ps.result145
-rw-r--r--mysql-test/suite/binlog/r/binlog_row_ctype_ucs.result183
-rw-r--r--mysql-test/suite/binlog/r/binlog_stm_ctype_ucs.result171
-rw-r--r--mysql-test/t/ctype_ucs.test34
-rw-r--r--mysql-test/t/ctype_utf8mb4.test15
-rw-r--r--mysql-test/t/ps.test140
-rw-r--r--sql/sql_lex.h10
-rw-r--r--sql/sql_parse.cc1
-rw-r--r--sql/sql_prepare.cc176
-rw-r--r--sql/sql_yacc.yy20
14 files changed, 1118 insertions, 103 deletions
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
@@ -1965,5 +1965,20 @@ 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
@@ -4046,5 +4046,145 @@ DROP FUNCTION get_status_var;
--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<Item> 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);
+ dst->str= "NULL";
+ dst->length= 4;
+ return false;
+ }
- 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;
+ /*
+ 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;
- if (needs_conversion)
+ uint32 unused;
+ if (String::needs_conversion(str->length(), str->charset(), to_cs, &unused))
+ {
+ if (!(dst->str= sql_strmake_with_convert(thd, str->ptr(), str->length(),
+ str->charset(), UINT_MAX32,
+ to_cs, &dst->length)))
{
- uint dummy_errors;
- len= copy_and_convert(query_str, len, to_cs, var_value->ptr(),
- var_value->length(), var_value->charset(),
- &dummy_errors);
+ dst->length= 0;
+ return true;
}
- else
- memcpy(query_str, var_value->ptr(), var_value->length());
- query_str[len]= '\0'; // Safety (mostly for debug)
- *query_len= len;
- }
- else
- {
- query_str= lex->prepared_stmt_code.str;
- *query_len= lex->prepared_stmt_code.length;
+ 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 *lex= thd->lex;
- lex->prepared_stmt_code= $1;
- lex->prepared_stmt_code_is_varref= FALSE;
- }
- | '@' ident_or_text
+ { Lex->expr_allows_subselect= false; }
+ expr
{
- 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