diff --git a/mysql-test/r/rpl_user_variables.result b/mysql-test/r/rpl_user_variables.result index b715b750b68..71147772ac4 100644 --- a/mysql-test/r/rpl_user_variables.result +++ b/mysql-test/r/rpl_user_variables.result @@ -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; diff --git a/mysql-test/r/user_var.result b/mysql-test/r/user_var.result index 23253f60de9..b7c64551dc0 100644 --- a/mysql-test/r/user_var.result +++ b/mysql-test/r/user_var.result @@ -5,6 +5,10 @@ set @a := connection_id() + 3; select @a - connection_id(); @a - connection_id() 3 +set @b := 1; +select @b; +@b +1 CREATE TABLE t1 ( i int not null, v int not null,index (i)); insert into t1 values (1,1),(1,3),(2,1); create table t2 (i int not null, unique (i)); diff --git a/mysql-test/t/rpl_user_variables.test b/mysql-test/t/rpl_user_variables.test index 7eeccaf64f2..35fbec72ac8 100644 --- a/mysql-test/t/rpl_user_variables.test +++ b/mysql-test/t/rpl_user_variables.test @@ -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; diff --git a/mysql-test/t/user_var.test b/mysql-test/t/user_var.test index 514eace25a3..947c944c79e 100644 --- a/mysql-test/t/user_var.test +++ b/mysql-test/t/user_var.test @@ -8,6 +8,9 @@ set @a := foo; set @a := connection_id() + 3; select @a - connection_id(); +set @b := 1; +select @b; + # Check using and setting variables with SELECT DISTINCT CREATE TABLE t1 ( i int not null, v int not null,index (i)); diff --git a/sql/item_func.cc b/sql/item_func.cc index b7979e7909c..0f9ee512be1 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -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,53 +2333,92 @@ 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; + 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) - { - uint size; - /* - First we need to store value of var_entry, when the next situation - appers: - > set @a:=1; - > insert into t1 values (@a), (@a:=@a+1), (@a:=@a+1); - We have to write to binlog value @a= 1; - */ - size= ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)) + var_entry->length; - if (!(user_var_event= (BINLOG_USER_VAR_EVENT *) thd->alloc(size))) - goto err; + var_entry= get_variable(&thd->user_vars, name, 0); - user_var_event->value= (char*) user_var_event + - ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)); - user_var_event->user_var_event= var_entry; - user_var_event->type= var_entry->type; - user_var_event->charset_number= var_entry->collation.collation->number; - if (!var_entry->value) - { - /* NULL value*/ - user_var_event->length= 0; - user_var_event->value= 0; - } - else - { - user_var_event->length= var_entry->length; - memcpy(user_var_event->value, var_entry->value, - var_entry->length); - } - var_entry->used_query_id= thd->query_id; - if (insert_dynamic(&thd->user_var_events, (gptr) &user_var_event)) - goto err; - } + if (!(opt_bin_log && is_update_query(thd->lex.sql_command))) + return; + + if (!var_entry) + { + /* + 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). + 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 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 + appers: + > set @a:=1; + > insert into t1 values (@a), (@a:=@a+1), (@a:=@a+1); + We have to write to binlog value @a= 1; + */ + size= ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)) + var_entry->length; + if (!(user_var_event= (BINLOG_USER_VAR_EVENT *) thd->alloc(size))) + goto err; + + user_var_event->value= (char*) user_var_event + + ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)); + user_var_event->user_var_event= var_entry; + user_var_event->type= var_entry->type; + user_var_event->charset_number= var_entry->collation.collation->number; + if (!var_entry->value) + { + /* NULL value*/ + user_var_event->length= 0; + user_var_event->value= 0; + } + else + { + user_var_event->length= var_entry->length; + 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: