Commit 4c45b820 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-10709 Expressions as parameters to Dynamic SQL

parent 8ea2e143
...@@ -4093,3 +4093,201 @@ deallocate prepare stmt; ...@@ -4093,3 +4093,201 @@ deallocate prepare stmt;
SET SESSION sql_mode = @save_sql_mode; SET SESSION sql_mode = @save_sql_mode;
DROP TABLE t1,t2; DROP TABLE t1,t2;
# End of 10.0 tests # End of 10.0 tests
#
# Start of 10.2 tests
#
#
# MDEV-10709 Expressions as parameters to Dynamic SQL
#
#
# Using a simple expressions as an EXECUTE parameter
#
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt USING 10;
?
10
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt USING TO_BASE64('xxx');
?
eHh4
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ?+? FROM DUAL';
EXECUTE stmt USING 10, 10 + 10;
?+?
30
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT CONCAT(?,?) FROM DUAL';
EXECUTE stmt USING 'xxx', CONCAT('yyy','zzz');
CONCAT(?,?)
xxxyyyzzz
DEALLOCATE PREPARE stmt;
#
# Testing disallowed expressions in USING
#
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt 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 'SELECT 1)' at line 1
DEALLOCATE PREPARE stmt;
CREATE FUNCTION f1() RETURNS VARCHAR(10) RETURN 'test';
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt USING f1();
ERROR 42000: EXECUTE..USING does not support subqueries or stored functions
DEALLOCATE PREPARE stmt;
DROP FUNCTION f1;
#
# Testing erroneous expressions in USING
#
PREPARE stmt FROM 'SELECT ?';
EXECUTE stmt USING _latin1'a'=_latin2'a';
ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin2_general_ci,COERCIBLE) for operation '='
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ?';
EXECUTE stmt USING ROW(1,2);
ERROR 21000: Operand should contain 1 column(s)
DEALLOCATE PREPARE stmt;
#
# Creating tables from EXECUTE parameters
#
PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? AS c1 FROM DUAL';
EXECUTE stmt USING 10;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` bigint(21) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING 10.123;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` decimal(5,3) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING 10.123e0;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` double NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_DATE;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP(3);
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` datetime(3) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP(6);
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` datetime(6) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` time DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME(3);
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` time(3) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME(6);
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` time(6) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
DEALLOCATE PREPARE stmt;
#
# Using a user variable as an EXECUTE..USING out parameter
#
CREATE PROCEDURE p1(OUT a INT)
BEGIN
SET a:= 10;
END;
/
SET @a=1;
CALL p1(@a);
SELECT @a;
@a
10
SET @a=2;
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING @a;
SELECT @a;
@a
10
DROP PROCEDURE p1;
#
# Using an SP variable as an EXECUTE..USING out parameter
#
CREATE PROCEDURE p1 (OUT a INT)
BEGIN
SET a=10;
END;
/
CREATE PROCEDURE p2 (OUT a INT)
BEGIN
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING a;
END;
/
SET @a= 1;
CALL p2(@a);
SELECT @a;
@a
10
DROP PROCEDURE p2;
DROP PROCEDURE p1;
#
# Testing re-prepare on a table metadata update between PREPARE and EXECUTE
#
CREATE TABLE t1 (a INT);
CREATE PROCEDURE p1(a INT)
BEGIN
INSERT INTO t1 VALUES (a);
END;
/
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING 10;
SELECT * FROM t1;
a
10
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET NEW.a=NEW.a+1;
EXECUTE stmt USING 20;
SELECT * FROM t1;
a
10
21
DEALLOCATE PREPARE stmt;
DROP PROCEDURE p1;
DROP TABLE t1;
#
# End of MDEV-10709 Expressions as parameters to Dynamic SQL
#
#
# End of 10.2 tests
#
...@@ -27,3 +27,79 @@ master-bin.000001 # Gtid # # BEGIN GTID #-#-# ...@@ -27,3 +27,79 @@ master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; insert into t1 select 100 limit 100 master-bin.000001 # Query # # use `test`; insert into t1 select 100 limit 100
master-bin.000001 # Query # # COMMIT master-bin.000001 # Query # # COMMIT
drop table t1; drop table t1;
#
# MDEV-10709 Expressions as parameters to Dynamic SQL
#
FLUSH LOGS;
SET TIMESTAMP=UNIX_TIMESTAMP('2001-01-02 10:20:30.123456');
CREATE TABLE t1 (a DECIMAL(30,8));
PREPARE stmt FROM 'INSERT INTO t1 VALUES (?)';
EXECUTE stmt USING 10;
EXECUTE stmt USING 11e0;
EXECUTE stmt USING 12.1;
EXECUTE stmt USING '13';
EXECUTE stmt USING CURRENT_DATE;
EXECUTE stmt USING MAKETIME(10,20,30);
EXECUTE stmt USING CURRENT_TIME;
EXECUTE stmt USING CURRENT_TIME(3);
EXECUTE stmt USING CURRENT_TIME(6);
EXECUTE stmt USING CURRENT_TIMESTAMP;
EXECUTE stmt USING CURRENT_TIMESTAMP(3);
EXECUTE stmt USING CURRENT_TIMESTAMP(6);
SELECT * FROM t1;
a
10.00000000
11.00000000
12.10000000
13.00000000
20010102.00000000
102030.00000000
102030.00000000
102030.12300000
102030.12345600
20010102102030.00000000
20010102102030.12300000
20010102102030.12345600
include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000002 # Binlog_checkpoint # # master-bin.000002
master-bin.000002 # Gtid # # GTID #-#-#
master-bin.000002 # Query # # use `test`; CREATE TABLE t1 (a DECIMAL(30,8))
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (10)
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (11)
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (12.1)
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES ('13')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (DATE'2001-01-02')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIME'10:20:30')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIME'10:20:30')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIME'10:20:30.123')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIME'10:20:30.123456')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIMESTAMP'2001-01-02 10:20:30')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIMESTAMP'2001-01-02 10:20:30.123')
master-bin.000002 # Query # # COMMIT
master-bin.000002 # Gtid # # BEGIN GTID #-#-#
master-bin.000002 # Query # # use `test`; INSERT INTO t1 VALUES (TIMESTAMP'2001-01-02 10:20:30.123456')
master-bin.000002 # Query # # COMMIT
DROP TABLE t1;
SET TIMESTAMP=DEFAULT;
...@@ -28,3 +28,29 @@ prepare s from "insert into t1 select 100 limit ?"; ...@@ -28,3 +28,29 @@ prepare s from "insert into t1 select 100 limit ?";
set @a=100; execute s using @a; set @a=100; execute s using @a;
source include/show_binlog_events.inc; source include/show_binlog_events.inc;
drop table t1; drop table t1;
--echo #
--echo # MDEV-10709 Expressions as parameters to Dynamic SQL
--echo #
FLUSH LOGS;
SET TIMESTAMP=UNIX_TIMESTAMP('2001-01-02 10:20:30.123456');
CREATE TABLE t1 (a DECIMAL(30,8));
PREPARE stmt FROM 'INSERT INTO t1 VALUES (?)';
EXECUTE stmt USING 10;
EXECUTE stmt USING 11e0;
EXECUTE stmt USING 12.1;
EXECUTE stmt USING '13';
EXECUTE stmt USING CURRENT_DATE;
EXECUTE stmt USING MAKETIME(10,20,30);
EXECUTE stmt USING CURRENT_TIME;
EXECUTE stmt USING CURRENT_TIME(3);
EXECUTE stmt USING CURRENT_TIME(6);
EXECUTE stmt USING CURRENT_TIMESTAMP;
EXECUTE stmt USING CURRENT_TIMESTAMP(3);
EXECUTE stmt USING CURRENT_TIMESTAMP(6);
SELECT * FROM t1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
DROP TABLE t1;
SET TIMESTAMP=DEFAULT;
...@@ -3672,3 +3672,174 @@ DROP TABLE t1,t2; ...@@ -3672,3 +3672,174 @@ DROP TABLE t1,t2;
--echo # End of 10.0 tests --echo # End of 10.0 tests
--echo #
--echo # Start of 10.2 tests
--echo #
--echo #
--echo # MDEV-10709 Expressions as parameters to Dynamic SQL
--echo #
--echo #
--echo # Using a simple expressions as an EXECUTE parameter
--echo #
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt USING 10;
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ? FROM DUAL';
EXECUTE stmt USING TO_BASE64('xxx');
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ?+? FROM DUAL';
EXECUTE stmt USING 10, 10 + 10;
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT CONCAT(?,?) FROM DUAL';
EXECUTE stmt USING 'xxx', CONCAT('yyy','zzz');
DEALLOCATE PREPARE stmt;
--echo #
--echo # Testing disallowed expressions in USING
--echo #
PREPARE stmt FROM 'SELECT ? FROM DUAL';
--error ER_PARSE_ERROR
EXECUTE stmt USING (SELECT 1);
DEALLOCATE PREPARE stmt;
CREATE FUNCTION f1() RETURNS VARCHAR(10) RETURN 'test';
PREPARE stmt FROM 'SELECT ? FROM DUAL';
--error ER_SUBQUERIES_NOT_SUPPORTED
EXECUTE stmt USING f1();
DEALLOCATE PREPARE stmt;
DROP FUNCTION f1;
--echo #
--echo # Testing erroneous expressions in USING
--echo #
PREPARE stmt FROM 'SELECT ?';
--error ER_CANT_AGGREGATE_2COLLATIONS
EXECUTE stmt USING _latin1'a'=_latin2'a';
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM 'SELECT ?';
--error ER_OPERAND_COLUMNS
EXECUTE stmt USING ROW(1,2);
DEALLOCATE PREPARE stmt;
--echo #
--echo # Creating tables from EXECUTE parameters
--echo #
PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? AS c1 FROM DUAL';
EXECUTE stmt USING 10;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING 10.123;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING 10.123e0;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_DATE;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP(3);
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIMESTAMP(6);
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME;
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME(3);
SHOW CREATE TABLE t1;
DROP TABLE t1;
EXECUTE stmt USING CURRENT_TIME(6);
SHOW CREATE TABLE t1;
DROP TABLE t1;
DEALLOCATE PREPARE stmt;
--echo #
--echo # Using a user variable as an EXECUTE..USING out parameter
--echo #
DELIMITER /;
CREATE PROCEDURE p1(OUT a INT)
BEGIN
SET a:= 10;
END;
/
DELIMITER ;/
SET @a=1;
CALL p1(@a);
SELECT @a;
SET @a=2;
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING @a;
SELECT @a;
DROP PROCEDURE p1;
--echo #
--echo # Using an SP variable as an EXECUTE..USING out parameter
--echo #
DELIMITER /;
CREATE PROCEDURE p1 (OUT a INT)
BEGIN
SET a=10;
END;
/
CREATE PROCEDURE p2 (OUT a INT)
BEGIN
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING a;
END;
/
DELIMITER ;/
SET @a= 1;
CALL p2(@a);
SELECT @a;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
--echo #
--echo # Testing re-prepare on a table metadata update between PREPARE and EXECUTE
--echo #
CREATE TABLE t1 (a INT);
DELIMITER /;
CREATE PROCEDURE p1(a INT)
BEGIN
INSERT INTO t1 VALUES (a);
END;
/
DELIMITER ;/
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING 10;
SELECT * FROM t1;
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET NEW.a=NEW.a+1;
EXECUTE stmt USING 20;
SELECT * FROM t1;
DEALLOCATE PREPARE stmt;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # End of MDEV-10709 Expressions as parameters to Dynamic SQL
--echo #
--echo #
--echo # End of 10.2 tests
--echo #
...@@ -3439,10 +3439,10 @@ bool Item_param::CONVERSION_INFO::convert(THD *thd, String *str) ...@@ -3439,10 +3439,10 @@ bool Item_param::CONVERSION_INFO::convert(THD *thd, String *str)
/** /**
Set parameter value from user variable value. Set parameter value from Item.
@param thd Current thread @param thd Current thread
@param entry User variable structure (NULL means use NULL value) @param item Item
@retval @retval
0 OK 0 OK
...@@ -3450,34 +3450,44 @@ bool Item_param::CONVERSION_INFO::convert(THD *thd, String *str) ...@@ -3450,34 +3450,44 @@ bool Item_param::CONVERSION_INFO::convert(THD *thd, String *str)
1 Out of memory 1 Out of memory
*/ */
bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) bool Item_param::set_from_item(THD *thd, Item *item)
{ {
DBUG_ENTER("Item_param::set_from_user_var"); DBUG_ENTER("Item_param::set_from_item");
if (entry && entry->value)
{
unsigned_flag= entry->unsigned_flag;
if (limit_clause_param) if (limit_clause_param)
{ {
bool unused; longlong val= item->val_int();
set_int(entry->val_int(&unused), MY_INT64_NUM_DECIMAL_DIGITS); if (item->null_value)
{
set_null();
DBUG_RETURN(false);
}
else
{
unsigned_flag= item->unsigned_flag;
set_int(val, MY_INT64_NUM_DECIMAL_DIGITS);
item_type= Item::INT_ITEM; item_type= Item::INT_ITEM;
set_handler_by_result_type(entry->type); set_handler_by_result_type(item->result_type());
DBUG_RETURN(!unsigned_flag && value.integer < 0 ? 1 : 0); DBUG_RETURN(!unsigned_flag && value.integer < 0 ? 1 : 0);
} }
switch (entry->type) { }
struct st_value tmp;
if (!item->store(&tmp, 0))
{
unsigned_flag= item->unsigned_flag;
switch (item->cmp_type()) {
case REAL_RESULT: case REAL_RESULT:
set_double(*(double*)entry->value); set_double(tmp.value.m_double);
item_type= Item::REAL_ITEM; item_type= Item::REAL_ITEM;
set_handler_by_field_type(MYSQL_TYPE_DOUBLE); set_handler_by_field_type(MYSQL_TYPE_DOUBLE);
break; break;
case INT_RESULT: case INT_RESULT:
set_int(*(longlong*)entry->value, MY_INT64_NUM_DECIMAL_DIGITS); set_int(tmp.value.m_longlong, MY_INT64_NUM_DECIMAL_DIGITS);
item_type= Item::INT_ITEM; item_type= Item::INT_ITEM;
set_handler_by_field_type(MYSQL_TYPE_LONGLONG); set_handler_by_field_type(MYSQL_TYPE_LONGLONG);
break; break;
case STRING_RESULT: case STRING_RESULT:
{ {
value.cs_info.set(thd, entry->charset()); value.cs_info.set(thd, item->collation.collation);
/* /*
Exact value of max_length is not known unless data is converted to Exact value of max_length is not known unless data is converted to
charset of connection, so we have to set it later. charset of connection, so we have to set it later.
...@@ -3485,13 +3495,13 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) ...@@ -3485,13 +3495,13 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry)
item_type= Item::STRING_ITEM; item_type= Item::STRING_ITEM;
set_handler_by_field_type(MYSQL_TYPE_VARCHAR); set_handler_by_field_type(MYSQL_TYPE_VARCHAR);
if (set_str((const char *)entry->value, entry->length)) if (set_str(tmp.m_string.ptr(), tmp.m_string.length()))
DBUG_RETURN(1); DBUG_RETURN(1);
break; break;
} }
case DECIMAL_RESULT: case DECIMAL_RESULT:
{ {
const my_decimal *ent_value= (const my_decimal *)entry->value; const my_decimal *ent_value= &tmp.m_decimal;
my_decimal2decimal(ent_value, &decimal_value); my_decimal2decimal(ent_value, &decimal_value);
state= DECIMAL_VALUE; state= DECIMAL_VALUE;
decimals= ent_value->frac; decimals= ent_value->frac;
...@@ -3502,8 +3512,17 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) ...@@ -3502,8 +3512,17 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry)
set_handler_by_field_type(MYSQL_TYPE_NEWDECIMAL); set_handler_by_field_type(MYSQL_TYPE_NEWDECIMAL);
break; break;
} }
case ROW_RESULT:
case TIME_RESULT: case TIME_RESULT:
{
value.time= tmp.value.m_time;
state= TIME_VALUE;
max_length= item->max_length;
decimals= item->decimals;
item_type= Item::DATE_ITEM;
set_handler(item->type_handler());
break;
}
case ROW_RESULT:
DBUG_ASSERT(0); DBUG_ASSERT(0);
set_null(); set_null();
} }
...@@ -3742,18 +3761,34 @@ const String *Item_param::query_val_str(THD *thd, String* str) const ...@@ -3742,18 +3761,34 @@ const String *Item_param::query_val_str(THD *thd, String* str) const
break; break;
case TIME_VALUE: case TIME_VALUE:
{ {
static const uint32 typelen= 9; // "TIMESTAMP" is the longest type name
char *buf, *ptr; char *buf, *ptr;
str->length(0); str->length(0);
/* /*
TODO: in case of error we need to notify replication TODO: in case of error we need to notify replication
that binary log contains wrong statement that binary log contains wrong statement
*/ */
if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3)) if (str->reserve(MAX_DATE_STRING_REP_LENGTH + 3 + typelen))
break; break;
/* Create date string inplace */ /* Create date string inplace */
switch (value.time.time_type) {
case MYSQL_TIMESTAMP_DATE:
str->append(C_STRING_WITH_LEN("DATE"));
break;
case MYSQL_TIMESTAMP_TIME:
str->append(C_STRING_WITH_LEN("TIME"));
break;
case MYSQL_TIMESTAMP_DATETIME:
str->append(C_STRING_WITH_LEN("TIMESTAMP"));
break;
case MYSQL_TIMESTAMP_ERROR:
case MYSQL_TIMESTAMP_NONE:
break;
}
DBUG_ASSERT(str->length() <= typelen);
buf= str->c_ptr_quick(); buf= str->c_ptr_quick();
ptr= buf; ptr= buf + str->length();
*ptr++= '\''; *ptr++= '\'';
ptr+= (uint) my_TIME_to_str(&value.time, ptr, decimals); ptr+= (uint) my_TIME_to_str(&value.time, ptr, decimals);
*ptr++= '\''; *ptr++= '\'';
......
...@@ -30,6 +30,29 @@ ...@@ -30,6 +30,29 @@
C_MODE_START C_MODE_START
#include <ma_dyncol.h> #include <ma_dyncol.h>
/*
A prototype for a C-compatible structure to store a value of any data type.
Currently it has to stay in /sql, as it depends on String and my_decimal.
We'll do the following changes:
1. add pure C "struct st_string" and "struct st_my_decimal"
2. change type of m_string to struct st_string and move inside the union
3. change type of m_decmal to struct st_my_decimal and move inside the union
4. move the definition to some file in /include
*/
struct st_value
{
enum enum_dynamic_column_type m_type;
union
{
longlong m_longlong;
double m_double;
MYSQL_TIME m_time;
} value;
String m_string;
my_decimal m_decimal;
};
C_MODE_END C_MODE_END
const char *dbug_print_item(Item *item); const char *dbug_print_item(Item *item);
...@@ -742,6 +765,57 @@ class Item: public Value_source, ...@@ -742,6 +765,57 @@ class Item: public Value_source,
supposed to be applied recursively. supposed to be applied recursively.
*/ */
virtual inline void quick_fix_field() { fixed= 1; } virtual inline void quick_fix_field() { fixed= 1; }
bool store(struct st_value *value, ulonglong fuzzydate)
{
switch (cmp_type()) {
case INT_RESULT:
{
value->m_type= unsigned_flag ? DYN_COL_UINT : DYN_COL_INT;
value->value.m_longlong= val_int();
break;
}
case REAL_RESULT:
{
value->m_type= DYN_COL_DOUBLE;
value->value.m_double= val_real();
break;
}
case DECIMAL_RESULT:
{
value->m_type= DYN_COL_DECIMAL;
my_decimal *dec= val_decimal(&value->m_decimal);
if (dec != &value->m_decimal && !null_value)
my_decimal2decimal(dec, &value->m_decimal);
break;
}
case STRING_RESULT:
{
value->m_type= DYN_COL_STRING;
String *str= val_str(&value->m_string);
if (str != &value->m_string && !null_value)
value->m_string.set(str->ptr(), str->length(), str->charset());
break;
}
case TIME_RESULT:
{
value->m_type= DYN_COL_DATETIME;
get_date(&value->value.m_time, fuzzydate);
break;
}
case ROW_RESULT:
DBUG_ASSERT(false);
null_value= true;
break;
}
if (null_value)
{
value->m_type= DYN_COL_NULL;
return true;
}
return false;
}
/* Function returns 1 on overflow and -1 on fatal errors */ /* Function returns 1 on overflow and -1 on fatal errors */
int save_in_field_no_warnings(Field *field, bool no_conversions); int save_in_field_no_warnings(Field *field, bool no_conversions);
virtual int save_in_field(Field *field, bool no_conversions); virtual int save_in_field(Field *field, bool no_conversions);
...@@ -2816,7 +2890,7 @@ class Item_param :public Item_basic_value, ...@@ -2816,7 +2890,7 @@ class Item_param :public Item_basic_value,
bool set_str(const char *str, ulong length); bool set_str(const char *str, ulong length);
bool set_longdata(const char *str, ulong length); bool set_longdata(const char *str, ulong length);
void set_time(MYSQL_TIME *tm, timestamp_type type, uint32 max_length_arg); void set_time(MYSQL_TIME *tm, timestamp_type type, uint32 max_length_arg);
bool set_from_user_var(THD *thd, const user_var_entry *entry); bool set_from_item(THD *thd, Item *item);
void reset(); void reset();
/* /*
Assign placeholder value from bind data. Assign placeholder value from bind data.
......
...@@ -1306,33 +1306,27 @@ bool Protocol_text::send_out_parameters(List<Item_param> *sp_params) ...@@ -1306,33 +1306,27 @@ bool Protocol_text::send_out_parameters(List<Item_param> *sp_params)
thd->lex->prepared_stmt_params.elements); thd->lex->prepared_stmt_params.elements);
List_iterator_fast<Item_param> item_param_it(*sp_params); List_iterator_fast<Item_param> item_param_it(*sp_params);
List_iterator_fast<LEX_STRING> user_var_name_it(thd->lex->prepared_stmt_params); List_iterator_fast<Item> param_it(thd->lex->prepared_stmt_params);
while (true) while (true)
{ {
Item_param *item_param= item_param_it++; Item_param *item_param= item_param_it++;
LEX_STRING *user_var_name= user_var_name_it++; Item *param= param_it++;
Settable_routine_parameter *sparam;
if (!item_param || !user_var_name) if (!item_param || !param)
break; break;
if (!item_param->get_out_param_info()) if (!item_param->get_out_param_info())
continue; // It's an IN-parameter. continue; // It's an IN-parameter.
Item_func_set_user_var *suv= if (!(sparam= param->get_settable_routine_parameter()))
new (thd->mem_root) Item_func_set_user_var(thd, *user_var_name, item_param); {
/* DBUG_ASSERT(0);
Item_func_set_user_var is not fixed after construction, call continue;
fix_fields(). }
*/
if (suv->fix_fields(thd, NULL))
return TRUE;
if (suv->check(FALSE))
return TRUE;
if (suv->update()) sparam->set_value(thd, thd->spcont, reinterpret_cast<Item **>(&item_param));
return TRUE;
} }
return FALSE; return FALSE;
......
...@@ -2728,7 +2728,7 @@ struct LEX: public Query_tables_list ...@@ -2728,7 +2728,7 @@ struct LEX: public Query_tables_list
/* If true, prepared_stmt_code is a name of variable that holds the query */ /* If true, prepared_stmt_code is a name of variable that holds the query */
bool prepared_stmt_code_is_varref; bool prepared_stmt_code_is_varref;
/* Names of user variables holding parameters (in EXECUTE) */ /* Names of user variables holding parameters (in EXECUTE) */
List<LEX_STRING> prepared_stmt_params; List<Item> prepared_stmt_params;
sp_head *sphead; sp_head *sphead;
sp_name *spname; sp_name *spname;
bool sp_lex_in_use; /* Keep track on lex usage in SPs for error handling */ bool sp_lex_in_use; /* Keep track on lex usage in SPs for error handling */
......
...@@ -186,8 +186,8 @@ class Prepared_statement: public Statement ...@@ -186,8 +186,8 @@ class Prepared_statement: public Statement
#else #else
bool (*set_params_data)(Prepared_statement *st, String *expanded_query); bool (*set_params_data)(Prepared_statement *st, String *expanded_query);
#endif #endif
bool (*set_params_from_vars)(Prepared_statement *stmt, bool (*set_params_from_actual_params)(Prepared_statement *stmt,
List<LEX_STRING>& varnames, List<Item> &list,
String *expanded_query); String *expanded_query);
public: public:
Prepared_statement(THD *thd_arg); Prepared_statement(THD *thd_arg);
...@@ -1141,31 +1141,27 @@ swap_parameter_array(Item_param **param_array_dst, ...@@ -1141,31 +1141,27 @@ swap_parameter_array(Item_param **param_array_dst,
Assign prepared statement parameters from user variables. Assign prepared statement parameters from user variables.
@param stmt Statement @param stmt Statement
@param varnames List of variables. Caller must ensure that number @param params A list of parameters. Caller must ensure that number
of variables in the list is equal to number of statement of parameters in the list is equal to number of statement
parameters parameters
@param query Ignored @param query Ignored
*/ */
static bool insert_params_from_vars(Prepared_statement *stmt, static bool
List<LEX_STRING>& varnames, insert_params_from_actual_params(Prepared_statement *stmt,
List<Item> &params,
String *query __attribute__((unused))) String *query __attribute__((unused)))
{ {
Item_param **begin= stmt->param_array; Item_param **begin= stmt->param_array;
Item_param **end= begin + stmt->param_count; Item_param **end= begin + stmt->param_count;
user_var_entry *entry; List_iterator<Item> param_it(params);
LEX_STRING *varname; DBUG_ENTER("insert_params_from_actual_params");
List_iterator<LEX_STRING> var_it(varnames);
DBUG_ENTER("insert_params_from_vars");
for (Item_param **it= begin; it < end; ++it) for (Item_param **it= begin; it < end; ++it)
{ {
Item_param *param= *it; Item_param *param= *it;
varname= var_it++; Item *ps_param= param_it++;
entry= (user_var_entry*)my_hash_search(&stmt->thd->user_vars, if (param->set_from_item(stmt->thd, ps_param) ||
(uchar*) varname->str,
varname->length);
if (param->set_from_user_var(stmt->thd, entry) ||
param->convert_str_value(stmt->thd)) param->convert_str_value(stmt->thd))
DBUG_RETURN(1); DBUG_RETURN(1);
} }
...@@ -1174,45 +1170,41 @@ static bool insert_params_from_vars(Prepared_statement *stmt, ...@@ -1174,45 +1170,41 @@ static bool insert_params_from_vars(Prepared_statement *stmt,
/** /**
Do the same as insert_params_from_vars but also construct query text for Do the same as insert_params_from_actual_params
binary log. but also construct query text for binary log.
@param stmt Prepared statement @param stmt Prepared statement
@param varnames List of variables. Caller must ensure that number of @param params A list of parameters. Caller must ensure that number of
variables in the list is equal to number of statement parameters in the list is equal to number of statement
parameters parameters
@param query The query with parameter markers replaced with corresponding @param query The query with parameter markers replaced with corresponding
user variables that were used to execute the query. user variables that were used to execute the query.
*/ */
static bool insert_params_from_vars_with_log(Prepared_statement *stmt, static bool
List<LEX_STRING>& varnames, insert_params_from_actual_params_with_log(Prepared_statement *stmt,
List<Item> &params,
String *query) String *query)
{ {
Item_param **begin= stmt->param_array; Item_param **begin= stmt->param_array;
Item_param **end= begin + stmt->param_count; Item_param **end= begin + stmt->param_count;
user_var_entry *entry; List_iterator<Item> param_it(params);
LEX_STRING *varname;
List_iterator<LEX_STRING> var_it(varnames);
THD *thd= stmt->thd; THD *thd= stmt->thd;
Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query); Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query);
DBUG_ENTER("insert_params_from_vars_with_log"); DBUG_ENTER("insert_params_from_actual_params_with_log");
for (Item_param **it= begin; it < end; ++it) for (Item_param **it= begin; it < end; ++it)
{ {
Item_param *param= *it; Item_param *param= *it;
varname= var_it++; Item *ps_param= param_it++;
entry= (user_var_entry *) my_hash_search(&thd->user_vars, (uchar*)
varname->str, varname->length);
/* /*
We have to call the setup_one_conversion_function() here to set We have to call the setup_one_conversion_function() here to set
the parameter's members that might be needed further the parameter's members that might be needed further
(e.g. value.cs_info.character_set_client is used in the query_val_str()). (e.g. value.cs_info.character_set_client is used in the query_val_str()).
*/ */
setup_one_conversion_function(thd, param, param->field_type()); setup_one_conversion_function(thd, param, param->field_type());
if (param->set_from_user_var(thd, entry)) if (param->set_from_item(thd, ps_param))
DBUG_RETURN(1); DBUG_RETURN(1);
if (acc.append(param)) if (acc.append(param))
...@@ -3042,7 +3034,49 @@ void mysql_sql_stmt_execute(THD *thd) ...@@ -3042,7 +3034,49 @@ void mysql_sql_stmt_execute(THD *thd)
DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
// Fix all Items in the USING list
List_iterator_fast<Item> param_it(lex->prepared_stmt_params);
while (Item *param= param_it++)
{
if (param->fix_fields(thd, 0) || param->check_cols(1))
DBUG_VOID_RETURN;
}
/*
thd->free_list can already have some Items.
Example queries:
- SET STATEMENT var=expr FOR EXECUTE stmt;
- EXECUTE stmt USING expr;
E.g. for a query like this:
PREPARE stmt FROM 'INSERT INTO t1 VALUES (@@max_sort_length)';
SET STATEMENT max_sort_length=2048 FOR EXECUTE stmt;
thd->free_list contains a pointer to Item_int corresponding to 2048.
If Prepared_statement::execute() notices that the table metadata for "t1"
has changed since PREPARE, it returns an error asking the calling
Prepared_statement::execute_loop() to re-prepare the statement.
Before returning the error, Prepared_statement::execute()
calls Prepared_statement::cleanup_stmt(),
which calls thd->cleanup_after_query(),
which calls Query_arena::free_items().
We hide "external" Items, e.g. those created while parsing the
"SET STATEMENT" or "USING" parts of the query,
so they don't get freed in case of re-prepare.
See MDEV-10702 Crash in SET STATEMENT FOR EXECUTE
*/
Item *free_list_backup= thd->free_list;
thd->free_list= NULL; // Hide the external (e.g. "SET STATEMENT") Items
(void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL); (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL);
thd->free_items(); // Free items created by execute_loop()
/*
Now restore the "external" (e.g. "SET STATEMENT") Item list.
It will be freed normaly in THD::cleanup_after_query().
*/
thd->free_list= free_list_backup;
stmt->lex->restore_set_statement_var(); stmt->lex->restore_set_statement_var();
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -3490,7 +3524,7 @@ void Prepared_statement::setup_set_params() ...@@ -3490,7 +3524,7 @@ void Prepared_statement::setup_set_params()
if (replace_params_with_values) if (replace_params_with_values)
{ {
set_params_from_vars= insert_params_from_vars_with_log; set_params_from_actual_params= insert_params_from_actual_params_with_log;
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
set_params= insert_params_with_log; set_params= insert_params_with_log;
#else #else
...@@ -3499,7 +3533,7 @@ void Prepared_statement::setup_set_params() ...@@ -3499,7 +3533,7 @@ void Prepared_statement::setup_set_params()
} }
else else
{ {
set_params_from_vars= insert_params_from_vars; set_params_from_actual_params= insert_params_from_actual_params;
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
set_params= insert_params; set_params= insert_params;
#else #else
...@@ -3809,7 +3843,7 @@ Prepared_statement::set_parameters(String *expanded_query, ...@@ -3809,7 +3843,7 @@ Prepared_statement::set_parameters(String *expanded_query,
if (is_sql_ps) if (is_sql_ps)
{ {
/* SQL prepared statement */ /* SQL prepared statement */
res= set_params_from_vars(this, thd->lex->prepared_stmt_params, res= set_params_from_actual_params(this, thd->lex->prepared_stmt_params,
expanded_query); expanded_query);
} }
else if (param_count) else if (param_count)
......
...@@ -2255,7 +2255,14 @@ execute: ...@@ -2255,7 +2255,14 @@ execute:
execute_using: execute_using:
/* nothing */ /* nothing */
| USING execute_var_list | USING { Lex->expr_allows_subselect= false; }
execute_var_list
{
if (Lex->table_or_sp_used())
my_yyabort_error((ER_SUBQUERIES_NOT_SUPPORTED, MYF(0),
"EXECUTE..USING"));
Lex->expr_allows_subselect= true;
}
; ;
execute_var_list: execute_var_list:
...@@ -2264,12 +2271,9 @@ execute_var_list: ...@@ -2264,12 +2271,9 @@ execute_var_list:
; ;
execute_var_ident: execute_var_ident:
'@' ident_or_text expr
{ {
LEX *lex=Lex; if (Lex->prepared_stmt_params.push_back($1, thd->mem_root))
LEX_STRING *lexstr= (LEX_STRING*)thd->memdup(&$2, sizeof(LEX_STRING));
if (!lexstr || lex->prepared_stmt_params.push_back(lexstr,
thd->mem_root))
MYSQL_YYABORT; MYSQL_YYABORT;
} }
; ;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment