← Back to team overview

maria-developers team mailing list archive

MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger

 

Hello Sanja,

Please review a patch for MDEV-15620.

Thanks!
commit 8306aaec113a061e5e1a576ff9f932f9cac1850a
Author: Alexander Barkov <bar@xxxxxxxxxxx>
Date:   Fri Mar 23 14:23:48 2018 +0400

    MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger
    
    The problem resided in this branch of the "option_value_no_option_type" rule:
    
    | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
    
    Summary:
    
    1. internal_variable_name initialized tmp.var to trg_new_row_fake_var (0x01).
    2. The condition "if (tmp.var == NULL)" did not check
       the special case with trg_new_row_fake_var,
       so Lex->set_system_variable(&tmp, $3, $6) was
       called with tmp.var pointing to trg_new_row_fake_var,
       which created a sys_var instance pointing to 0x01 instead of
       a real system variable.
    3. Later, at the trigger invocation time, this method was called:
       sys_var::do_deprecated_warning (this=0x1, thd=0x7ffe6c000a98)
       Notice, "this" is equal to trg_new_row_fake_var (0x01)
    
    Solution:
    
    The old implementation with separate rules
    internal_variable_name (in sql_yacc.yy and sql_yacc_ora.yy) and
    internal_variable_name_directly_assignable (in sql_yacc_ora.yy only)
    was too complex and hard to follow.
    
    Rewriting the code in a more straightforward way.
    
    1. Changing LEX::set_system_variable()
    
    from:
    
    bool set_system_variable(struct sys_var_with_base *, enum_var_type, Item *);
    
    to:
    
    bool set_system_variable(enum_var_type, sys_var *, const LEX_CSTRING *, Item *);
    
    2. Adding new methods in LEX, which operate with variable names:
    
    bool set_trigger_field(const LEX_CSTRING *, const LEX_CSTRING *, Item *);
    bool set_system_variable(enum_var_type var_type, const LEX_CSTRING *name,
                             Item *val);
    bool set_system_variable(THD *thd, enum_var_type var_type,
                             const LEX_CSTRING *name1,
                             const LEX_CSTRING *name2,
                             Item *val);
    bool set_default_system_variable(enum_var_type var_type,
                                     const LEX_CSTRING *name,
                                     Item *val);
    bool set_variable(const LEX_CSTRING *name, Item *item);
    
    3. Changing the grammar to call the new methods directly
       in option_value_no_option_type,
       Removing rules internal_variable_name and
       internal_variable_name_directly_assignable.
    
    4. Removing "struct sys_var_with_base" and trg_new_row_fake_var.
    
    Good side effect:
    
    - The code in /sql reduced from 314 to 183 lines.
    - MDEV-15615 Unexpected syntax error instead of "Unknown system variable" ...
      was also fixed automatically

diff --git a/mysql-test/r/parser.result b/mysql-test/r/parser.result
index f197fbe..1a65f8f 100644
--- a/mysql-test/r/parser.result
+++ b/mysql-test/r/parser.result
@@ -1328,3 +1328,19 @@ CREATE TABLE raw (raw int);
 DROP TABLE raw;
 CREATE TABLE varchar2 (varchar2 int);
 DROP TABLE varchar2;
+#
+# MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger
+#
+CREATE TABLE t1 (a INT);
+CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET @@NEW.a=0;
+ERROR HY000: Unknown structured system variable or ROW routine variable 'NEW'
+DROP TABLE t1;
+#
+# MDEV-15615 Unexpected syntax error instead of "Unknown system variable" inside an SP
+#
+BEGIN NOT ATOMIC
+DECLARE a INT;
+SET GLOBAL a=10;
+END;
+$$
+ERROR HY000: Unknown system variable 'a'
diff --git a/mysql-test/r/set_statement.result b/mysql-test/r/set_statement.result
index 5bcf825..c34e117 100644
--- a/mysql-test/r/set_statement.result
+++ b/mysql-test/r/set_statement.result
@@ -881,7 +881,7 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp
 SET max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
 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 'FOR INSERT INTO t1 VALUES (1,2)' at line 1
 SET STATEMENT GLOBAL max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
-ERROR HY000: Unknown system variable 'GLOBAL'
+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 'max_error_count=100 FOR INSERT INTO t1 VALUES (1,2)' at line 1
 SET STATEMENT @@global.max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
 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 '@@global.max_error_count=100 FOR INSERT INTO t1 VALUES (1,2)' at line 1
 ''
diff --git a/mysql-test/suite/compat/oracle/r/func_misc.result b/mysql-test/suite/compat/oracle/r/func_misc.result
index 0e2ba0c..f285423 100644
--- a/mysql-test/suite/compat/oracle/r/func_misc.result
+++ b/mysql-test/suite/compat/oracle/r/func_misc.result
@@ -35,7 +35,7 @@ CALL p1('SELECT 1');
 Error1: 0 normal, successful completition
 CALL p1('xxx');
 'Error2: ' || SQLCODE || ' ' || SQLERRM
-Error2: 1193 Unknown system variable 'xxx'
+Error2: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1
 CALL p1('SELECT 1');
 1
 1
diff --git a/mysql-test/suite/compat/oracle/r/parser.result b/mysql-test/suite/compat/oracle/r/parser.result
new file mode 100644
index 0000000..29588a6
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/r/parser.result
@@ -0,0 +1,18 @@
+SET sql_mode=ORACLE;
+#
+# MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger
+#
+CREATE TABLE t1 (a INT);
+CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET @@NEW.a=0;
+ERROR HY000: Unknown structured system variable or ROW routine variable 'NEW'
+DROP TABLE t1;
+#
+# MDEV-15615 Unexpected syntax error instead of "Unknown system variable" inside an SP
+#
+DECLARE
+a INT;
+BEGIN
+SET GLOBAL a=10;
+END;
+$$
+ERROR HY000: Unknown system variable 'a'
diff --git a/mysql-test/suite/compat/oracle/r/trigger.result b/mysql-test/suite/compat/oracle/r/trigger.result
index f77a565..7c24c78 100644
--- a/mysql-test/suite/compat/oracle/r/trigger.result
+++ b/mysql-test/suite/compat/oracle/r/trigger.result
@@ -1,10 +1,10 @@
 set sql_mode=ORACLE;
 :NEW.a := 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 'a := 1' at line 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 ':NEW.a := 1' at line 1
 :OLD.a := 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 'a := 1' at line 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 ':OLD.a := 1' at line 1
 :OLa.a := 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 'a := 1' at line 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 ':OLa.a := 1' at line 1
 SELECT :NEW.a;
 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 'a' at line 1
 SELECT :OLD.a;
diff --git a/mysql-test/suite/compat/oracle/t/parser.test b/mysql-test/suite/compat/oracle/t/parser.test
new file mode 100644
index 0000000..5aa37c1
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/t/parser.test
@@ -0,0 +1,24 @@
+SET sql_mode=ORACLE;
+
+--echo #
+--echo # MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger
+--echo #
+
+CREATE TABLE t1 (a INT);
+--error ER_UNKNOWN_STRUCTURED_VARIABLE
+CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET @@NEW.a=0;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-15615 Unexpected syntax error instead of "Unknown system variable" inside an SP
+--echo #
+
+DELIMITER $$;
+--error ER_UNKNOWN_SYSTEM_VARIABLE
+DECLARE
+  a INT;
+BEGIN
+  SET GLOBAL a=10;
+END;
+$$
+DELIMITER ;$$
diff --git a/mysql-test/t/parser.test b/mysql-test/t/parser.test
index 98eaa7a..08b6d34 100644
--- a/mysql-test/t/parser.test
+++ b/mysql-test/t/parser.test
@@ -1349,3 +1349,26 @@ DROP TABLE raw;
 
 CREATE TABLE varchar2 (varchar2 int);
 DROP TABLE varchar2;
+
+
+--echo #
+--echo # MDEV-15620 Crash when using "SET @@NEW.a=expr" inside a trigger
+--echo #
+
+CREATE TABLE t1 (a INT);
+--error ER_UNKNOWN_STRUCTURED_VARIABLE
+CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW SET @@NEW.a=0;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-15615 Unexpected syntax error instead of "Unknown system variable" inside an SP
+--echo #
+
+DELIMITER $$;
+--error ER_UNKNOWN_SYSTEM_VARIABLE
+BEGIN NOT ATOMIC
+  DECLARE a INT;
+  SET GLOBAL a=10;
+END;
+$$
+DELIMITER ;$$
diff --git a/mysql-test/t/set_statement.test b/mysql-test/t/set_statement.test
index 32f56e4..cc36155 100644
--- a/mysql-test/t/set_statement.test
+++ b/mysql-test/t/set_statement.test
@@ -828,7 +828,7 @@ SET STATEMENT max_error_count=100 INSERT t1 VALUES (1,2);
 SET STATEMENT FOR INSERT INTO t1 VALUES (1,2);
 --error ER_PARSE_ERROR
 SET max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
---error ER_UNKNOWN_SYSTEM_VARIABLE
+--error ER_PARSE_ERROR
 SET STATEMENT GLOBAL max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
 --error ER_PARSE_ERROR
 SET STATEMENT @@global.max_error_count=100 FOR INSERT INTO t1 VALUES (1,2);
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 1bdbd4e..bf373dd 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -688,17 +688,6 @@ sys_var *intern_find_sys_var(const char *str, size_t 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_clex_str;
-
-  return thd->is_error();
-}
-
-
 /**
   Execute update of all variables.
 
diff --git a/sql/set_var.h b/sql/set_var.h
index d0143e1..9014a9c 100644
--- a/sql/set_var.h
+++ b/sql/set_var.h
@@ -393,7 +393,6 @@ 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 b169b9e..dcf0b7a 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -41,12 +41,6 @@ void LEX::parse_error(uint err_number)
 
 static int lex_one_token(YYSTYPE *yylval, THD *thd);
 
-/*
-  We are using pointer to this variable for distinguishing between assignment
-  to NEW row field (when parsing trigger definition) and structured variable.
-*/
-
-sys_var *trg_new_row_fake_var= (sys_var*) 0x01;
 
 /**
   LEX_STRING constant for null-string to be used in parser and other places.
@@ -5253,36 +5247,7 @@ LEX::find_variable(const LEX_CSTRING *name,
 }
 
 
-bool LEX::init_internal_variable(struct sys_var_with_base *variable,
-                                 const LEX_CSTRING *name)
-{
-  sp_variable *spv;
-  const Sp_rcontext_handler *rh;
-
-  /* Best effort lookup for system variable. */
-  if (!(spv= find_variable(name, &rh)))
-  {
-    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::is_trigger_new_or_old_reference(const LEX_CSTRING *name)
+bool LEX::is_trigger_new_or_old_reference(const LEX_CSTRING *name) const
 {
   return sphead && sphead->m_handler->type() == TYPE_ENUM_TRIGGER &&
          name->length == 3 &&
@@ -5291,68 +5256,6 @@ bool LEX::is_trigger_new_or_old_reference(const LEX_CSTRING *name)
 }
 
 
-bool LEX::init_internal_variable(struct sys_var_with_base *variable,
-                                 const LEX_CSTRING *dbname,
-                                 const LEX_CSTRING *name)
-{
-  if (check_reserved_words(dbname))
-  {
-    my_error(ER_UNKNOWN_STRUCTURED_VARIABLE, MYF(0),
-             (int) dbname->length, dbname->str);
-    return true;
-  }
-  if (is_trigger_new_or_old_reference(dbname))
-  {
-    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_ex(thd, name->str, name->length, true, false);
-  if (!tmp)
-  {
-    my_error(ER_UNKNOWN_STRUCTURED_VARIABLE, MYF(0),
-             (int) dbname->length, dbname->str);
-    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_CSTRING 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;
-}
-
 void LEX::sp_variable_declarations_init(THD *thd, int nvars)
 {
   sp_variable *spvar= spcont->get_last_context_variable();
@@ -6994,38 +6897,6 @@ bool LEX::set_user_variable(THD *thd, const LEX_CSTRING *name, Item *val)
 }
 
 
-/*
-  Perform assignment for a trigger, a system variable, or an SP variable.
-  "variable" be previously set by init_internal_variable(variable, name).
-*/
-bool LEX::set_variable(struct sys_var_with_base *variable, Item *item)
-{
-  if (variable->var == trg_new_row_fake_var)
-  {
-    /* We are in trigger and assigning value to field of new row */
-    return set_trigger_new_row(&variable->base_name, item);
-  }
-  if (variable->var)
-  {
-    /* It is a system variable. */
-    return set_system_variable(variable, option_type, item);
-  }
-
-  /*
-    spcont and spv should not be NULL, as the variable
-    was previously checked by init_internal_variable().
-  */
-  DBUG_ASSERT(spcont);
-  const Sp_rcontext_handler *rh;
-  sp_pcontext *ctx;
-  sp_variable *spv= find_variable(&variable->base_name, &ctx, &rh);
-  DBUG_ASSERT(spv);
-  DBUG_ASSERT(ctx);
-  DBUG_ASSERT(rh);
-  return sphead->set_local_variable(thd, ctx, rh, spv, item, this, true);
-}
-
-
 Item *LEX::create_item_ident_nosp(THD *thd, LEX_CSTRING *name)
 {
   if (current_select->parsing_place != IN_HAVING ||
@@ -7084,6 +6955,17 @@ Item *LEX::create_item_ident_sp(THD *thd, LEX_CSTRING *name,
 }
 
 
+
+bool LEX::set_variable(const LEX_CSTRING *name, Item *item)
+{
+  sp_pcontext *ctx;
+  const Sp_rcontext_handler *rh;
+  sp_variable *spv= find_variable(name, &ctx, &rh);
+  return spv ? sphead->set_local_variable(thd, ctx, rh, spv, item, this, true) :
+               set_system_variable(option_type, name, item);
+}
+
+
 /**
   Generate instructions for:
     SET x.y= expr;
@@ -7111,10 +6993,76 @@ bool LEX::set_variable(const LEX_CSTRING *name1,
                                                 item, this);
   }
 
-  // A trigger field or a system variable
-  sys_var_with_base sysvar;
-  return init_internal_variable(&sysvar, name1, name2) ||
-         set_variable(&sysvar, item);
+  if (is_trigger_new_or_old_reference(name1))
+    return set_trigger_field(name1, name2, item);
+
+  return set_system_variable(thd, option_type, name1, name2, item);
+}
+
+
+bool LEX::set_default_system_variable(enum_var_type var_type,
+                                      const LEX_CSTRING *name,
+                                      Item *val)
+{
+  static LEX_CSTRING default_base_name= {STRING_WITH_LEN("default")};
+  sys_var *var= find_sys_var(thd, name->str, name->length);
+  if (!var)
+    return true;
+  if (!var->is_struct())
+    my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), name->str);
+  return set_system_variable(var_type, var, &default_base_name, val);
+}
+
+
+bool LEX::set_system_variable(enum_var_type var_type,
+                              const LEX_CSTRING *name,
+                              Item *val)
+{
+  sys_var *var= find_sys_var(thd, name->str, name->length);
+  DBUG_ASSERT(thd->is_error() || var != NULL);
+  return var ? set_system_variable(var_type, var, &null_clex_str, val) : true;
+}
+
+
+bool LEX::set_system_variable(THD *thd, enum_var_type var_type,
+                              const LEX_CSTRING *name1,
+                              const LEX_CSTRING *name2,
+                              Item *val)
+{
+  sys_var *tmp;
+  if (check_reserved_words(name1) ||
+      !(tmp= find_sys_var_ex(thd, name2->str, name2->length, true, false)))
+  {
+    my_error(ER_UNKNOWN_STRUCTURED_VARIABLE, MYF(0),
+             (int) name1->length, name1->str);
+    return true;
+  }
+  if (!tmp->is_struct())
+    my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), name2->str);
+  return set_system_variable(var_type, tmp, name1, val);
+}
+
+
+bool LEX::set_trigger_field(const LEX_CSTRING *name1, const LEX_CSTRING *name2,
+                            Item *val)
+{
+  DBUG_ASSERT(is_trigger_new_or_old_reference(name1));
+  if (name1->str[0]=='O' || name1->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;
+  }
+  return set_trigger_new_row(name2, val);
 }
 
 
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index f0241a3..9856ca1 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -155,15 +155,6 @@ extern uint binlog_unsafe_map[256];
 void binlog_unsafe_map_init();
 #endif
 
-/**
-  used by the parser to store internal variable name
-*/
-struct sys_var_with_base
-{
-  sys_var *var;
-  LEX_CSTRING base_name;
-};
-
 struct LEX_TYPE
 {
   enum enum_field_types type;
@@ -1338,8 +1329,6 @@ struct st_trg_chistics: public st_trg_execution_order
 
 };
 
-extern sys_var *trg_new_row_fake_var;
-
 enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE,
                       XA_SUSPEND, XA_FOR_MIGRATE};
 
@@ -3220,9 +3209,20 @@ struct LEX: public Query_tables_list
                                 enum sub_select_type type,
                                 bool is_top_level);
   bool setup_select_in_parentheses();
-  bool set_trigger_new_row(LEX_CSTRING *name, Item *val);
-  bool set_system_variable(struct sys_var_with_base *tmp,
-                           enum enum_var_type var_type, Item *val);
+  bool set_trigger_new_row(const LEX_CSTRING *name, Item *val);
+  bool set_trigger_field(const LEX_CSTRING *name1, const LEX_CSTRING *name2,
+                         Item *val);
+  bool set_system_variable(enum_var_type var_type, sys_var *var,
+                           const LEX_CSTRING *base_name, Item *val);
+  bool set_system_variable(enum_var_type var_type, const LEX_CSTRING *name,
+                           Item *val);
+  bool set_system_variable(THD *thd, enum_var_type var_type,
+                           const LEX_CSTRING *name1,
+                           const LEX_CSTRING *name2,
+                           Item *val);
+  bool set_default_system_variable(enum_var_type var_type,
+                                   const LEX_CSTRING *name,
+                                   Item *val);
   bool set_user_variable(THD *thd, const LEX_CSTRING *name, Item *val);
   void set_stmt_init();
   sp_name *make_sp_name(THD *thd, const LEX_CSTRING *name);
@@ -3265,14 +3265,7 @@ struct LEX: public Query_tables_list
     sp_pcontext *not_used_ctx;
     return find_variable(name, &not_used_ctx, rh);
   }
-  bool init_internal_variable(struct sys_var_with_base *variable,
-                             const LEX_CSTRING *name);
-  bool init_internal_variable(struct sys_var_with_base *variable,
-                              const LEX_CSTRING *dbname,
-                              const LEX_CSTRING *name);
-  bool init_default_internal_variable(struct sys_var_with_base *variable,
-                                      LEX_CSTRING name);
-  bool set_variable(struct sys_var_with_base *variable, Item *item);
+  bool set_variable(const LEX_CSTRING *name, Item *item);
   bool set_variable(const LEX_CSTRING *name1, const LEX_CSTRING *name2,
                     Item *item);
   void sp_variable_declarations_init(THD *thd, int nvars);
@@ -3464,7 +3457,7 @@ struct LEX: public Query_tables_list
                         const LEX_CSTRING *var_name,
                         const LEX_CSTRING *field_name);
 
-  bool is_trigger_new_or_old_reference(const LEX_CSTRING *name);
+  bool is_trigger_new_or_old_reference(const LEX_CSTRING *name) const;
 
   Item *create_and_link_Item_trigger_field(THD *thd, const LEX_CSTRING *name,
                                            bool new_row);
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 35ec2d2..e3ceb3b 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -288,27 +288,28 @@ int LEX::case_stmt_action_then()
 */
 
 bool
-LEX::set_system_variable(struct sys_var_with_base *tmp,
-                         enum enum_var_type var_type, Item *val)
+LEX::set_system_variable(enum enum_var_type var_type,
+                         sys_var *sysvar, const LEX_CSTRING *base_name,
+                         Item *val)
 {
-  set_var *var;
+  set_var *setvar;
 
   /* No AUTOCOMMIT from a stored function or trigger. */
-  if (spcont && tmp->var == Sys_autocommit_ptr)
+  if (spcont && sysvar == Sys_autocommit_ptr)
     sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
 
   if (val && val->type() == Item::FIELD_ITEM &&
       ((Item_field*)val)->table_name)
   {
-    my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), tmp->var->name.str);
+    my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), sysvar->name.str);
     return TRUE;
   }
 
-  if (! (var= new (thd->mem_root)
-         set_var(thd, var_type, tmp->var, &tmp->base_name, val)))
+  if (!(setvar= new (thd->mem_root) set_var(thd, var_type, sysvar,
+                                            base_name, val)))
     return TRUE;
 
-  return var_list.push_back(var, thd->mem_root);
+  return var_list.push_back(setvar, thd->mem_root);
 }
 
 
@@ -322,7 +323,7 @@ LEX::set_system_variable(struct sys_var_with_base *tmp,
   @return TRUE if error, FALSE otherwise.
 */
 
-bool LEX::set_trigger_new_row(LEX_CSTRING *name, Item *val)
+bool LEX::set_trigger_new_row(const LEX_CSTRING *name, Item *val)
 {
   Item_trigger_field *trg_fld;
   sp_instr_set_trigger_field *sp_fld;
@@ -778,7 +779,6 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr)
   LEX_CSTRING lex_str;
   LEX_SYMBOL symbol;
   Lex_string_with_metadata_st lex_string_with_metadata;
-  struct sys_var_with_base variable;
   Lex_string_with_pos_st lex_string_with_pos;
   Lex_spblock_st spblock;
   Lex_spblock_handlers_st spblock_handlers;
@@ -1869,8 +1869,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
         opt_load_data_charset
         UNDERSCORE_CHARSET
 
-%type <variable> internal_variable_name
-
 %type <select_lex> subselect
         get_select_lex get_select_lex_derived
         simple_table
@@ -15960,25 +15958,20 @@ opt_var_ident_type:
 
 /* Option values with preceding option_type. */
 option_value_following_option_type:
-          internal_variable_name equal set_expr_or_default
+          ident equal set_expr_or_default
           {
-            LEX *lex= Lex;
-
-            if ($1.var && $1.var != trg_new_row_fake_var)
-            {
-              /* It is a system variable. */
-              if (lex->set_system_variable(&$1, lex->option_type, $3))
-                MYSQL_YYABORT;
-            }
-            else
-            {
-              /*
-                Not in trigger assigning value to new row,
-                and option_type preceding local variable is illegal.
-              */
-              thd->parse_error();
+            if (Lex->set_system_variable(Lex->option_type, &$1, $3))
+              MYSQL_YYABORT;
+          }
+        | ident '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_system_variable(thd, Lex->option_type, &$1, &$3, $5))
+              MYSQL_YYABORT;
+          }
+        | DEFAULT '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_default_system_variable(Lex->option_type, &$3, $5))
               MYSQL_YYABORT;
-            }
           }
         ;
 
@@ -15986,9 +15979,7 @@ option_value_following_option_type:
 option_value_no_option_type:
           ident equal set_expr_or_default
           {
-            struct sys_var_with_base var;
-            if (Lex->init_internal_variable(&var, &$1) ||
-                Lex->set_variable(&var, $3))
+            if (Lex->set_variable(&$1, $3))
               MYSQL_YYABORT;
           }
         | ident '.' ident equal set_expr_or_default
@@ -15999,9 +15990,7 @@ option_value_no_option_type:
           }
         | DEFAULT '.' ident equal set_expr_or_default
           {
-            struct sys_var_with_base var;
-            if (Lex->init_default_internal_variable(&var, $3) ||
-                Lex->set_variable(&var, $5))
+            if (Lex->set_default_system_variable(Lex->option_type, &$3, $5))
               MYSQL_YYABORT;
           }
         | '@' ident_or_text equal expr
@@ -16009,16 +15998,19 @@ option_value_no_option_type:
             if (Lex->set_user_variable(thd, &$2, $4))
               MYSQL_YYABORT;
           }
-        | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
+        | '@' '@' opt_var_ident_type ident equal set_expr_or_default
           {
-            struct sys_var_with_base tmp= $4;
-            /* Lookup if necessary: must be a system variable. */
-            if (tmp.var == NULL)
-            {
-              if (find_sys_var_null_base(thd, &tmp))
-                MYSQL_YYABORT;
-            }
-            if (Lex->set_system_variable(&tmp, $3, $6))
+            if (Lex->set_system_variable($3, &$4, $6))
+              MYSQL_YYABORT;
+          }
+        | '@' '@' opt_var_ident_type ident '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_system_variable(thd, $3, &$4, &$6, $8))
+              MYSQL_YYABORT;
+          }
+        | '@' '@' opt_var_ident_type DEFAULT '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_default_system_variable($3, &$6, $8))
               MYSQL_YYABORT;
           }
         | charset old_or_new_charset_name_or_default
