summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2016-08-08 16:42:01 +0400
committerAlexander Barkov <bar@mariadb.org>2017-04-05 15:02:39 +0400
commit892af78085482bb304947e7856bfd1b8def37c83 (patch)
tree54fd3b2ca25fa90c264d56b10785c326e6aaa876 /sql
parent7e10e38825f5cf02048a3cc69aea9d440e3140ba (diff)
downloadmariadb-git-892af78085482bb304947e7856bfd1b8def37c83.tar.gz
MDEV-10411 Providing compatibility for basic PL/SQL constructs
Part6: assignment operator var:= 10;
Diffstat (limited to 'sql')
-rw-r--r--sql/set_var.cc11
-rw-r--r--sql/set_var.h1
-rw-r--r--sql/sql_lex.cc96
-rw-r--r--sql/sql_lex.h7
-rw-r--r--sql/sql_yacc.yy89
-rw-r--r--sql/sql_yacc_ora.yy247
6 files changed, 259 insertions, 192 deletions
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 07395e3e708..a5b80e34993 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -686,6 +686,17 @@ sys_var *intern_find_sys_var(const char *str, uint length)
}
+bool find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
+{
+ tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
+
+ if (tmp->var != NULL)
+ tmp->base_name= null_lex_str;
+
+ return thd->is_error();
+}
+
+
/**
Execute update of all variables.
diff --git a/sql/set_var.h b/sql/set_var.h
index 97dc3b5ba51..ddd6a225eb8 100644
--- a/sql/set_var.h
+++ b/sql/set_var.h
@@ -391,6 +391,7 @@ SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type);
int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond);
sys_var *find_sys_var(THD *thd, const char *str, size_t length=0);
+bool find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp);
int sql_set_variables(THD *thd, List<set_var_base> *var_list, bool free);
#define SYSVAR_AUTOSIZE(VAR,VAL) \
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index d28504431e2..4be04cbde8d 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -5090,6 +5090,102 @@ bool LEX::sp_param_fill_definition(sp_variable *spvar)
}
+void LEX::set_stmt_init()
+{
+ sql_command= SQLCOM_SET_OPTION;
+ mysql_init_select(this);
+ option_type= OPT_SESSION;
+ autocommit= 0;
+};
+
+
+bool LEX::init_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING name)
+{
+ sp_variable *spv;
+
+ /* Best effort lookup for system variable. */
+ if (!spcont || !(spv = spcont->find_variable(name, false)))
+ {
+ struct sys_var_with_base tmp= {NULL, name};
+
+ /* Not an SP local variable */
+ if (find_sys_var_null_base(thd, &tmp))
+ return true;
+
+ *variable= tmp;
+ return false;
+ }
+
+ /*
+ Possibly an SP local variable (or a shadowed sysvar).
+ Will depend on the context of the SET statement.
+ */
+ variable->var= NULL;
+ variable->base_name= name;
+ return false;
+}
+
+
+bool LEX::init_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING dbname, LEX_STRING name)
+{
+ if (check_reserved_words(&dbname))
+ {
+ thd->parse_error();
+ return true;
+ }
+ if (sphead && sphead->m_type == TYPE_ENUM_TRIGGER &&
+ (!my_strcasecmp(system_charset_info, dbname.str, "NEW") ||
+ !my_strcasecmp(system_charset_info, dbname.str, "OLD")))
+ {
+ if (dbname.str[0]=='O' || dbname.str[0]=='o')
+ {
+ my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", "");
+ return true;
+ }
+ if (trg_chistics.event == TRG_EVENT_DELETE)
+ {
+ my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
+ return true;
+ }
+ if (trg_chistics.action_time == TRG_ACTION_AFTER)
+ {
+ my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after ");
+ return true;
+ }
+ /* This special combination will denote field of NEW row */
+ variable->var= trg_new_row_fake_var;
+ variable->base_name= name;
+ return false;
+ }
+
+ sys_var *tmp= find_sys_var(thd, name.str, name.length);
+ if (!tmp)
+ return true;
+ if (!tmp->is_struct())
+ my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), name.str);
+ variable->var= tmp;
+ variable->base_name= dbname;
+ return false;
+}
+
+
+bool LEX::init_default_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING name)
+{
+ sys_var *tmp= find_sys_var(thd, name.str, name.length);
+ if (!tmp)
+ return true;
+ if (!tmp->is_struct())
+ my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), name.str);
+ variable->var= tmp;
+ variable->base_name.str= (char*) "default";
+ variable->base_name.length= 7;
+ return false;
+}
+
+
#ifdef MYSQL_SERVER
uint binlog_unsafe_map[256];
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 0b433867974..a298bf33968 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -3064,6 +3064,13 @@ public:
bool set_trigger_new_row(LEX_STRING *name, Item *val);
bool set_system_variable(struct sys_var_with_base *tmp,
enum enum_var_type var_type, Item *val);
+ void set_stmt_init();
+ bool init_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING name);
+ bool init_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING dbname, LEX_STRING name);
+ bool init_default_internal_variable(struct sys_var_with_base *variable,
+ LEX_STRING name);
bool set_local_variable(sp_variable *spv, Item *val);
Item_splocal *create_item_for_sp_var(LEX_STRING name, sp_variable *spvar,
const char *start_in_q,
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 9eb5669a60f..4c08c0fbc43 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -335,17 +335,6 @@ int LEX::case_stmt_action_then()
return sphead->push_backpatch(thd, i, spcont->last_label());
}
-static bool
-find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
-{
- tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
-
- if (tmp->var != NULL)
- tmp->base_name= null_lex_str;
-
- return thd->is_error();
-}
-
/**
Helper action for a SET statement.
@@ -14880,22 +14869,15 @@ set:
SET
{
LEX *lex=Lex;
- lex->sql_command= SQLCOM_SET_OPTION;
- mysql_init_select(lex);
- lex->option_type=OPT_SESSION;
+ lex->set_stmt_init();
lex->var_list.empty();
- lex->autocommit= 0;
sp_create_assignment_lex(thd, yychar == YYEMPTY);
}
start_option_value_list
{}
| SET STATEMENT_SYM
{
- LEX *lex= Lex;
- mysql_init_select(lex);
- lex->option_type= OPT_SESSION;
- lex->sql_command= SQLCOM_SET_OPTION;
- lex->autocommit= 0;
+ Lex->set_stmt_init();
}
set_stmt_option_value_following_option_type_list
{
@@ -15193,77 +15175,18 @@ option_value_no_option_type:
internal_variable_name:
ident
{
- sp_pcontext *spc= thd->lex->spcont;
- sp_variable *spv;
-
- /* Best effort lookup for system variable. */
- if (!spc || !(spv = spc->find_variable($1, false)))
- {
- struct sys_var_with_base tmp= {NULL, $1};
-
- /* Not an SP local variable */
- if (find_sys_var_null_base(thd, &tmp))
- MYSQL_YYABORT;
-
- $$= tmp;
- }
- else
- {
- /*
- Possibly an SP local variable (or a shadowed sysvar).
- Will depend on the context of the SET statement.
- */
- $$.var= NULL;
- $$.base_name= $1;
- }
+ if (Lex->init_internal_variable(&$$, $1))
+ MYSQL_YYABORT;
}
| ident '.' ident
{
- LEX *lex= Lex;
- if (check_reserved_words(&$1))
- {
- thd->parse_error();
+ if (Lex->init_internal_variable(&$$, $1, $3))
MYSQL_YYABORT;
- }
- if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
- (!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
- !my_strcasecmp(system_charset_info, $1.str, "OLD")))
- {
- if ($1.str[0]=='O' || $1.str[0]=='o')
- my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", ""));
- if (lex->trg_chistics.event == TRG_EVENT_DELETE)
- {
- my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0),
- "NEW", "on DELETE");
- MYSQL_YYABORT;
- }
- if (lex->trg_chistics.action_time == TRG_ACTION_AFTER)
- my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after "));
- /* This special combination will denote field of NEW row */
- $$.var= trg_new_row_fake_var;
- $$.base_name= $3;
- }
- else
- {
- sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
- if (!tmp)
- MYSQL_YYABORT;
- if (!tmp->is_struct())
- my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
- $$.var= tmp;
- $$.base_name= $1;
- }
}
| DEFAULT '.' ident
{
- sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
- if (!tmp)
+ if (Lex->init_default_internal_variable(&$$, $3))
MYSQL_YYABORT;
- if (!tmp->is_struct())
- my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
- $$.var= tmp;
- $$.base_name.str= (char*) "default";
- $$.base_name.length= 7;
}
;
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 081a26f2c05..3766a650fc0 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -215,18 +215,6 @@ static bool push_sp_empty_label(THD *thd)
}
-static bool
-find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
-{
- tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
-
- if (tmp->var != NULL)
- tmp->base_name= null_lex_str;
-
- return thd->is_error();
-}
-
-
#define bincmp_collation(X,Y) \
do \
{ \
@@ -335,10 +323,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%parse-param { THD *thd }
%lex-param { THD *thd }
/*
- Currently there are 102 shift/reduce conflicts.
+ Currently there are 104 shift/reduce conflicts.
We should not introduce new conflicts any more.
*/
-%expect 102
+%expect 104
/*
Comments for TOKENS.
@@ -1059,7 +1047,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
opt_component key_cache_name
sp_opt_label BIN_NUM label_ident TEXT_STRING_filesystem ident_or_empty
opt_constraint constraint opt_ident
- label_declaration_oracle
+ label_declaration_oracle ident_directly_assignable
%type <lex_string_with_metadata>
TEXT_STRING
@@ -1217,6 +1205,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%type <Lex_length_and_dec> precision opt_precision float_options
%type <symbol> keyword keyword_sp
+ keyword_directly_assignable
+ keyword_directly_not_assignable
%type <lex_user> user grant_user grant_role user_or_role current_role
admin_option_for_role user_maybe_role
@@ -1234,6 +1224,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
UNDERSCORE_CHARSET
%type <variable> internal_variable_name
+ internal_variable_name_directly_assignable
%type <select_lex> subselect
get_select_lex get_select_lex_derived
@@ -1320,6 +1311,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
vcol_opt_attribute_list vcol_attribute
opt_serial_attribute opt_serial_attribute_list serial_attribute
explainable_command
+ set_assign
END_OF_INPUT
%type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt
@@ -1507,6 +1499,7 @@ statement:
| savepoint
| select
| set
+ | set_assign
| signal_stmt
| show
| shutdown
@@ -13788,6 +13781,26 @@ ident:
}
;
+
+ident_directly_assignable:
+ IDENT_sys { $$=$1; }
+ | keyword_directly_assignable
+ {
+ $$.str= thd->strmake($1.str, $1.length);
+ if ($$.str == NULL)
+ MYSQL_YYABORT;
+ $$.length= $1.length;
+ }
+ | keyword_sp
+ {
+ $$.str= thd->strmake($1.str, $1.length);
+ if ($$.str == NULL)
+ MYSQL_YYABORT;
+ $$.length= $1.length;
+ }
+ ;
+
+
label_ident:
IDENT_sys { $$=$1; }
| keyword_sp
@@ -13876,27 +13889,30 @@ user: user_maybe_role
/* Keyword that we allow for identifiers (except SP labels) */
keyword:
keyword_sp {}
- | ASCII_SYM {}
+ | keyword_directly_assignable {}
+ | keyword_directly_not_assignable {}
+ ;
+
+
+/*
+ Keywords that we allow in Oracle-style direct assignments:
+ xxx := 10;
+*/
+keyword_directly_assignable:
+ ASCII_SYM {}
| BACKUP_SYM {}
- | BEGIN_SYM {}
| BINLOG_SYM {}
| BYTE_SYM {}
| CACHE_SYM {}
- | CHARSET {}
| CHECKSUM_SYM {}
| CHECKPOINT_SYM {}
- | CLOSE_SYM {}
| COLUMN_ADD_SYM {}
| COLUMN_CHECK_SYM {}
| COLUMN_CREATE_SYM {}
| COLUMN_DELETE_SYM {}
| COLUMN_GET_SYM {}
- | COMMENT_SYM {}
| COMMIT_SYM {}
- | CONTAINS_SYM {}
| DEALLOCATE_SYM {}
- | DO_SYM {}
- | END {}
| EXAMINED_SYM {}
| EXCLUDE_SYM {}
| EXECUTE_SYM {}
@@ -13905,13 +13921,9 @@ keyword:
| FOLLOWING_SYM {}
| FORMAT_SYM {}
| GET_SYM {}
- | HANDLER_SYM {}
| HELP_SYM {}
| HOST_SYM {}
| INSTALL_SYM {}
- | LANGUAGE_SYM {}
- | NO_SYM {}
- | OPEN_SYM {}
| OPTION {}
| OPTIONS_SYM {}
| OTHERS_SYM {}
@@ -13922,11 +13934,9 @@ keyword:
| PRECEDING_SYM {}
| PREPARE_SYM {}
| REMOVE_SYM {}
- | REPAIR {}
| RESET_SYM {}
| RESTORE_SYM {}
| ROLLBACK_SYM {}
- | SAVEPOINT_SYM {}
| SECURITY_SYM {}
| SERVER_SYM {}
| SHUTDOWN {}
@@ -13939,7 +13949,6 @@ keyword:
| STOP_SYM {}
| STORED_SYM {}
| TIES_SYM {}
- | TRUNCATE_SYM {}
| UNICODE_SYM {}
| UNINSTALL_SYM {}
| UNBOUNDED_SYM {}
@@ -13949,6 +13958,51 @@ keyword:
;
/*
+ Keywords that are allowed as identifiers (e.g. table, column names),
+ but:
+ - not allowed as SP label names
+ - not allowed as variable names in Oracle-style assignments:
+ xxx := 10;
+
+ If we allowed these variables in assignments, there would be conflicts
+ with SP characteristics, or verb clauses, or compound statements, e.g.:
+ CREATE PROCEDURE p1 LANGUAGE ...
+ would be either:
+ CREATE PROCEDURE p1 LANGUAGE SQL BEGIN END;
+ or
+ CREATE PROCEDURE p1 LANGUAGE:=10;
+
+ Note, these variables can still be assigned using quoted identifiers:
+ `do`:= 10;
+ "do":= 10; (when ANSI_QUOTES)
+ or using a SET statement:
+ SET do= 10;
+
+ Note, some of these keywords are reserved keywords in Oracle.
+ In case if heavy grammar conflicts are found in the future,
+ we'll possibly need to make them reserved for sql_mode=ORACLE.
+
+ TODO: Allow these variables as SP lables when sql_mode=ORACLE.
+ TODO: Allow assigning of "SP characteristics" marked variables
+ inside compound blocks.
+*/
+keyword_directly_not_assignable:
+ CONTAINS_SYM { /* SP characteristic */ }
+ | LANGUAGE_SYM { /* SP characteristic */ }
+ | NO_SYM { /* SP characteristic */ }
+ | CHARSET { /* SET CHARSET utf8; */ }
+ | DO_SYM { /* Verb clause */ }
+ | REPAIR { /* Verb clause */ }
+ | HANDLER_SYM { /* Verb clause */ }
+ | CLOSE_SYM { /* Verb clause. Reserved in Oracle */ }
+ | OPEN_SYM { /* Verb clause. Reserved in Oracle */ }
+ | SAVEPOINT_SYM { /* Verb clause. Reserved in Oracle */ }
+ | TRUNCATE_SYM { /* Verb clause. Reserved in Oracle */ }
+ | BEGIN_SYM { /* Compound. Reserved in Oracle */ }
+ | END { /* Compound. Reserved in Oracle */ }
+ ;
+
+/*
* Keywords that we allow for labels in SPs.
* Anything that's the beginning of a statement or characteristics
* must be in keyword above, otherwise we get (harmful) shift/reduce
@@ -14271,7 +14325,7 @@ keyword_sp:
| X509_SYM {}
| XML_SYM {}
| YEAR_SYM {}
- | VIA_SYM {}
+ | VIA_SYM {}
;
/*
@@ -14285,22 +14339,15 @@ set:
SET
{
LEX *lex=Lex;
- lex->sql_command= SQLCOM_SET_OPTION;
- mysql_init_select(lex);
- lex->option_type=OPT_SESSION;
+ lex->set_stmt_init();
lex->var_list.empty();
- lex->autocommit= 0;
sp_create_assignment_lex(thd, yychar == YYEMPTY);
}
start_option_value_list
{}
| SET STATEMENT_SYM
{
- LEX *lex= Lex;
- mysql_init_select(lex);
- lex->option_type= OPT_SESSION;
- lex->sql_command= SQLCOM_SET_OPTION;
- lex->autocommit= 0;
+ Lex->set_stmt_init();
}
set_stmt_option_value_following_option_type_list
{
@@ -14314,6 +14361,28 @@ set:
{}
;
+set_assign:
+ internal_variable_name_directly_assignable SET_VAR
+ {
+ LEX *lex=Lex;
+ lex->set_stmt_init();
+ lex->var_list.empty();
+ sp_create_assignment_lex(thd, yychar == YYEMPTY);
+ }
+ set_expr_or_default
+ {
+ sp_pcontext *spc= Lex->spcont;
+ sp_variable *spv= spc->find_variable($1.base_name, false);
+
+ /* It is a local variable. */
+ if (Lex->set_local_variable(spv, $4))
+ MYSQL_YYABORT;
+
+ if (sp_create_assignment_instr(thd, yychar == YYEMPTY))
+ MYSQL_YYABORT;
+ }
+ ;
+
set_stmt_option_value_following_option_type_list:
/*
Only system variables can be used here. If this condition is changed
@@ -14598,77 +14667,37 @@ option_value_no_option_type:
internal_variable_name:
ident
{
- sp_pcontext *spc= thd->lex->spcont;
- sp_variable *spv;
-
- /* Best effort lookup for system variable. */
- if (!spc || !(spv = spc->find_variable($1, false)))
- {
- struct sys_var_with_base tmp= {NULL, $1};
+ if (Lex->init_internal_variable(&$$, $1))
+ MYSQL_YYABORT;
+ }
+ | ident '.' ident
+ {
+ if (Lex->init_internal_variable(&$$, $1, $3))
+ MYSQL_YYABORT;
+ }
+ | DEFAULT '.' ident
+ {
+ if (Lex->init_default_internal_variable(&$$, $3))
+ MYSQL_YYABORT;
+ }
+ ;
- /* Not an SP local variable */
- if (find_sys_var_null_base(thd, &tmp))
- MYSQL_YYABORT;
- $$= tmp;
- }
- else
- {
- /*
- Possibly an SP local variable (or a shadowed sysvar).
- Will depend on the context of the SET statement.
- */
- $$.var= NULL;
- $$.base_name= $1;
- }
+internal_variable_name_directly_assignable:
+ ident_directly_assignable
+ {
+ if (Lex->init_internal_variable(&$$, $1))
+ MYSQL_YYABORT;
}
- | ident '.' ident
+ | ident_directly_assignable '.' ident
{
- LEX *lex= Lex;
- if (check_reserved_words(&$1))
- {
- thd->parse_error();
+ if (Lex->init_internal_variable(&$$, $1, $3))
MYSQL_YYABORT;
- }
- if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
- (!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
- !my_strcasecmp(system_charset_info, $1.str, "OLD")))
- {
- if ($1.str[0]=='O' || $1.str[0]=='o')
- my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", ""));
- if (lex->trg_chistics.event == TRG_EVENT_DELETE)
- {
- my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0),
- "NEW", "on DELETE");
- MYSQL_YYABORT;
- }
- if (lex->trg_chistics.action_time == TRG_ACTION_AFTER)
- my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after "));
- /* This special combination will denote field of NEW row */
- $$.var= trg_new_row_fake_var;
- $$.base_name= $3;
- }
- else
- {
- sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
- if (!tmp)
- MYSQL_YYABORT;
- if (!tmp->is_struct())
- my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
- $$.var= tmp;
- $$.base_name= $1;
- }
}
| DEFAULT '.' ident
{
- sys_var *tmp=find_sys_var(thd, $3.str, $3.length);
- if (!tmp)
+ if (Lex->init_default_internal_variable(&$$, $3))
MYSQL_YYABORT;
- if (!tmp->is_struct())
- my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), $3.str);
- $$.var= tmp;
- $$.base_name.str= (char*) "default";
- $$.base_name.length= 7;
}
;
@@ -15485,11 +15514,6 @@ opt_release:
| NO_SYM RELEASE_SYM { $$= TVL_NO; }
;
-opt_savepoint:
- /* empty */ {}
- | SAVEPOINT_SYM {}
- ;
-
commit:
COMMIT_SYM opt_work opt_chain opt_release
{
@@ -15512,13 +15536,18 @@ rollback:
lex->tx_chain= $3;
lex->tx_release= $4;
}
- | ROLLBACK_SYM opt_work
- TO_SYM opt_savepoint ident
+ | ROLLBACK_SYM opt_work TO_SYM SAVEPOINT_SYM ident
{
LEX *lex=Lex;
lex->sql_command= SQLCOM_ROLLBACK_TO_SAVEPOINT;
lex->ident= $5;
}
+ | ROLLBACK_SYM opt_work TO_SYM ident
+ {
+ LEX *lex=Lex;
+ lex->sql_command= SQLCOM_ROLLBACK_TO_SAVEPOINT;
+ lex->ident= $4;
+ }
;
savepoint: