diff --git a/mysql-test/r/join_nested.result b/mysql-test/r/join_nested.result index 6b9bea7aa21..75caa6b45a4 100644 --- a/mysql-test/r/join_nested.result +++ b/mysql-test/r/join_nested.result @@ -1562,3 +1562,46 @@ id ngroupbynsa 2 1 2 1 DROP TABLE t1,t2,t3,t4,t5; +CREATE TABLE t1 ( +id int NOT NULL PRIMARY KEY, +ct int DEFAULT NULL, +pc int DEFAULT NULL, +INDEX idx_ct (ct), +INDEX idx_pc (pc) +); +INSERT INTO t1 VALUES +(1,NULL,NULL),(2,NULL,NULL),(3,NULL,NULL),(4,NULL,NULL),(5,NULL,NULL); +CREATE TABLE t2 ( +id int NOT NULL PRIMARY KEY, +sr int NOT NULL, +nm varchar(255) NOT NULL, +INDEX idx_sr (sr) +); +INSERT INTO t2 VALUES +(2441905,4308,'LesAbymes'),(2441906,4308,'Anse-Bertrand'); +CREATE TABLE t3 ( +id int NOT NULL PRIMARY KEY, +ct int NOT NULL, +ln int NOT NULL, +INDEX idx_ct (ct), +INDEX idx_ln (ln) +); +CREATE TABLE t4 ( +id int NOT NULL PRIMARY KEY, +nm varchar(255) NOT NULL +); +INSERT INTO t4 VALUES (4308,'Guadeloupe'),(4309,'Martinique'); +SELECT t1.* +FROM t1 LEFT JOIN +(t2 LEFT JOIN t3 ON t3.ct=t2.id AND t3.ln='5') ON t1.ct=t2.id +WHERE t1.id='5'; +id ct pc +5 NULL NULL +SELECT t1.*, t4.nm +FROM t1 LEFT JOIN +(t2 LEFT JOIN t3 ON t3.ct=t2.id AND t3.ln='5') ON t1.ct=t2.id +LEFT JOIN t4 ON t2.sr=t4.id +WHERE t1.id='5'; +id ct pc nm +5 NULL NULL NULL +DROP TABLE t1,t2,t3,t4; diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index df0269defd9..d82b07fa7d3 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -1536,6 +1536,13 @@ a 1 2 DEALLOCATE PREPARE stmt; +PREPARE stmt FROM 'SELECT a FROM t1 WHERE (SELECT b FROM t2 limit ?) IS NULL'; +SET @arg=1; +EXECUTE stmt USING @arg; +a +1 +2 +DEALLOCATE PREPARE stmt; DROP TABLE t1,t2; End of 5.0 tests. create procedure proc_1() reset query cache; diff --git a/mysql-test/r/type_binary.result b/mysql-test/r/type_binary.result index 0b340502eaf..432c58272a2 100644 --- a/mysql-test/r/type_binary.result +++ b/mysql-test/r/type_binary.result @@ -136,4 +136,13 @@ insert into t1 values(NULL, 0x412020); ERROR 22001: Data too long for column 'vb' at row 1 drop table t1; set @@sql_mode= @old_sql_mode; +create table t1(f1 int, f2 binary(2) not null, f3 char(2) not null); +insert into t1 set f1=1; +Warnings: +Warning 1364 Field 'f2' doesn't have a default value +Warning 1364 Field 'f3' doesn't have a default value +select hex(f2), hex(f3) from t1; +hex(f2) hex(f3) +0000 +drop table t1; End of 5.0 tests diff --git a/mysql-test/r/user_var.result b/mysql-test/r/user_var.result index 8382f521b84..3a70dcddd24 100644 --- a/mysql-test/r/user_var.result +++ b/mysql-test/r/user_var.result @@ -301,7 +301,14 @@ select @var:=f2 from t1 group by f1 order by f2 desc limit 1; select @var; @var 3 -drop table t1; +create table t2 as select @var:=f2 from t1 group by f1 order by f2 desc limit 1; +select * from t2; +@var:=f2 +3 +select @var; +@var +3 +drop table t1,t2; insert into city 'blah'; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''blah'' at line 1 SHOW COUNT(*) WARNINGS; diff --git a/mysql-test/t/join_nested.test b/mysql-test/t/join_nested.test index 69886d035bf..e7405418be7 100644 --- a/mysql-test/t/join_nested.test +++ b/mysql-test/t/join_nested.test @@ -994,3 +994,54 @@ SELECT t1.id1 AS id, t5.id1 AS ngroupbynsa DROP TABLE t1,t2,t3,t4,t5; +# +# Test for bug #24345: crash with nested left outer join when outer table is substituted +# for a row that happens to have a null value for the join attribute. +# + +CREATE TABLE t1 ( + id int NOT NULL PRIMARY KEY, + ct int DEFAULT NULL, + pc int DEFAULT NULL, + INDEX idx_ct (ct), + INDEX idx_pc (pc) +); +INSERT INTO t1 VALUES + (1,NULL,NULL),(2,NULL,NULL),(3,NULL,NULL),(4,NULL,NULL),(5,NULL,NULL); + +CREATE TABLE t2 ( + id int NOT NULL PRIMARY KEY, + sr int NOT NULL, + nm varchar(255) NOT NULL, + INDEX idx_sr (sr) +); +INSERT INTO t2 VALUES + (2441905,4308,'LesAbymes'),(2441906,4308,'Anse-Bertrand'); + +CREATE TABLE t3 ( + id int NOT NULL PRIMARY KEY, + ct int NOT NULL, + ln int NOT NULL, + INDEX idx_ct (ct), + INDEX idx_ln (ln) +); + +CREATE TABLE t4 ( + id int NOT NULL PRIMARY KEY, + nm varchar(255) NOT NULL +); + +INSERT INTO t4 VALUES (4308,'Guadeloupe'),(4309,'Martinique'); + +SELECT t1.* + FROM t1 LEFT JOIN + (t2 LEFT JOIN t3 ON t3.ct=t2.id AND t3.ln='5') ON t1.ct=t2.id + WHERE t1.id='5'; + +SELECT t1.*, t4.nm + FROM t1 LEFT JOIN + (t2 LEFT JOIN t3 ON t3.ct=t2.id AND t3.ln='5') ON t1.ct=t2.id + LEFT JOIN t4 ON t2.sr=t4.id + WHERE t1.id='5'; + +DROP TABLE t1,t2,t3,t4; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index f9d8e11ee96..d65da7f3018 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -1590,8 +1590,13 @@ SELECT a FROM t1 WHERE (SELECT b FROM t2) IS NULL; PREPARE stmt FROM 'SELECT a FROM t1 WHERE (SELECT b FROM t2) IS NULL'; EXECUTE stmt; - DEALLOCATE PREPARE stmt; + +PREPARE stmt FROM 'SELECT a FROM t1 WHERE (SELECT b FROM t2 limit ?) IS NULL'; +SET @arg=1; +EXECUTE stmt USING @arg; +DEALLOCATE PREPARE stmt; + DROP TABLE t1,t2; --echo End of 5.0 tests. diff --git a/mysql-test/t/type_binary.test b/mysql-test/t/type_binary.test index 1639aff4711..91eba9b328e 100644 --- a/mysql-test/t/type_binary.test +++ b/mysql-test/t/type_binary.test @@ -91,4 +91,12 @@ insert into t1 values(NULL, 0x412020); drop table t1; set @@sql_mode= @old_sql_mode; +# +# Bug#14171: Wrong default value for a BINARY field +# +create table t1(f1 int, f2 binary(2) not null, f3 char(2) not null); +insert into t1 set f1=1; +select hex(f2), hex(f3) from t1; +drop table t1; + --echo End of 5.0 tests diff --git a/mysql-test/t/user_var.test b/mysql-test/t/user_var.test index 65ca1b2c1b7..70f57fdf283 100644 --- a/mysql-test/t/user_var.test +++ b/mysql-test/t/user_var.test @@ -210,7 +210,10 @@ create table t1(f1 int, f2 int); insert into t1 values (1,2),(2,3),(3,1); select @var:=f2 from t1 group by f1 order by f2 desc limit 1; select @var; -drop table t1; +create table t2 as select @var:=f2 from t1 group by f1 order by f2 desc limit 1; +select * from t2; +select @var; +drop table t1,t2; # # Bug#19024 - SHOW COUNT(*) WARNINGS not return Errors diff --git a/sql/field.h b/sql/field.h index bf68c37aec3..29a969cad59 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1096,7 +1096,8 @@ public: bool zero_pack() const { return 0; } int reset(void) { - charset()->cset->fill(charset(),ptr,field_length,' '); + charset()->cset->fill(charset(),ptr,field_length, + (has_charset() ? ' ' : 0)); return 0; } int store(const char *to,uint length,CHARSET_INFO *charset); diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index d7942fe0800..3a615d4c10a 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -3080,7 +3080,7 @@ longlong Item_func_isnull::val_int() Handle optimization if the argument can't be null This has to be here because of the test in update_used_tables(). */ - if (!used_tables_cache) + if (!used_tables_cache && !with_subselect) return cached_value; return args[0]->is_null() ? 1: 0; } @@ -3089,7 +3089,7 @@ longlong Item_is_not_null_test::val_int() { DBUG_ASSERT(fixed == 1); DBUG_ENTER("Item_is_not_null_test::val_int"); - if (!used_tables_cache) + if (!used_tables_cache && !with_subselect) { owner->was_null|= (!cached_value); DBUG_PRINT("info", ("cached: %ld", (long) cached_value)); @@ -3116,7 +3116,7 @@ void Item_is_not_null_test::update_used_tables() else { args[0]->update_used_tables(); - if (!(used_tables_cache=args[0]->used_tables())) + if (!(used_tables_cache=args[0]->used_tables()) && !with_subselect) { /* Remember if the value is always NULL or never NULL */ cached_value= (longlong) !args[0]->is_null(); diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 3d02e73f6e4..a51cc0d4b30 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -1102,7 +1102,8 @@ public: else { args[0]->update_used_tables(); - if ((const_item_cache= !(used_tables_cache= args[0]->used_tables()))) + if ((const_item_cache= !(used_tables_cache= args[0]->used_tables())) && + !with_subselect) { /* Remember if the value is always NULL or never NULL */ cached_value= (longlong) args[0]->is_null(); diff --git a/sql/item_func.cc b/sql/item_func.cc index 4cbe76a49f8..37778acd962 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4053,6 +4053,105 @@ void Item_func_set_user_var::make_field(Send_field *tmp_field) Item::make_field(tmp_field); } + +/* + Save the value of a user variable into a field + + SYNOPSIS + save_in_field() + field target field to save the value to + no_conversion flag indicating whether conversions are allowed + + DESCRIPTION + Save the function value into a field and update the user variable + accordingly. If a result field is defined and the target field doesn't + coincide with it then the value from the result field will be used as + the new value of the user variable. + + The reason to have this method rather than simply using the result + field in the val_xxx() methods is that the value from the result field + not always can be used when the result field is defined. + Let's consider the following cases: + 1) when filling a tmp table the result field is defined but the value of it + is undefined because it has to be produced yet. Thus we can't use it. + 2) on execution of an INSERT ... SELECT statement the save_in_field() + function will be called to fill the data in the new record. If the SELECT + part uses a tmp table then the result field is defined and should be + used in order to get the correct result. + + The difference between the SET_USER_VAR function and regular functions + like CONCAT is that the Item_func objects for the regular functions are + replaced by Item_field objects after the values of these functions have + been stored in a tmp table. Yet an object of the Item_field class cannot + be used to update a user variable. + Due to this we have to handle the result field in a special way here and + in the Item_func_set_user_var::send() function. + + RETURN VALUES + FALSE Ok + TRUE Error +*/ + +int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions) +{ + bool use_result_field= (result_field && result_field != field); + int error; + + /* Update the value of the user variable */ + check(use_result_field); + update(); + + if (result_type() == STRING_RESULT || + result_type() == REAL_RESULT && + field->result_type() == STRING_RESULT) + { + String *result; + CHARSET_INFO *cs= collation.collation; + char buff[MAX_FIELD_WIDTH]; // Alloc buffer for small columns + str_value.set_quick(buff, sizeof(buff), cs); + result= entry->val_str(&null_value, &str_value, decimals); + + if (null_value) + { + str_value.set_quick(0, 0, cs); + return set_field_to_null_with_conversions(field, no_conversions); + } + + /* NOTE: If null_value == FALSE, "result" must be not NULL. */ + + field->set_notnull(); + error=field->store(result->ptr(),result->length(),cs); + str_value.set_quick(0, 0, cs); + } + else if (result_type() == REAL_RESULT) + { + double nr= entry->val_real(&null_value); + if (null_value) + return set_field_to_null(field); + field->set_notnull(); + error=field->store(nr); + } + else if (result_type() == DECIMAL_RESULT) + { + my_decimal decimal_value; + my_decimal *value= entry->val_decimal(&null_value, &decimal_value); + if (null_value) + return set_field_to_null(field); + field->set_notnull(); + error=field->store_decimal(value); + } + else + { + longlong nr= entry->val_int(&null_value); + if (null_value) + return set_field_to_null_with_conversions(field, no_conversions); + field->set_notnull(); + error=field->store(nr, unsigned_flag); + } + return error; +} + + String * Item_func_get_user_var::val_str(String *str) { diff --git a/sql/item_func.h b/sql/item_func.h index 6d8e0bec7a6..7810c0ce9a9 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1214,6 +1214,7 @@ public: void print(String *str); void print_as_stmt(String *str); const char *func_name() const { return "set_user_var"; } + int save_in_field(Field *field, bool no_conversions); }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index e21658bc6b8..a6174b80d86 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1478,6 +1478,7 @@ File open_binlog(IO_CACHE *log, const char *log_file_name, /* mysqld.cc */ extern void MYSQLerror(const char*); void refresh_status(THD *thd); +my_bool mysql_rm_tmp_tables(void); /* item_func.cc */ extern bool check_reserved_words(LEX_STRING *name); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 72add4d3aa4..b0f412910c4 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3637,7 +3637,7 @@ we force server id to 2, but this MySQL server will not act as a slave."); error_handler_hook= my_message_sql; start_signal_handler(); // Creates pidfile - if (acl_init(opt_noacl) || + if (mysql_rm_tmp_tables() || acl_init(opt_noacl) || my_tz_init((THD *)0, default_tz_name, opt_bootstrap)) { abort_loop=1; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7614506f77f..3de7d4375f1 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -40,7 +40,6 @@ static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); static void free_cache_entry(TABLE *entry); -static void mysql_rm_tmp_tables(void); static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, @@ -63,7 +62,6 @@ extern "C" byte *table_cache_key(const byte *record,uint *length, bool table_cache_init(void) { - mysql_rm_tmp_tables(); return hash_init(&open_cache, &my_charset_bin, table_cache_size+16, 0, 0,table_cache_key, (hash_free_key) free_cache_entry, 0) != 0; @@ -6072,14 +6070,21 @@ fill_record_n_invoke_before_triggers(THD *thd, Field **ptr, } -static void mysql_rm_tmp_tables(void) +my_bool mysql_rm_tmp_tables(void) { uint i, idx; - char filePath[FN_REFLEN], *tmpdir; + char filePath[FN_REFLEN], *tmpdir, filePathCopy[FN_REFLEN]; MY_DIR *dirp; FILEINFO *file; + TABLE tmp_table; + THD *thd; DBUG_ENTER("mysql_rm_tmp_tables"); + if (!(thd= new THD)) + DBUG_RETURN(1); + thd->thread_stack= (char*) &thd; + thd->store_globals(); + for (i=0; i<=mysql_tmpdir_list.max; i++) { tmpdir=mysql_tmpdir_list.list[i]; @@ -6098,15 +6103,39 @@ static void mysql_rm_tmp_tables(void) (file->name[1] == '.' && !file->name[2]))) continue; - if (!bcmp(file->name,tmp_file_prefix,tmp_file_prefix_length)) + if (!bcmp(file->name,tmp_file_prefix,tmp_file_prefix_length)) + { + char *ext= fn_ext(file->name); + uint ext_len= strlen(ext); + uint filePath_len= my_snprintf(filePath, sizeof(filePath), + "%s%c%s", FN_LIBCHAR,tmpdir, + file->name); + if (!bcmp(reg_ext, ext, ext_len)) { - sprintf(filePath,"%s%c%s",tmpdir,FN_LIBCHAR,file->name); - VOID(my_delete(filePath,MYF(MY_WME))); + TABLE tmp_table; + if (!openfrm(thd, filePath, "tmp_table", (uint) 0, + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + 0, &tmp_table)) + { + /* We should cut file extention before deleting of table */ + memcpy(filePathCopy, filePath, filePath_len - ext_len); + filePathCopy[filePath_len - ext_len]= 0; + tmp_table.file->delete_table(filePathCopy); + closefrm(&tmp_table); + } } + /* + File can be already deleted by tmp_table.file->delete_table(). + So we hide error messages which happnes during deleting of these + files(MYF(0)). + */ + VOID(my_delete(filePath, MYF(0))); } my_dirend(dirp); } - DBUG_VOID_RETURN; + delete thd; + my_pthread_setspecific_ptr(THR_THD, 0); + DBUG_RETURN(0); } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index ad098a55811..18d30494701 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1934,6 +1934,7 @@ void st_select_lex_unit::set_limit(SELECT_LEX *sl) { ha_rows select_limit_val; + DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); select_limit_val= (ha_rows)(sl->select_limit ? sl->select_limit->val_uint() : HA_POS_ERROR); offset_limit_cnt= (ha_rows)(sl->offset_limit ? sl->offset_limit->val_uint() : diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 3c1c2fe4778..c8830278e42 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2333,8 +2333,18 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds, substitution of a const table the key value happens to be null then we can state that there are no matches for this equi-join. */ - if ((keyuse= s->keyuse) && *s->on_expr_ref) + if ((keyuse= s->keyuse) && *s->on_expr_ref && !s->embedding_map) { + /* + When performing an outer join operation if there are no matching rows + for the single row of the outer table all the inner tables are to be + null complemented and thus considered as constant tables. + Here we apply this consideration to the case of outer join operations + with a single inner table only because the case with nested tables + would require a more thorough analysis. + TODO. Apply single row substitution to null complemented inner tables + for nested outer join operations. + */ while (keyuse->table == table) { if (!(keyuse->val->used_tables() & ~join->const_table_map) &&