@@ -16116,25 +16108,6 @@ option_value_no_option_type:
           }
         ;
 
-
-internal_variable_name:
-          ident
-          {
-            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;
-          }
-        ;
-
 transaction_characteristics:
           transaction_access_mode
         | isolation_level
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index c8f93c1..bdc4694 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -171,7 +171,6 @@ void ORAerror(THD *thd, const char *s)
   LEX_CSTRING lex_str;
   LEX_SYMBOL symbol;
   Lex_string_with_metadata_st lex_string_with_metadata;
-  struct sys_var_with_base variable;
   Lex_string_with_pos_st lex_string_with_pos;
   Lex_spblock_st spblock;
   Lex_spblock_handlers_st spblock_handlers;
@@ -1271,9 +1270,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
         opt_load_data_charset
         UNDERSCORE_CHARSET
 
-%type <variable> internal_variable_name
-                 internal_variable_name_directly_assignable
-
 %type <select_lex> subselect
         get_select_lex get_select_lex_derived
         query_specification
@@ -14809,9 +14805,9 @@ simple_ident_q2:
             if (lex->is_trigger_new_or_old_reference(&$2))
             {
               bool new_row= ($2.str[0]=='N' || $2.str[0]=='n');
-              if (!($$= Lex->create_and_link_Item_trigger_field(thd,
-                                                               &$4,
-                                                               new_row)))
+              if (!($$= lex->create_and_link_Item_trigger_field(thd,
+                                                                &$4,
+                                                                new_row)))
                 MYSQL_YYABORT;
             }
             else
