Commit 38486e83 authored by konstantin@mysql.com's avatar konstantin@mysql.com

Implement WL#2661 "Prepared Statements: Dynamic SQL in Stored Procedures".

The idea of the patch is to separate statement processing logic,
such as parsing, validation of the parsed tree, execution and cleanup, 
from global query processing logic, such as logging, resetting
priorities of a thread, resetting stored procedure cache, resetting
thread count of errors and warnings.
This makes PREPARE and EXECUTE behave similarly to the rest of SQL
statements and allows their use in stored procedures.
This patch contains a change in behaviour:
until recently for each SQL prepared statement command, 2 queries
were written to the general log, e.g.
[Query]   prepare stmt from @stmt_text;
[Prepare] select * from t1 <-- contents of @stmt_text
The chagne was necessary to prevent [Prepare] commands from being written
to the general log when executing a stored procedure with Dynamic SQL.
We should consider whether the old behavior is preferrable and probably
restore it.
This patch refixes Bug#7115, Bug#10975 (partially), Bug#10605 (various bugs
in Dynamic SQL reported before it was disabled).
parent a3ddcdf8
prepare stmt1 from ' show full processlist ';
execute stmt1;
Id User Host db Command Time State Info
number root localhost test Execute time NULL show full processlist
number root localhost test Query time NULL show full processlist
deallocate prepare stmt1;
create procedure p1()
begin
prepare stmt from "select 1";
execute stmt;
execute stmt;
execute stmt;
deallocate prepare stmt;
end|
call p1()|
1
1
1
1
1
1
call p1()|
1
1
1
1
1
1
call p1()|
1
1
1
1
1
1
drop procedure p1|
create procedure p1()
begin
execute stmt;
end|
prepare stmt from "call p1()"|
execute stmt|
ERROR HY000: The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner
execute stmt|
ERROR HY000: The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner
execute stmt|
ERROR HY000: The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner
call p1()|
ERROR HY000: Recursive stored routines are not allowed.
call p1()|
ERROR HY000: Recursive stored routines are not allowed.
call p1()|
ERROR HY000: Recursive stored routines are not allowed.
drop procedure p1|
create procedure p1()
begin
prepare stmt from "create procedure p2() begin select 1; end";
execute stmt;
deallocate prepare stmt;
end|
call p1()|
ERROR HY000: This command is not supported in the prepared statement protocol yet
call p1()|
ERROR HY000: This command is not supported in the prepared statement protocol yet
drop procedure p1|
create procedure p1()
begin
prepare stmt from "drop procedure p2";
execute stmt;
deallocate prepare stmt;
end|
call p1()|
ERROR HY000: This command is not supported in the prepared statement protocol yet
call p1()|
ERROR HY000: This command is not supported in the prepared statement protocol yet
drop procedure p1|
create procedure p1()
begin
prepare stmt_drop from "drop table if exists t1";
execute stmt_drop;
prepare stmt from "create table t1 (a int)";
execute stmt;
insert into t1 (a) values (1);
select * from t1;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
call p1()|
a
1
Warnings:
Note 1051 Unknown table 't1'
call p1()|
a
1
drop procedure p1|
create procedure p1()
begin
set @tab_name=concat("tab_", replace(curdate(), '-', '_'));
set @drop_sql=concat("drop table if exists ", @tab_name);
set @create_sql=concat("create table ", @tab_name, " (a int)");
set @insert_sql=concat("insert into ", @tab_name, " values (1), (2), (3)");
set @select_sql=concat("select * from ", @tab_name);
select @tab_name;
select @drop_sql;
select @create_sql;
select @insert_sql;
select @select_sql;
prepare stmt_drop from @drop_sql;
execute stmt_drop;
prepare stmt from @create_sql;
execute stmt;
prepare stmt from @insert_sql;
execute stmt;
prepare stmt from @select_sql;
execute stmt;
execute stmt_drop;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
call p1()|
call p1()|
drop procedure p1|
create procedure p1()
begin
prepare stmt_drop from "drop table if exists t1";
execute stmt_drop;
prepare stmt from "create table t1 (a int)";
execute stmt;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
drop function if exists f1|
create function f1(a int) returns int
begin
call p1();
return 1;
end|
select f1(0)|
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
select f1(f1(0))|
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
select f1(f1(f1(0)))|
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
drop function f1|
drop procedure p1|
create procedure p1()
begin
drop table if exists t1;
create table t1 (id integer not null primary key,
name varchar(20) not null);
insert into t1 (id, name) values (1, 'aaa'), (2, 'bbb'), (3, 'ccc');
prepare stmt from "select name from t1";
execute stmt;
select name from t1;
execute stmt;
prepare stmt from
"select name from t1 where name=(select name from t1 where id=2)";
execute stmt;
select name from t1 where name=(select name from t1 where id=2);
execute stmt;
end|
call p1()|
name
aaa
bbb
ccc
name
aaa
bbb
ccc
name
aaa
bbb
ccc
name
bbb
name
bbb
name
bbb
call p1()|
name
aaa
bbb
ccc
name
aaa
bbb
ccc
name
aaa
bbb
ccc
name
bbb
name
bbb
name
bbb
drop procedure p1|
prepare stmt from "select * from t1"|
create procedure p1()
begin
execute stmt;
deallocate prepare stmt;
end|
call p1()|
id name
1 aaa
2 bbb
3 ccc
call p1()|
ERROR HY000: Unknown prepared statement handler (stmt) given to EXECUTE
drop procedure p1|
create procedure p1()
begin
declare a char(10);
set a="sp-variable";
set @a="mysql-variable";
prepare stmt from "select 'dynamic sql:', @a, a";
execute stmt;
end|
call p1()|
ERROR 42S22: Unknown column 'a' in 'field list'
call p1()|
ERROR 42S22: Unknown column 'a' in 'field list'
drop procedure p1|
create procedure p1()
begin
prepare stmt from 'select ? as a';
execute stmt using @a;
end|
set @a=1|
call p1()|
a
1
call p1()|
a
1
drop procedure p1|
drop table if exists t1|
create table t1 (id integer primary key auto_increment,
stmt_text char(35), status varchar(20))|
insert into t1 (stmt_text) values
("select 1"), ("flush tables"), ("handler t1 open as ha"),
("analyze table t1"), ("check table t1"), ("checksum table t1"),
("check table t1"), ("optimize table t1"), ("repair table t1"),
("describe extended select * from t1"),
("help help"), ("show databases"), ("show tables"),
("show table status"), ("show open tables"), ("show storage engines"),
("insert into t1 (id) values (1)"), ("update t1 set status=''"),
("delete from t1"), ("truncate t1"), ("call p1()"), ("foo bar")|
create procedure p1()
begin
declare v_stmt_text varchar(255);
declare v_id integer;
declare done int default 0;
declare c cursor for select id, stmt_text from t1;
declare continue handler for 1295 -- ER_UNSUPPORTED_PS
set @status='not supported';
declare continue handler for 1064 -- ER_SYNTAX_ERROR
set @status='syntax error';
declare continue handler for sqlstate '02000' set done = 1;
prepare update_stmt from "update t1 set status=? where id=?";
open c;
repeat
if not done then
fetch c into v_id, v_stmt_text;
set @id=v_id, @stmt_text=v_stmt_text;
set @status="supported";
prepare stmt from @stmt_text;
execute update_stmt using @status, @id;
end if;
until done end repeat;
deallocate prepare update_stmt;
end|
call p1()|
select * from t1|
id stmt_text status
1 select 1 supported
2 flush tables not supported
3 handler t1 open as ha not supported
4 analyze table t1 not supported
5 check table t1 not supported
6 checksum table t1 not supported
7 check table t1 not supported
8 optimize table t1 not supported
9 repair table t1 not supported
10 describe extended select * from t1 supported
11 help help not supported
12 show databases supported
13 show tables supported
14 show table status supported
15 show open tables supported
16 show storage engines supported
17 insert into t1 (id) values (1) supported
18 update t1 set status='' supported
19 delete from t1 supported
20 truncate t1 supported
21 call p1() supported
22 foo bar syntax error
drop procedure p1|
drop table t1|
prepare stmt from 'select 1'|
create procedure p1() execute stmt|
call p1()|
1
1
call p1()|
1
1
drop procedure p1|
create function f1() returns int
begin
deallocate prepare stmt;
return 1;
end|
ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger
create procedure p1()
begin
prepare stmt from 'select 1 A';
execute stmt;
end|
prepare stmt from 'call p1()'|
execute stmt|
ERROR HY000: The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner
execute stmt|
ERROR HY000: The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner
drop procedure p1|
drop table if exists t1, t2|
create procedure p1 (a int) language sql deterministic
begin
declare rsql varchar(100);
drop table if exists t1, t2;
set @rsql= "create table t1 (a int)";
select @rsql;
prepare pst from @rsql;
execute pst;
set @rsql= null;
set @rsql= "create table t2 (a int)";
select @rsql;
prepare pst from @rsql;
execute pst;
drop table if exists t1, t2;
end|
set @a:=0|
call p1(@a)|
@rsql
create table t1 (a int)
@rsql
create table t2 (a int)
Warnings:
Note 1051 Unknown table 't1'
Note 1051 Unknown table 't2'
select @a|
@a
0
call p1(@a)|
@rsql
create table t1 (a int)
@rsql
create table t2 (a int)
Warnings:
Note 1051 Unknown table 't1'
Note 1051 Unknown table 't2'
select @a|
@a
0
drop procedure if exists p1|
......@@ -618,7 +618,7 @@ select * from t1|
call bug8408_p()|
val x
select bug8408_f()|
ERROR 0A000: PROCEDURE test.bug8408_p can't return a result set in the given context
ERROR 0A000: Not allowed to return a result set from a function
drop procedure bug8408_p|
drop function bug8408_f|
create function bug8408() returns int
......@@ -665,20 +665,6 @@ select default(t30.s1) from t30;
end|
drop procedure bug10969|
drop table t1|
prepare stmt from "select 1";
create procedure p() deallocate prepare stmt;
ERROR 0A000: DEALLOCATE is not allowed in stored procedures
create function f() returns int begin deallocate prepare stmt;
ERROR 0A000: DEALLOCATE is not allowed in stored procedures
create procedure p() prepare stmt from "select 1";
ERROR 0A000: PREPARE is not allowed in stored procedures
create function f() returns int begin prepare stmt from "select 1";
ERROR 0A000: PREPARE is not allowed in stored procedures
create procedure p() execute stmt;
ERROR 0A000: EXECUTE is not allowed in stored procedures
create function f() returns int begin execute stmt;
ERROR 0A000: EXECUTE is not allowed in stored procedures
deallocate prepare stmt;
create table t1(f1 int);
create table t2(f1 int);
CREATE PROCEDURE SP001()
......
......@@ -689,7 +689,7 @@ call bug11587();
set new.c2= '2004-04-02';
end|
insert into t1 (c1) values (4),(5),(6);
ERROR 0A000: PROCEDURE test.bug11587 can't return a result set in the given context
ERROR 0A000: Not allowed to return a result set from a trigger
select * from t1;
c1 c2
1 NULL
......
delimiter |;
######################################################################
# Test Dynamic SQL in stored procedures. #############################
######################################################################
#
# A. Basics
#
create procedure p1()
begin
prepare stmt from "select 1";
execute stmt;
execute stmt;
execute stmt;
deallocate prepare stmt;
end|
call p1()|
call p1()|
call p1()|
drop procedure p1|
#
# B. Recursion. Recusion is disabled in SP, and recursive use of PS is not
# possible as well.
#
create procedure p1()
begin
execute stmt;
end|
prepare stmt from "call p1()"|
--error ER_PS_NO_RECURSION
execute stmt|
--error ER_PS_NO_RECURSION
execute stmt|
--error ER_PS_NO_RECURSION
execute stmt|
--error ER_SP_NO_RECURSION
call p1()|
--error ER_SP_NO_RECURSION
call p1()|
--error ER_SP_NO_RECURSION
call p1()|
drop procedure p1|
#
# C. Create/drop a stored procedure in Dynamic SQL.
# One cannot create stored procedure from a stored procedure because of
# the way MySQL SP cache works: it's important that this limitation is not
# possible to circumvent by means of Dynamic SQL.
#
create procedure p1()
begin
prepare stmt from "create procedure p2() begin select 1; end";
execute stmt;
deallocate prepare stmt;
end|
--error ER_UNSUPPORTED_PS
call p1()|
--error ER_UNSUPPORTED_PS
call p1()|
drop procedure p1|
create procedure p1()
begin
prepare stmt from "drop procedure p2";
execute stmt;
deallocate prepare stmt;
end|
--error ER_UNSUPPORTED_PS
call p1()|
--error ER_UNSUPPORTED_PS
call p1()|
drop procedure p1|
#
# D. Create/Drop a table (a DDL that issues a commit) in Dynamic SQL.
# (should work ok).
#
create procedure p1()
begin
prepare stmt_drop from "drop table if exists t1";
execute stmt_drop;
prepare stmt from "create table t1 (a int)";
execute stmt;
insert into t1 (a) values (1);
select * from t1;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
call p1()|
call p1()|
drop procedure p1|
#
# A more real example (a case similar to submitted by 24/7).
#
create procedure p1()
begin
set @tab_name=concat("tab_", replace(curdate(), '-', '_'));
set @drop_sql=concat("drop table if exists ", @tab_name);
set @create_sql=concat("create table ", @tab_name, " (a int)");
set @insert_sql=concat("insert into ", @tab_name, " values (1), (2), (3)");
set @select_sql=concat("select * from ", @tab_name);
select @tab_name;
select @drop_sql;
select @create_sql;
select @insert_sql;
select @select_sql;
prepare stmt_drop from @drop_sql;
execute stmt_drop;
prepare stmt from @create_sql;
execute stmt;
prepare stmt from @insert_sql;
execute stmt;
prepare stmt from @select_sql;
execute stmt;
execute stmt_drop;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
--disable_result_log
call p1()|
call p1()|
--enable_result_log
drop procedure p1|
#
# E. Calling a stored procedure with Dynamic SQL
# from a stored function (currently disabled).
#
create procedure p1()
begin
prepare stmt_drop from "drop table if exists t1";
execute stmt_drop;
prepare stmt from "create table t1 (a int)";
execute stmt;
deallocate prepare stmt;
deallocate prepare stmt_drop;
end|
--disable_warnings
drop function if exists f1|
--enable_warnings
create function f1(a int) returns int
begin
call p1();
return 1;
end|
# Every stored procedure that contains Dynamic SQL is marked as
# such. Stored procedures that contain Dynamic SQL are not
# allowed in a stored function or trigger, and here we get the
# corresponding error message.
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
select f1(0)|
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
select f1(f1(0))|
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
select f1(f1(f1(0)))|
drop function f1|
drop procedure p1|
#
# F. Rollback and cleanup lists management in Dynamic SQL.
#
create procedure p1()
begin
drop table if exists t1;
create table t1 (id integer not null primary key,
name varchar(20) not null);
insert into t1 (id, name) values (1, 'aaa'), (2, 'bbb'), (3, 'ccc');
prepare stmt from "select name from t1";
execute stmt;
select name from t1;
execute stmt;
prepare stmt from
"select name from t1 where name=(select name from t1 where id=2)";
execute stmt;
select name from t1 where name=(select name from t1 where id=2);
execute stmt;
end|
call p1()|
call p1()|
drop procedure p1|
#
# H. Executing a statement prepared externally in SP.
#
prepare stmt from "select * from t1"|
create procedure p1()
begin
execute stmt;
deallocate prepare stmt;
end|
call p1()|
--error ER_UNKNOWN_STMT_HANDLER
call p1()|
drop procedure p1|
#
# I. Use of an SP variable in Dynamic SQL is not possible and
# this limitation is necessary for correct binary logging: prepared
# statements do not substitute SP variables with their values for binlog, so
# SP variables must be not accessible in Dynamic SQL.
#
create procedure p1()
begin
declare a char(10);
set a="sp-variable";
set @a="mysql-variable";
prepare stmt from "select 'dynamic sql:', @a, a";
execute stmt;
end|
--error ER_BAD_FIELD_ERROR
call p1()|
--error ER_BAD_FIELD_ERROR
call p1()|
drop procedure p1|
#
# J. Use of placeholders in Dynamic SQL.
#
create procedure p1()
begin
prepare stmt from 'select ? as a';
execute stmt using @a;
end|
set @a=1|
call p1()|
call p1()|
drop procedure p1|
#
# K. Use of continue handlers with Dynamic SQL.
#
drop table if exists t1|
create table t1 (id integer primary key auto_increment,
stmt_text char(35), status varchar(20))|
insert into t1 (stmt_text) values
("select 1"), ("flush tables"), ("handler t1 open as ha"),
("analyze table t1"), ("check table t1"), ("checksum table t1"),
("check table t1"), ("optimize table t1"), ("repair table t1"),
("describe extended select * from t1"),
("help help"), ("show databases"), ("show tables"),
("show table status"), ("show open tables"), ("show storage engines"),
("insert into t1 (id) values (1)"), ("update t1 set status=''"),
("delete from t1"), ("truncate t1"), ("call p1()"), ("foo bar")|
create procedure p1()
begin
declare v_stmt_text varchar(255);
declare v_id integer;
declare done int default 0;
declare c cursor for select id, stmt_text from t1;
declare continue handler for 1295 -- ER_UNSUPPORTED_PS
set @status='not supported';
declare continue handler for 1064 -- ER_SYNTAX_ERROR
set @status='syntax error';
declare continue handler for sqlstate '02000' set done = 1;
prepare update_stmt from "update t1 set status=? where id=?";
open c;
repeat
if not done then
fetch c into v_id, v_stmt_text;
set @id=v_id, @stmt_text=v_stmt_text;
set @status="supported";
prepare stmt from @stmt_text;
execute update_stmt using @status, @id;
end if;
until done end repeat;
deallocate prepare update_stmt;
end|
call p1()|
select * from t1|
drop procedure p1|
drop table t1|
#
# Bug#7115 "Prepared Statements: packet error if execution within stored
# procedure".
#
prepare stmt from 'select 1'|
create procedure p1() execute stmt|
call p1()|
call p1()|
drop procedure p1|
#
# Bug#10975 "Prepared statements: crash if function deallocates"
# Check that a prepared statement that is currently in use
# can't be deallocated.
#
# a) Prepared statements and stored procedure cache:
#
# TODO: add when the corresponding bug (Bug #12093 "SP not found on second
# PS execution if another thread drops other SP in between") is fixed.
#
# b) attempt to deallocate a prepared statement that is being executed
--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG
create function f1() returns int
begin
deallocate prepare stmt;
return 1;
end|
# b)-2 a crash (#1) spotted by Sergey Petrunia during code review
create procedure p1()
begin
prepare stmt from 'select 1 A';
execute stmt;
end|
prepare stmt from 'call p1()'|
--error ER_PS_NO_RECURSION
execute stmt|
--error ER_PS_NO_RECURSION
execute stmt|
drop procedure p1|
#
# Bug#10605 "Stored procedure with multiple SQL prepared statements
# disconnects client"
#
--disable_warnings
drop table if exists t1, t2|
--enable_warnings
create procedure p1 (a int) language sql deterministic
begin
declare rsql varchar(100);
drop table if exists t1, t2;
set @rsql= "create table t1 (a int)";
select @rsql;
prepare pst from @rsql;
execute pst;
set @rsql= null;
set @rsql= "create table t2 (a int)";
select @rsql;
prepare pst from @rsql;
execute pst;
drop table if exists t1, t2;
end|
set @a:=0|
call p1(@a)|
select @a|
call p1(@a)|
select @a|
drop procedure if exists p1|
# End of the test
delimiter ;|
......@@ -875,7 +875,7 @@ create procedure bug8408_p()
select * from t1|
call bug8408_p()|
--error ER_SP_BADSELECT
--error ER_SP_NO_RETSET
select bug8408_f()|
drop procedure bug8408_p|
......@@ -956,39 +956,10 @@ end|
drop procedure bug10969|
#
# BUG#NNNN: New bug synopsis
#
#--disable_warnings
#drop procedure if exists bugNNNN|
#--enable_warnings
#create procedure bugNNNN...
drop table t1|
delimiter ;|
#
# Bug#10975, #10605, #7115: Dynamic SQL by means of
# PREPARE/EXECUTE/DEALLOCATE is not supported yet.
# Check that an error message is returned.
#
prepare stmt from "select 1";
--error ER_SP_BADSTATEMENT
create procedure p() deallocate prepare stmt;
--error ER_SP_BADSTATEMENT
create function f() returns int begin deallocate prepare stmt;
--error ER_SP_BADSTATEMENT
create procedure p() prepare stmt from "select 1";
--error ER_SP_BADSTATEMENT
create function f() returns int begin prepare stmt from "select 1";
--error ER_SP_BADSTATEMENT
create procedure p() execute stmt;
--error ER_SP_BADSTATEMENT
create function f() returns int begin execute stmt;
deallocate prepare stmt;
# BUG#9814: Closing a cursor that is not open
create table t1(f1 int);
create table t2(f1 int);
......@@ -1114,3 +1085,12 @@ drop function bug11834_1;
execute stmt;
deallocate prepare stmt;
drop function bug11834_2;
#
# BUG#NNNN: New bug synopsis
#
#--disable_warnings
#drop procedure if exists bugNNNN|
#--enable_warnings
#create procedure bugNNNN...
......@@ -723,7 +723,7 @@ begin
end|
delimiter ;|
--error 1312
--error ER_SP_NO_RETSET
insert into t1 (c1) values (4),(5),(6);
select * from t1;
......
......@@ -3841,14 +3841,14 @@ longlong Item_func_get_user_var::val_int()
*/
int get_var_with_binlog(THD *thd, LEX_STRING &name,
user_var_entry **out_entry)
int get_var_with_binlog(THD *thd, enum_sql_command sql_command,
LEX_STRING &name, user_var_entry **out_entry)
{
BINLOG_USER_VAR_EVENT *user_var_event;
user_var_entry *var_entry;
var_entry= get_variable(&thd->user_vars, name, 0);
if (!(opt_bin_log && is_update_query(thd->lex->sql_command)))
if (!(opt_bin_log && is_update_query(sql_command)))
{
*out_entry= var_entry;
return 0;
......@@ -3941,7 +3941,7 @@ void Item_func_get_user_var::fix_length_and_dec()
decimals=NOT_FIXED_DEC;
max_length=MAX_BLOB_WIDTH;
error= get_var_with_binlog(thd, name, &var_entry);
error= get_var_with_binlog(thd, thd->lex->sql_command, name, &var_entry);
if (var_entry)
{
......
......@@ -1177,9 +1177,6 @@ class Xid_log_event: public Log_event
Every time a query uses the value of a user variable, a User_var_log_event is
written before the Query_log_event, to set the user variable.
Every time a query uses the value of a user variable, a User_var_log_event is
written before the Query_log_event, to set the user variable.
****************************************************************************/
class User_var_log_event: public Log_event
......
......@@ -589,7 +589,7 @@ bool mysql_change_db(THD *thd,const char *name,bool no_access_check);
void mysql_parse(THD *thd,char *inBuf,uint length);
bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length);
bool is_update_query(enum enum_sql_command command);
bool alloc_query(THD *thd, char *packet, ulong packet_length);
bool alloc_query(THD *thd, const char *packet, uint packet_length);
void mysql_init_select(LEX *lex);
void mysql_reset_thd_for_next_command(THD *thd);
void mysql_init_query(THD *thd, uchar *buf, uint length);
......@@ -848,16 +848,17 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
bool get_schema_tables_result(JOIN *join);
/* sql_prepare.cc */
bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
LEX_STRING *name);
void mysql_stmt_prepare(THD *thd, const char *packet, uint packet_length);
void mysql_stmt_execute(THD *thd, char *packet, uint packet_length);
void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name);
void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length);
void mysql_stmt_close(THD *thd, char *packet);
void mysql_sql_stmt_prepare(THD *thd);
void mysql_sql_stmt_execute(THD *thd);
void mysql_sql_stmt_close(THD *thd);
void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length);
void mysql_stmt_reset(THD *thd, char *packet);
void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
void reinit_stmt_before_use(THD *thd, LEX *lex);
void init_stmt_after_parse(THD*, LEX*);
/* sql_handler.cc */
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen);
......@@ -1361,8 +1362,8 @@ extern int sql_cache_hit(THD *thd, char *inBuf, uint length);
/* item_func.cc */
Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,
LEX_STRING component);
int get_var_with_binlog(THD *thd, LEX_STRING &name,
user_var_entry **out_entry);
int get_var_with_binlog(THD *thd, enum_sql_command sql_command,
LEX_STRING &name, user_var_entry **out_entry);
/* log.cc */
bool flush_error_log(void);
......
......@@ -5399,3 +5399,5 @@ ER_DATETIME_FUNCTION_OVERFLOW 22008
eng "Datetime function: %-.32s field overflow"
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
eng "Can't update table '%-.64s' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
ER_PS_NO_RECURSION
eng "The prepared statement contains a stored routine call that refers to that same statement. It's not allowed to execute a prepared statement in such a recursive manner"
......@@ -47,15 +47,30 @@ sp_map_result_type(enum enum_field_types type)
}
/*
* Returns TRUE if the 'cmd' is a command that might result in
* multiple result sets being sent back.
* Note: This does not include SQLCOM_SELECT which is treated
* separately in sql_yacc.yy.
*/
bool
sp_multi_results_command(enum enum_sql_command cmd)
SYNOPSIS
sp_get_flags_for_command()
DESCRIPTION
Returns a combination of:
* sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might
result in multiple result sets being sent back.
* sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE,
EXECUTE, DEALLOCATE.
*/
uint
sp_get_flags_for_command(LEX *lex)
{
switch (cmd) {
uint flags;
switch (lex->sql_command) {
case SQLCOM_SELECT:
if (lex->result)
{
flags= 0; /* This is a SELECT with INTO clause */
break;
}
/* fallthrough */
case SQLCOM_ANALYZE:
case SQLCOM_CHECKSUM:
case SQLCOM_HA_READ:
......@@ -90,10 +105,26 @@ sp_multi_results_command(enum enum_sql_command cmd)
case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_VARIABLES:
case SQLCOM_SHOW_WARNS:
return TRUE;
flags= sp_head::MULTI_RESULTS;
break;
/*
EXECUTE statement may return a result set, but doesn't have to.
We can't, however, know it in advance, and therefore must add
this statement here. This is ok, as is equivalent to a result-set
statement within an IF condition.
*/
case SQLCOM_EXECUTE:
flags= sp_head::MULTI_RESULTS | sp_head::CONTAINS_DYNAMIC_SQL;
break;
case SQLCOM_PREPARE:
case SQLCOM_DEALLOCATE_PREPARE:
flags= sp_head::CONTAINS_DYNAMIC_SQL;
break;
default:
return FALSE;
flags= 0;
break;
}
return flags;
}
......@@ -364,9 +395,7 @@ sp_head::operator delete(void *ptr, size_t size)
sp_head::sp_head()
:Query_arena(&main_mem_root, INITIALIZED_FOR_SP),
m_returns_cs(NULL), m_has_return(FALSE),
m_simple_case(FALSE), m_multi_results(FALSE), m_in_handler(FALSE),
m_is_invoked(FALSE)
m_flags(0), m_returns_cs(NULL)
{
extern byte *
sp_table_key(const byte *ptr, uint *plen, my_bool first);
......@@ -782,7 +811,7 @@ int sp_head::execute(THD *thd)
DBUG_RETURN(-1);
}
if (m_is_invoked)
if (m_flags & IS_INVOKED)
{
/*
We have to disable recursion for stored routines since in
......@@ -802,7 +831,7 @@ int sp_head::execute(THD *thd)
my_error(ER_SP_NO_RECURSION, MYF(0));
DBUG_RETURN(-1);
}
m_is_invoked= TRUE;
m_flags|= IS_INVOKED;
dbchanged= FALSE;
if (m_db.length &&
......@@ -889,6 +918,15 @@ int sp_head::execute(THD *thd)
/* we should cleanup free_list and memroot, used by instruction */
thd->free_items();
/*
FIXME: we must free user var events only if the routine is executed
in non-prelocked mode and statement-by-statement replication is used.
But if we don't free them now, the server crashes because user var
events are allocated in execute_mem_root. This is Bug#12637, and when
it's fixed, please add if (thd->options & OPTION_BIN_LOG) here.
*/
if (opt_bin_log)
reset_dynamic(&thd->user_var_events);
free_root(&execute_mem_root, MYF(0));
/*
......@@ -955,7 +993,7 @@ int sp_head::execute(THD *thd)
if (! thd->killed)
ret= mysql_change_db(thd, olddb, 0);
}
m_is_invoked= FALSE;
m_flags&= ~IS_INVOKED;
DBUG_RETURN(ret);
}
......@@ -1397,7 +1435,6 @@ sp_head::restore_lex(THD *thd)
LEX *sublex= thd->lex;
LEX *oldlex= (LEX *)m_lex.pop();
init_stmt_after_parse(thd, sublex);
if (! oldlex)
return; // Nothing to restore
......
......@@ -33,8 +33,8 @@
Item_result
sp_map_result_type(enum enum_field_types type);
bool
sp_multi_results_command(enum enum_sql_command cmd);
uint
sp_get_flags_for_command(LEX *lex);
struct sp_label;
class sp_instr;
......@@ -107,18 +107,23 @@ class sp_head :private Query_arena
MEM_ROOT main_mem_root;
public:
/* Possible values of m_flags */
const static int
HAS_RETURN= 1, // For FUNCTIONs only: is set if has RETURN
IN_SIMPLE_CASE= 2, // Is set if parsing a simple CASE
IN_HANDLER= 4, // Is set if the parser is in a handler body
MULTI_RESULTS= 8, // Is set if a procedure with SELECT(s)
CONTAINS_DYNAMIC_SQL= 16, // Is set if a procedure with PREPARE/EXECUTE
IS_INVOKED= 32; // Is set if this sp_head is being used.
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
uint m_flags; // Boolean attributes of a stored routine
enum enum_field_types m_returns; // For FUNCTIONs only
Field::geometry_type m_geom_returns;
CHARSET_INFO *m_returns_cs; // For FUNCTIONs only
TYPELIB *m_returns_typelib; // For FUNCTIONs only
uint m_returns_len; // For FUNCTIONs only
uint m_returns_pack; // For FUNCTIONs only
my_bool m_has_return; // For FUNCTIONs only
my_bool m_simple_case; // TRUE if parsing simple case, FALSE otherwise
my_bool m_multi_results; // TRUE if a procedure with SELECT(s)
my_bool m_in_handler; // TRUE if parser in a handler body
uchar *m_tmp_query; // Temporary pointer to sub query string
uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value
st_sp_chistics *m_chistics;
......@@ -265,6 +270,19 @@ public:
bool add_used_tables_to_table_list(THD *thd,
TABLE_LIST ***query_tables_last_ptr);
/*
Check if this stored routine contains statements disallowed
in a stored function or trigger, and set an appropriate error message
if this is the case.
*/
bool is_not_allowed_in_function(const char *where)
{
if (m_flags & CONTAINS_DYNAMIC_SQL)
my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "Dynamic SQL");
else if (m_flags & MULTI_RESULTS)
my_error(ER_SP_NO_RETSET, MYF(0), where);
return test(m_flags & (CONTAINS_DYNAMIC_SQL|MULTI_RESULTS));
}
private:
MEM_ROOT *m_thd_root; // Temp. store for thd's mem_root
......@@ -290,9 +308,6 @@ private:
*/
HASH m_sptabs;
/* Used for tracking of routine invocations and preventing recursion. */
bool m_is_invoked;
int
execute(THD *thd);
......
......@@ -551,11 +551,6 @@ void THD::cleanup_after_query()
}
/* Free Items that were created during this execution */
free_items();
/*
In the rest of code we assume that free_list never points to garbage:
Keep this predicate true.
*/
free_list= 0;
}
/*
......@@ -1686,23 +1681,17 @@ Statement_map::Statement_map() :
NULL,MYF(0));
}
int Statement_map::insert(Statement *statement)
{
int rc= my_hash_insert(&st_hash, (byte *) statement);
if (rc == 0)
last_found_statement= statement;
if (statement->name.str)
{
/*
If there is a statement with the same name, remove it. It is ok to
remove old and fail to insert new one at the same time.
*/
Statement *old_stmt;
if ((old_stmt= find_by_name(&statement->name)))
erase(old_stmt);
if ((rc= my_hash_insert(&names_hash, (byte*)statement)))
hash_delete(&st_hash, (byte*)statement);
}
if (rc == 0)
last_found_statement= statement;
return rc;
}
......
......@@ -128,6 +128,7 @@ void lex_start(THD *thd, uchar *buf,uint length)
lex->update_list.empty();
lex->param_list.empty();
lex->view_list.empty();
lex->prepared_stmt_params.empty();
lex->unit.next= lex->unit.master=
lex->unit.link_next= lex->unit.return_to= 0;
lex->unit.prev= lex->unit.link_prev= 0;
......@@ -143,6 +144,7 @@ void lex_start(THD *thd, uchar *buf,uint length)
lex->describe= 0;
lex->subqueries= FALSE;
lex->view_prepare_mode= FALSE;
lex->stmt_prepare_mode= FALSE;
lex->derived_tables= 0;
lex->lock_option= TL_READ;
lex->found_semicolon= 0;
......@@ -568,8 +570,7 @@ int yylex(void *arg, void *yythd)
its value in a query for the binlog, the query must stay
grammatically correct.
*/
else if (c == '?' && ((THD*) yythd)->command == COM_STMT_PREPARE &&
!ident_map[yyPeek()])
else if (c == '?' && lex->stmt_prepare_mode && !ident_map[yyPeek()])
return(PARAM_MARKER);
return((int) c);
......@@ -981,7 +982,7 @@ int yylex(void *arg, void *yythd)
{
THD* thd= (THD*)yythd;
if ((thd->client_capabilities & CLIENT_MULTI_STATEMENTS) &&
(thd->command != COM_STMT_PREPARE))
!lex->stmt_prepare_mode)
{
lex->safe_to_cache_query= 0;
lex->found_semicolon=(char*) lex->ptr;
......
......@@ -808,6 +808,11 @@ typedef struct st_lex
to an .frm file. We need this definition to stay untouched.
*/
bool view_prepare_mode;
/*
TRUE if we're parsing a prepared statement: in this mode
we should allow placeholders and disallow multistatements.
*/
bool stmt_prepare_mode;
bool safe_to_cache_query;
bool subqueries, ignore;
bool variables_used;
......
......@@ -1644,7 +1644,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
}
case COM_STMT_PREPARE:
{
mysql_stmt_prepare(thd, packet, packet_length, 0);
mysql_stmt_prepare(thd, packet, packet_length);
break;
}
case COM_STMT_CLOSE:
......@@ -1664,6 +1664,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
char *packet_end= thd->query + thd->query_length;
mysql_log.write(thd,command,"%s",thd->query);
DBUG_PRINT("query",("%-.4096s",thd->query));
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
mysql_parse(thd,thd->query, thd->query_length);
while (!thd->killed && thd->lex->found_semicolon && !thd->net.report_error)
......@@ -2220,7 +2224,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
TRUE error; In this case thd->fatal_error is set
*/
bool alloc_query(THD *thd, char *packet, ulong packet_length)
bool alloc_query(THD *thd, const char *packet, uint packet_length)
{
packet_length--; // Remove end null
/* Remove garbage at start and end of query */
......@@ -2229,7 +2233,7 @@ bool alloc_query(THD *thd, char *packet, ulong packet_length)
packet++;
packet_length--;
}
char *pos=packet+packet_length; // Point at end null
const char *pos= packet + packet_length; // Point at end null
while (packet_length > 0 &&
(pos[-1] == ';' || my_isspace(thd->charset() ,pos[-1])))
{
......@@ -2250,8 +2254,6 @@ bool alloc_query(THD *thd, char *packet, ulong packet_length)
thd->packet.shrink(thd->variables.net_buffer_length);
thd->convert_buffer.shrink(thd->variables.net_buffer_length);
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
return FALSE;
}
......@@ -2466,112 +2468,17 @@ mysql_execute_command(THD *thd)
}
case SQLCOM_PREPARE:
{
char *query_str;
uint query_len;
if (lex->prepared_stmt_code_is_varref)
{
/* This is PREPARE stmt FROM @var. */
String str;
CHARSET_INFO *to_cs= thd->variables.collation_connection;
bool need_conversion;
user_var_entry *entry;
String *pstr= &str;
uint32 unused;
/*
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 uniform.
*/
if ((entry=
(user_var_entry*)hash_search(&thd->user_vars,
(byte*)lex->prepared_stmt_code.str,
lex->prepared_stmt_code.length))
&& entry->value)
{
my_bool is_var_null;
pstr= 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 (!pstr)
goto error;
}
else
{
/*
variable absent or equal to NULL, so we need to set variable to
something reasonable to get readable error message during parsing
*/
str.set("NULL", 4, &my_charset_latin1);
}
need_conversion=
String::needs_conversion(pstr->length(), pstr->charset(),
to_cs, &unused);
query_len= need_conversion? (pstr->length() * to_cs->mbmaxlen) :
pstr->length();
if (!(query_str= alloc_root(thd->mem_root, query_len+1)))
goto error;
if (need_conversion)
{
uint dummy_errors;
query_len= copy_and_convert(query_str, query_len, to_cs,
pstr->ptr(), pstr->length(),
pstr->charset(), &dummy_errors);
}
else
memcpy(query_str, pstr->ptr(), pstr->length());
query_str[query_len]= 0;
}
else
{
query_str= lex->prepared_stmt_code.str;
query_len= lex->prepared_stmt_code.length;
DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str,
query_len, query_str));
}
thd->command= COM_STMT_PREPARE;
if (!(res= mysql_stmt_prepare(thd, query_str, query_len + 1,
&lex->prepared_stmt_name)))
send_ok(thd, 0L, 0L, "Statement prepared");
mysql_sql_stmt_prepare(thd);
break;
}
case SQLCOM_EXECUTE:
{
DBUG_PRINT("info", ("EXECUTE: %.*s\n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str));
mysql_sql_stmt_execute(thd, &lex->prepared_stmt_name);
lex->prepared_stmt_params.empty();
mysql_sql_stmt_execute(thd);
break;
}
case SQLCOM_DEALLOCATE_PREPARE:
{
Statement* stmt;
DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str));
/* We account deallocate in the same manner as mysql_stmt_close */
statistic_increment(thd->status_var.com_stmt_close, &LOCK_status);
if ((stmt= thd->stmt_map.find_by_name(&lex->prepared_stmt_name)))
{
thd->stmt_map.erase(stmt);
send_ok(thd);
}
else
{
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0),
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str,
"DEALLOCATE PREPARE");
goto error;
}
mysql_sql_stmt_close(thd);
break;
}
case SQLCOM_DO:
......@@ -4124,7 +4031,7 @@ end_with_restore_list:
}
#endif
if (lex->sphead->m_type == TYPE_ENUM_FUNCTION &&
!lex->sphead->m_has_return)
!(lex->sphead->m_flags & sp_head::HAS_RETURN))
{
my_error(ER_SP_NORETURN, MYF(0), name);
delete lex->sphead;
......@@ -4213,15 +4120,31 @@ end_with_restore_list:
ha_rows select_limit;
/* bits that should be cleared in thd->server_status */
uint bits_to_be_cleared= 0;
/*
Check that the stored procedure doesn't contain Dynamic SQL
and doesn't return result sets: such stored procedures can't
be called from a function or trigger.
*/
if (thd->in_sub_stmt)
{
const char *where= (thd->in_sub_stmt & SUB_STMT_TRIGGER ?
"trigger" : "function");
if (sp->is_not_allowed_in_function(where))
goto error;
}
#ifndef EMBEDDED_LIBRARY
my_bool nsok= thd->net.no_send_ok;
thd->net.no_send_ok= TRUE;
#endif
if (sp->m_multi_results)
if (sp->m_flags & sp_head::MULTI_RESULTS)
{
if (! (thd->client_capabilities & CLIENT_MULTI_RESULTS))
{
/*
The client does not support multiple result sets being sent
back
*/
my_error(ER_SP_BADSELECT, MYF(0), sp->m_qname.str);
#ifndef EMBEDDED_LIBRARY
thd->net.no_send_ok= nsok;
......@@ -4265,7 +4188,7 @@ end_with_restore_list:
thd->row_count_func= 0;
/*
We never write CALL statements int binlog:
We never write CALL statements into binlog:
- If the mode is non-prelocked, each statement will be logged
separately.
- If the mode is prelocked, the invoking statement will care
......
This diff is collapsed.
......@@ -921,16 +921,11 @@ deallocate:
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_STMT_PREPARE)
if (lex->stmt_prepare_mode)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
if (lex->sphead)
{
my_error(ER_SP_BADSTATEMENT, MYF(0), "DEALLOCATE");
YYABORT;
}
lex->sql_command= SQLCOM_DEALLOCATE_PREPARE;
lex->prepared_stmt_name= $3;
};
......@@ -946,16 +941,11 @@ prepare:
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_STMT_PREPARE)
if (lex->stmt_prepare_mode)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
if (lex->sphead)
{
my_error(ER_SP_BADSTATEMENT, MYF(0), "PREPARE");
YYABORT;
}
lex->sql_command= SQLCOM_PREPARE;
lex->prepared_stmt_name= $2;
};
......@@ -981,16 +971,11 @@ execute:
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_STMT_PREPARE)
if (lex->stmt_prepare_mode)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
if (lex->sphead)
{
my_error(ER_SP_BADSTATEMENT, MYF(0), "EXECUTE");
YYABORT;
}
lex->sql_command= SQLCOM_EXECUTE;
lex->prepared_stmt_name= $2;
}
......@@ -1324,11 +1309,8 @@ create:
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
sp->restore_thd_mem_root(YYTHD);
if (sp->m_multi_results)
{
my_error(ER_SP_NO_RETSET, MYF(0), "trigger");
if (sp->is_not_allowed_in_function("trigger"))
YYABORT;
}
/*
We have to do it after parsing trigger body, because some of
......@@ -1481,11 +1463,9 @@ create_function_tail:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
if (sp->m_multi_results)
{
my_error(ER_SP_NO_RETSET, MYF(0), "function");
if (sp->is_not_allowed_in_function("function"))
YYABORT;
}
if (sp->check_backpatch(YYTHD))
YYABORT;
lex->sql_command= SQLCOM_CREATE_SPFUNCTION;
......@@ -1735,7 +1715,7 @@ sp_decl:
sp->add_instr(i);
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
sp->m_in_handler= TRUE;
sp->m_flags|= sp_head::IN_HANDLER;
}
sp_hcond_list sp_proc_stmt
{
......@@ -1759,7 +1739,7 @@ sp_decl:
sp->push_backpatch(i, lex->spcont->last_label()); /* Block end */
}
lex->sphead->backpatch(hlab);
sp->m_in_handler= FALSE;
sp->m_flags&= ~sp_head::IN_HANDLER;
$$.vars= $$.conds= $$.curs= 0;
$$.hndlrs= $6;
ctx->add_handlers($6);
......@@ -1971,12 +1951,7 @@ sp_proc_stmt:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
if ((lex->sql_command == SQLCOM_SELECT && !lex->result) ||
sp_multi_results_command(lex->sql_command))
{
/* We maybe have one or more SELECT without INTO */
sp->m_multi_results= TRUE;
}
sp->m_flags|= sp_get_flags_for_command(lex);
if (lex->sql_command == SQLCOM_CHANGE_DB)
{ /* "USE db" doesn't work in a procedure */
my_error(ER_SP_BADSTATEMENT, MYF(0), "USE");
......@@ -2026,14 +2001,14 @@ sp_proc_stmt:
i= new sp_instr_freturn(sp->instructions(), lex->spcont,
$3, sp->m_returns, lex);
sp->add_instr(i);
sp->m_has_return= TRUE;
sp->m_flags|= sp_head::HAS_RETURN;
}
sp->restore_lex(YYTHD);
}
| IF sp_if END IF {}
| CASE_SYM WHEN_SYM
{
Lex->sphead->m_simple_case= FALSE;
Lex->sphead->m_flags&= ~sp_head::IN_SIMPLE_CASE;
}
sp_case END CASE_SYM {}
| CASE_SYM
......@@ -2053,7 +2028,7 @@ sp_proc_stmt:
lex->spcont->push_pvar(&dummy, MYSQL_TYPE_STRING, sp_param_in);
lex->sphead->add_instr(i);
lex->sphead->m_simple_case= TRUE;
lex->sphead->m_flags|= sp_head::IN_SIMPLE_CASE;
lex->sphead->restore_lex(YYTHD);
}
sp_case END CASE_SYM
......@@ -2367,7 +2342,7 @@ sp_case:
uint ip= sp->instructions();
sp_instr_jump_if_not *i;
if (! sp->m_simple_case)
if (! (sp->m_flags & sp_head::IN_SIMPLE_CASE))
i= new sp_instr_jump_if_not(ip, ctx, $2, lex);
else
{ /* Simple case: <caseval> = <whenval> */
......
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