Commit 2f11e5d1 authored by guilhem@gbichot2's avatar guilhem@gbichot2

fix for BUG#1331: "Unexistent user variable is not replicated".

When an update query is to be written to the binlog, and it reads unset
user variables (example: INSERT INTO t VALUEs(@A) where @A does not exist),
we create the variable like if it had been set with SET before, and we loop
(i.e. we redo the steps of logging, now that the variable exists).
parent e274bfb1
......@@ -21,6 +21,7 @@ set @q:='abc';
insert t1 values (@q), (@q:=concat(@q, 'n1')), (@q:=concat(@q, 'n2'));
set @a:=5;
insert into t1 values (@a),(@a);
insert into t1 values (@a),(@a),(@a*5);
select * from t1;
n
12345678901234
......@@ -45,6 +46,36 @@ abcn1
abcn1n2
5
5
NULL
NULL
NULL
select * from t1;
n
12345678901234
-12345678901234
0
-1
12.5
-12.5
This is a test
abc'def
abc\def
abc'def
NULL
NULL
0
1
2
5
abc
abcn1
abcn1n2
5
5
NULL
NULL
NULL
show binlog events from 141;
Log_name Pos Event_type Server_id Orig_log_pos Info
slave-bin.000001 141 User var 2 141 @i1=12345678901234
......@@ -63,13 +94,16 @@ slave-bin.000001 719 User var 2 719 @s5='abc'def'
slave-bin.000001 761 Query 1 761 use `test`; insert into t1 values (@s1), (@s2), (@s3), (@s4), (@s5)
slave-bin.000001 851 User var 2 851 @n1=NULL
slave-bin.000001 877 Query 1 877 use `test`; insert into t1 values (@n1)
slave-bin.000001 939 Query 1 939 use `test`; insert into t1 values (@n2)
slave-bin.000001 1001 Query 1 1001 use `test`; insert into t1 values (@a:=0), (@a:=@a+1), (@a:=@a+1)
slave-bin.000001 1089 User var 2 1089 @a=2
slave-bin.000001 1131 Query 1 1131 use `test`; insert into t1 values (@a+(@b:=@a+1))
slave-bin.000001 1203 User var 2 1203 @q='abc'
slave-bin.000001 1240 Query 1 1240 use `test`; insert t1 values (@q), (@q:=concat(@q, 'n1')), (@q:=concat(@q, 'n2'))
slave-bin.000001 1344 User var 2 1344 @a=5
slave-bin.000001 1386 Query 1 1386 use `test`; insert into t1 values (@a),(@a)
slave-bin.000001 939 User var 2 939 @n2=NULL
slave-bin.000001 965 Query 1 965 use `test`; insert into t1 values (@n2)
slave-bin.000001 1027 Query 1 1027 use `test`; insert into t1 values (@a:=0), (@a:=@a+1), (@a:=@a+1)
slave-bin.000001 1115 User var 2 1115 @a=2
slave-bin.000001 1157 Query 1 1157 use `test`; insert into t1 values (@a+(@b:=@a+1))
slave-bin.000001 1229 User var 2 1229 @q='abc'
slave-bin.000001 1266 Query 1 1266 use `test`; insert t1 values (@q), (@q:=concat(@q, 'n1')), (@q:=concat(@q, 'n2'))
slave-bin.000001 1370 User var 2 1370 @a=5
slave-bin.000001 1412 Query 1 1412 use `test`; insert into t1 values (@a),(@a)
slave-bin.000001 1478 User var 2 1478 @a=NULL
slave-bin.000001 1503 Query 1 1503 use `test`; insert into t1 values (@a),(@a),(@a*5)
drop table t1;
stop slave;
......@@ -29,13 +29,16 @@ insert into t1 values (@i1), (@i2), (@i3), (@i4);
insert into t1 values (@r1), (@r2);
insert into t1 values (@s1), (@s2), (@s3), (@s4), (@s5);
insert into t1 values (@n1);
insert into t1 values (@n2);
insert into t1 values (@n2); # not explicitely set before
insert into t1 values (@a:=0), (@a:=@a+1), (@a:=@a+1);
insert into t1 values (@a+(@b:=@a+1));
set @q:='abc';
insert t1 values (@q), (@q:=concat(@q, 'n1')), (@q:=concat(@q, 'n2'));
set @a:=5;
insert into t1 values (@a),(@a);
connection master1; # see if variable is reset in binlog when thread changes
insert into t1 values (@a),(@a),(@a*5);
select * from t1;
save_master_pos;
connection slave;
sync_with_master;
......
......@@ -2071,6 +2071,16 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
entry->value=0;
entry->length=0;
entry->update_query_id=0;
/*
If we are here, we were called from a SET or a query which sets a
variable. Imagine it is this:
INSERT INTO t SELECT @a:=10, @a:=@a+1.
Then when we have a Item_func_get_user_var (because of the @a+1) so we
think we have to write the value of @a to the binlog. But before that,
we have a Item_func_set_user_var to create @a (@a:=10), in this we mark
the variable as "already logged" (line below) so that it won't be logged
by Item_func_get_user_var (because that's not necessary).
*/
entry->used_query_id=current_thd->query_id;
entry->type=STRING_RESULT;
memcpy(entry->name.str, name.str, name.length+1);
......@@ -2083,7 +2093,10 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
return entry;
}
/*
When a user variable is updated (in a SET command or a query like SELECT @a:=
).
*/
bool Item_func_set_user_var::fix_fields(THD *thd, TABLE_LIST *tables,
Item **ref)
......@@ -2093,6 +2106,11 @@ bool Item_func_set_user_var::fix_fields(THD *thd, TABLE_LIST *tables,
!(entry= get_variable(&thd->user_vars, name, 1)))
return 1;
entry->type= cached_result_type;
/*
Remember the last query which updated it, this way a query can later know
if this variable is a constant item in the query (it is if update_query_id
is different from query_id).
*/
entry->update_query_id=thd->query_id;
return 0;
}
......@@ -2315,19 +2333,60 @@ longlong Item_func_get_user_var::val_int()
}
/*
When a user variable is invoked from an update query (INSERT, UPDATE etc),
stores this variable and its value in thd->user_var_events, so that it can be
written to the binlog (will be written just before the query is written, see
log.cc).
*/
void Item_func_get_user_var::fix_length_and_dec()
{
BINLOG_USER_VAR_EVENT *user_var_event;
THD *thd=current_thd;
if (!(opt_bin_log && is_update_query(thd->lex.sql_command)))
return;
BINLOG_USER_VAR_EVENT *user_var_event;
maybe_null=1;
decimals=NOT_FIXED_DEC;
max_length=MAX_BLOB_WIDTH;
if ((var_entry= get_variable(&thd->user_vars, name, 0)))
{
if (opt_bin_log && is_update_query(thd->lex.sql_command) &&
var_entry->used_query_id != thd->query_id)
/*
If the variable does not exist, it's NULL, but we want to create it so
that it gets into the binlog (if it didn't, the slave could be
influenced by a variable of the same name previously set by another
thread).
*/
if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
{
/*
We create it like if it had been explicitely set with SET before.
The 'new' mimicks what sql_yacc.yy does when 'SET @a=10;'.
sql_set_variables() is what is called from 'case SQLCOM_SET_OPTION'
in dispatch_command()). Instead of building a one-element list to pass to
sql_set_variables(), we could instead manually call check() and update();
this would save memory and time; but calling sql_set_variables() makes one
unique place to maintain (sql_set_variables()).
*/
List<set_var_base> tmp_var_list;
tmp_var_list.push_back(new set_var_user(new Item_func_set_user_var(name,
new Item_null())));
if (sql_set_variables(thd, &tmp_var_list)) /* this will create the variable */
goto err;
if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
goto err;
}
/*
If this variable was already stored in user_var_events by this query
(because it's used in more than one place in the query), don't store
it.
*/
else if (var_entry->used_query_id == thd->query_id)
return;
uint size;
/*
First we need to store value of var_entry, when the next situation
......@@ -2357,11 +2416,11 @@ void Item_func_get_user_var::fix_length_and_dec()
memcpy(user_var_event->value, var_entry->value,
var_entry->length);
}
/* Mark that this variable has been used by this query */
var_entry->used_query_id= thd->query_id;
if (insert_dynamic(&thd->user_var_events, (gptr) &user_var_event))
goto err;
}
}
return;
err:
......
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