@@ -15659,7 +15655,7 @@ set:
         ;
 
 set_assign:
-          internal_variable_name_directly_assignable SET_VAR
+          ident_directly_assignable SET_VAR
           {
             LEX *lex=Lex;
             lex->set_stmt_init();
@@ -15687,6 +15683,25 @@ set_assign:
                 lex->sphead->restore_lex(thd))
               MYSQL_YYABORT;
           }
+        | colon_with_pos ident '.' ident SET_VAR
+          {
+            LEX *lex= Lex;
+            if (!lex->is_trigger_new_or_old_reference(&$2))
+            {
+              thd->parse_error(ER_SYNTAX_ERROR, $1);
+              MYSQL_YYABORT;
+            }
+            lex->set_stmt_init();
+            lex->var_list.empty();
+            sp_create_assignment_lex(thd, yychar == YYEMPTY);
+          }
+          set_expr_or_default
+          {
+            LEX_CSTRING tmp= { $2.str, $2.length };
+            if (Lex->set_trigger_field(&tmp, &$4, $7) ||
+                sp_create_assignment_instr(thd, yychar == YYEMPTY))
+              MYSQL_YYABORT;
+          }
         ;
 
 set_stmt_option_value_following_option_type_list:
@@ -15798,25 +15813,20 @@ opt_var_ident_type:
 
 /* Option values with preceding option_type. */
 option_value_following_option_type:
-          internal_variable_name equal set_expr_or_default
+          ident equal set_expr_or_default
           {
-            LEX *lex= Lex;
-
-            if ($1.var && $1.var != trg_new_row_fake_var)
-            {
-              /* It is a system variable. */
-              if (lex->set_system_variable(&$1, lex->option_type, $3))
-                MYSQL_YYABORT;
-            }
-            else
-            {
-              /*
-                Not in trigger assigning value to new row,
-                and option_type preceding local variable is illegal.
-              */
-              thd->parse_error();
+            if (Lex->set_system_variable(Lex->option_type, &$1, $3))
+              MYSQL_YYABORT;
+          }
+        | ident '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_system_variable(thd, Lex->option_type, &$1, &$3, $5))
+              MYSQL_YYABORT;
+          }
+        | DEFAULT '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_default_system_variable(Lex->option_type, &$3, $5))
               MYSQL_YYABORT;
-            }
           }
         ;
 
@@ -15824,9 +15834,7 @@ option_value_following_option_type:
 option_value_no_option_type:
           ident equal set_expr_or_default
           {
-            struct sys_var_with_base var;
-            if (Lex->init_internal_variable(&var, &$1) ||
-                Lex->set_variable(&var, $3))
+            if (Lex->set_variable(&$1, $3))
               MYSQL_YYABORT;
           }
         | ident '.' ident equal set_expr_or_default
@@ -15837,9 +15845,7 @@ option_value_no_option_type:
           }
         | DEFAULT '.' ident equal set_expr_or_default
           {
-            struct sys_var_with_base var;
-            if (Lex->init_default_internal_variable(&var, $3) ||
-                Lex->set_variable(&var, $5))
+            if (Lex->set_default_system_variable(Lex->option_type, &$3, $5))
               MYSQL_YYABORT;
           }
         | '@' ident_or_text equal expr
@@ -15847,16 +15853,19 @@ option_value_no_option_type:
             if (Lex->set_user_variable(thd, &$2, $4))
               MYSQL_YYABORT;
           }
-        | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
+        | '@' '@' opt_var_ident_type ident equal set_expr_or_default
           {
-            struct sys_var_with_base tmp= $4;
-            /* Lookup if necessary: must be a system variable. */
-            if (tmp.var == NULL)
-            {
-              if (find_sys_var_null_base(thd, &tmp))
-                MYSQL_YYABORT;
-            }
-            if (Lex->set_system_variable(&tmp, $3, $6))
+            if (Lex->set_system_variable($3, &$4, $6))
+              MYSQL_YYABORT;
+          }
+        | '@' '@' opt_var_ident_type ident '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_system_variable(thd, $3, &$4, &$6, $8))
+              MYSQL_YYABORT;
+          }
+        | '@' '@' opt_var_ident_type DEFAULT '.' ident equal set_expr_or_default
+          {
+            if (Lex->set_default_system_variable($3, &$6, $8))
               MYSQL_YYABORT;
           }
         | charset old_or_new_charset_name_or_default
@@ -15955,48 +15964,6 @@ option_value_no_option_type:
         ;
 
 
-internal_variable_name:
-          ident
-          {
-            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;
-          }
-        ;
-
-
-internal_variable_name_directly_assignable:
-          ident_directly_assignable
-          {
-            if (Lex->init_internal_variable(&$$, &$1))
-              MYSQL_YYABORT;
-          }
-        | DEFAULT '.' ident
-          {
-            if (Lex->init_default_internal_variable(&$$, $3))
-              MYSQL_YYABORT;
-          }
-        | colon_with_pos ident_directly_assignable '.' ident
-          {
-            if (!Lex->is_trigger_new_or_old_reference(&$2))
-            {
-              thd->parse_error();
-              MYSQL_YYABORT;
-            }
-            if (Lex->init_internal_variable(&$$, &$2, &$4))
-              MYSQL_YYABORT;
-          }
-        ;
-
 transaction_characteristics:
           transaction_access_mode
         | isolation_level