From f63608ea97133b12a1a5b78326e5eaddefb4d9b2 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Mon, 3 May 2010 18:16:51 +0300 Subject: [PATCH 1/8] Bug #53371: COM_FIELD_LIST can be abused to bypass table level grants. The server was not checking the supplied to COM_FIELD_LIST table name for validity and compliance to acceptable table names standards. Fixed by checking the table name for compliance similar to how it's normally checked by the parser and returning an error message if it's not compliant. --- sql/sql_parse.cc | 7 +++++++ tests/mysql_client_test.c | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2b43d95dd7c..807d6c09a46 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2042,6 +2042,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } thd->convert_string(&conv_name, system_charset_info, packet, arg_length, thd->charset()); + if (check_table_name (conv_name.str, conv_name.length)) + { + /* this is OK due to convert_string() null-terminating the string */ + my_error(ER_WRONG_TABLE_NAME, MYF(0), conv_name.str); + break; + } + table_list.alias= table_list.table_name= conv_name.str; packet= pend+1; diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 63137bdba93..5b26b96707b 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -16679,6 +16679,47 @@ static void test_bug45010() } +static void test_bug53371() +{ + int rc; + MYSQL_RES *result; + + myheader("test_bug53371"); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + myquery(rc); + rc= mysql_query(mysql, "DROP DATABASE IF EXISTS bug53371"); + myquery(rc); + rc= mysql_query(mysql, "DROP USER 'testbug'@localhost"); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a INT)"); + myquery(rc); + rc= mysql_query(mysql, "CREATE DATABASE bug53371"); + myquery(rc); + rc= mysql_query(mysql, "GRANT SELECT ON bug53371.* to 'testbug'@localhost"); + myquery(rc); + + rc= mysql_change_user(mysql, "testbug", NULL, "bug53371"); + myquery(rc); + + rc= mysql_query(mysql, "SHOW COLUMNS FROM client_test_db.t1"); + DIE_UNLESS(rc); + DIE_UNLESS(mysql_errno(mysql) == 1142); + + result= mysql_list_fields(mysql, "../client_test_db/t1", NULL); + DIE_IF(result); + + rc= mysql_change_user(mysql, opt_user, opt_password, current_db); + myquery(rc); + rc= mysql_query(mysql, "DROP TABLE t1"); + myquery(rc); + rc= mysql_query(mysql, "DROP DATABASE bug53371"); + myquery(rc); + rc= mysql_query(mysql, "DROP USER 'testbug'@localhost"); + myquery(rc); +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -16982,6 +17023,7 @@ static struct my_tests_st my_tests[]= { { "test_bug41078", test_bug41078 }, { "test_bug20023", test_bug20023 }, { "test_bug45010", test_bug45010 }, + { "test_bug53371", test_bug53371 }, { 0, 0 } }; From 819caf8eb86ebd7f2b56506eb6264bc2442898a8 Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Tue, 4 May 2010 10:17:20 +0100 Subject: [PATCH 2/8] BUG#43406 CHANGE MASTER TO should log previous state in error log When issuing a 'CHANGE MASTER TO' statement, key elements of the previous state, namely the host, port, the master_log_file and the master_log_pos are dumped into the error log. --- sql/sql_repl.cc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 44215d90634..c220f609c09 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1134,6 +1134,10 @@ bool change_master(THD* thd, Master_info* mi) int thread_mask; const char* errmsg= 0; bool need_relay_log_purge= 1; + char saved_host[HOSTNAME_LENGTH + 1]; + uint saved_port; + char saved_log_name[FN_REFLEN]; + my_off_t saved_log_pos; DBUG_ENTER("change_master"); lock_slave_threads(mi); @@ -1162,6 +1166,17 @@ bool change_master(THD* thd, Master_info* mi) could possibly modify the data structures from running */ + /* + Before processing the command, save the previous state. + */ + char *pos; + pos= strmake(saved_host, mi->host, HOSTNAME_LENGTH); + pos= '\0'; + saved_port= mi->port; + pos= strmake(saved_log_name, mi->master_log_name, FN_REFLEN - 1); + pos= '\0'; + saved_log_pos= mi->master_log_pos; + /* If the user specified host or port without binlog or position, reset binlog's name to FIRST and position to 4. @@ -1325,6 +1340,15 @@ bool change_master(THD* thd, Master_info* mi) /* Clear the errors, for a clean start */ mi->rli.clear_error(); mi->rli.clear_until_condition(); + + sql_print_information("'CHANGE MASTER TO executed'. " + "Previous state master_host='%s', master_port='%u', master_log_file='%s', " + "master_log_pos='%ld'. " + "New state master_host='%s', master_port='%u', master_log_file='%s', " + "master_log_pos='%ld'.", saved_host, saved_port, saved_log_name, + (ulong) saved_log_pos, mi->host, mi->port, mi->master_log_name, + (ulong) mi->master_log_pos); + /* If we don't write new coordinates to disk now, then old will remain in relay-log.info until START SLAVE is issued; but if mysqld is shutdown From 3c30921662f9b1ceb8b0e72d18a43820dc732f6e Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Tue, 4 May 2010 10:41:28 +0100 Subject: [PATCH 3/8] BUG#43407 SET GLOBAL SQL_SLAVE_SKIP_COUNTER should log previous state in error log When issuing a 'SET GLOBAL SQL_SLAVE_SKIP_COUNTER' statement, the previous position along with the new position is dumped into the error log. Namely, the following information is printed out: skip_counter, group_relay_log_name and group_relay_log_pos. --- sql/slave.cc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sql/slave.cc b/sql/slave.cc index e8405ffcd37..2e4642d179e 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2883,6 +2883,11 @@ pthread_handler_t handle_slave_sql(void *arg) { THD *thd; /* needs to be first for thread_stack */ char llbuff[22],llbuff1[22]; + char saved_log_name[FN_REFLEN]; + char saved_master_log_name[FN_REFLEN]; + my_off_t saved_log_pos; + my_off_t saved_master_log_pos; + my_off_t saved_skip= 0; Relay_log_info* rli = &((Master_info*)arg)->rli; const char *errmsg; @@ -3028,6 +3033,17 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, do not want to wait for next event in this case. */ pthread_mutex_lock(&rli->data_lock); + if (rli->slave_skip_counter) + { + char *pos; + pos= strmake(saved_log_name, rli->group_relay_log_name, FN_REFLEN - 1); + pos= '\0'; + pos= strmake(saved_master_log_name, rli->group_master_log_name, FN_REFLEN - 1); + pos= '\0'; + saved_log_pos= rli->group_relay_log_pos; + saved_master_log_pos= rli->group_master_log_pos; + saved_skip= rli->slave_skip_counter; + } if (rli->until_condition != Relay_log_info::UNTIL_NONE && rli->is_until_satisfied(thd, NULL)) { @@ -3046,6 +3062,21 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, thd_proc_info(thd, "Reading event from the relay log"); DBUG_ASSERT(rli->sql_thd == thd); THD_CHECK_SENTRY(thd); + + if (saved_skip && rli->slave_skip_counter == 0) + { + sql_print_information("'SQL_SLAVE_SKIP_COUNTER=%ld' executed at " + "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', " + "master_log_pos='%ld' and new position at " + "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', " + "master_log_pos='%ld' ", + (ulong) saved_skip, saved_log_name, (ulong) saved_log_pos, + saved_master_log_name, (ulong) saved_master_log_pos, + rli->group_relay_log_name, (ulong) rli->group_relay_log_pos, + rli->group_master_log_name, (ulong) rli->group_master_log_pos); + saved_skip= 0; + } + if (exec_relay_log_event(thd,rli)) { DBUG_PRINT("info", ("exec_relay_log_event() failed")); From fb47b3038425bf9bcf51fda9b29dff7f8edb794c Mon Sep 17 00:00:00 2001 From: Omer BarNir Date: Tue, 4 May 2010 14:24:36 -0700 Subject: [PATCH 4/8] Updates to README file of the 'engines' test suites --- mysql-test/suite/engines/README | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mysql-test/suite/engines/README b/mysql-test/suite/engines/README index 3ff6fccc689..0f89d5af67f 100644 --- a/mysql-test/suite/engines/README +++ b/mysql-test/suite/engines/README @@ -50,22 +50,22 @@ The following suites are included: Known Issues ------------ -1) The 'funcs' and 'iuds' suites currently runs only against MySQL 5.1 server. - Running them against MySQL 5.5 will generate errors. - -2) The folowing tests in the 'iuds' suite: +1) The folowing tests in the 'iuds' suite: - delete_decimal - insert_decimal - update_decimal will return a 'Warning 1264 - Out of range value...' warning if run in a 32-bit environment. Add the '--force' option to prevent the test run from aborting. -3) The following tests in the 'funcs' suite will fail when run against the innodb_plugin: +2) The following tests in the 'funcs' suite will fail when run against the innodb_plugin: - crash_manycolumns_string (bug 50495) - ix_unique_lob (bug 52056, masked by an 'Out of memory error' on some 32-bit platforms) - ix_unique_string_length (bug 52056, masked by an 'Out of memory error' on some 32-bit platforms) Add the '--force' option to prevent the test run from aborting. -4) Some of the rpl_xxx tests in the 'funcs' suite require a secific binlog_forat setting and will be +3) Some of the rpl_xxx tests in the 'funcs' suite require a secific binlog_forat setting and will be skipped otherwise. +4) Some of the rpl_xxx tests in the 'funcs' suite will report a 'Statement unsafe for replication' warning + when run againsr a server configured to use statement based replication. + From addd0a3e67164037149140b71c027272ecbaee49 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Wed, 5 May 2010 11:54:52 +0300 Subject: [PATCH 5/8] On behalf of Kristofer : Bug#53417 my_getwd() makes assumptions on the buffer sizes which not always hold true The mysys library contains many functions for rewriting file paths. Most of these functions makes implicit assumptions on the buffer sizes they write to. If a path is put in my_realpath() it will propagate to my_getwd() which assumes that the buffer holding the path name is greater than 2. This is not true in cases. In the special case where a VARBIN_ITEM is passed as argument to the LOAD_FILE function this can lead to a crash. This patch fixes the issue by introduce more safe guards agaist buffer overruns. --- mysys/mf_loadpath.c | 11 ++++++----- mysys/my_getwd.c | 7 +++++++ sql/item.cc | 12 ++++++++++++ sql/item.h | 4 +++- sql/mysqld.cc | 5 +++++ 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/mysys/mf_loadpath.c b/mysys/mf_loadpath.c index 48a69207839..1df613a1733 100644 --- a/mysys/mf_loadpath.c +++ b/mysys/mf_loadpath.c @@ -34,7 +34,7 @@ char * my_load_path(char * to, const char *path, if ((path[0] == FN_HOMELIB && path[1] == FN_LIBCHAR) || test_if_hard_path(path)) - VOID(strmov(buff,path)); + VOID(strnmov(buff, path, FN_REFLEN)); else if ((is_cur=(path[0] == FN_CURLIB && path[1] == FN_LIBCHAR)) || (is_prefix(path,FN_PARENTDIR)) || ! own_path_prefix) @@ -42,13 +42,14 @@ char * my_load_path(char * to, const char *path, if (is_cur) is_cur=2; /* Remove current dir */ if (! my_getwd(buff,(uint) (FN_REFLEN-strlen(path)+is_cur),MYF(0))) - VOID(strcat(buff,path+is_cur)); + VOID(strncat(buff, path+is_cur, FN_REFLEN)); else - VOID(strmov(buff,path)); /* Return org file name */ + VOID(strnmov(buff, path, FN_REFLEN)); /* Return org file name */ } else - VOID(strxmov(buff,own_path_prefix,path,NullS)); - strmov(to,buff); + VOID(strxnmov(buff, FN_REFLEN, own_path_prefix,path, NullS)); + strnmov(to, buff, FN_REFLEN); + to[FN_REFLEN-1]= '\0'; DBUG_PRINT("exit",("to: %s",to)); DBUG_RETURN(to); } /* my_load_path */ diff --git a/mysys/my_getwd.c b/mysys/my_getwd.c index e0c5b94b53e..e6b867e2753 100644 --- a/mysys/my_getwd.c +++ b/mysys/my_getwd.c @@ -50,11 +50,16 @@ int my_getwd(char * buf, size_t size, myf MyFlags) DBUG_PRINT("my",("buf: 0x%lx size: %u MyFlags %d", (long) buf, (uint) size, MyFlags)); + if (size < 1) + return(-1); + if (curr_dir[0]) /* Current pos is saved here */ VOID(strmake(buf,&curr_dir[0],size-1)); else { #if defined(HAVE_GETCWD) + if (size < 2) + return(-1); if (!getcwd(buf,(uint) (size-2)) && MyFlags & MY_WME) { my_errno=errno; @@ -68,6 +73,8 @@ int my_getwd(char * buf, size_t size, myf MyFlags) strmake(buf,pathname,size-1); } #elif defined(VMS) + if (size < 2) + return(-1); if (!getcwd(buf,size-2,1) && MyFlags & MY_WME) { my_errno=errno; diff --git a/sql/item.cc b/sql/item.cc index 3407d2fecd4..2175a579f4a 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5366,13 +5366,25 @@ inline uint char_val(char X) X-'a'+10); } +Item_hex_string::Item_hex_string() +{ + hex_string_init("", 0); +} Item_hex_string::Item_hex_string(const char *str, uint str_length) +{ + hex_string_init(str, str_length); +} + +void Item_hex_string::hex_string_init(const char *str, uint str_length) { max_length=(str_length+1)/2; char *ptr=(char*) sql_alloc(max_length+1); if (!ptr) + { + str_value.set("", 0, &my_charset_bin); return; + } str_value.set(ptr,max_length,&my_charset_bin); char *end=ptr+max_length; if (max_length*2 != str_length) diff --git a/sql/item.h b/sql/item.h index d2303853743..174995b43e6 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2123,7 +2123,7 @@ public: class Item_hex_string: public Item_basic_constant { public: - Item_hex_string() {} + Item_hex_string(); Item_hex_string(const char *str,uint str_length); enum Type type() const { return VARBIN_ITEM; } double val_real() @@ -2143,6 +2143,8 @@ public: bool eq(const Item *item, bool binary_cmp) const; virtual Item *safe_charset_converter(CHARSET_INFO *tocs); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} +private: + void hex_string_init(const char *str, uint str_length); }; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 24614737a59..3664f46995f 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -8796,6 +8796,9 @@ bool is_secure_file_path(char *path) if (!opt_secure_file_priv) return TRUE; + if (strlen(path) >= FN_REFLEN) + return FALSE; + if (my_realpath(buff1, path, 0)) { /* @@ -8882,6 +8885,8 @@ static int fix_paths(void) } else { + if (strlen(opt_secure_file_priv) >= FN_REFLEN) + opt_secure_file_priv[FN_REFLEN-1]= '\0'; if (my_realpath(buff, opt_secure_file_priv, 0)) { sql_print_warning("Failed to normalize the argument for --secure-file-priv."); From 1eada91053287af3d46da93b88d5feb30ed4ba27 Mon Sep 17 00:00:00 2001 From: Martin Hansson Date: Thu, 6 May 2010 10:45:00 +0200 Subject: [PATCH 6/8] Bug#52357: Assertion failed: join->best_read in greedy_search optimizer_search_depth=0 The algorithm inside restore_prev_nj_state failed to properly update the counters within the NESTED_JOIN tree. The counter was decremented each time a table in the node was removed from the QEP, the correct thing to do being only to decrement it when the last table in the child node was removed from the plan. This lead to node counters getting negative values and the plan thus appeared impossible. An assertion caught this. Fixed by not recursing up the tree unless the last table in the join nest node is removed from the plan --- mysql-test/r/join_outer.result | 30 ++++++++++++++++++ mysql-test/t/join_outer.test | 24 +++++++++++++++ sql/sql_select.cc | 56 +++++++++++++++++++++++++++------- sql/table.h | 15 ++++++++- 4 files changed, 113 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/join_outer.result b/mysql-test/r/join_outer.result index 1366a8fe97a..31539b68069 100644 --- a/mysql-test/r/join_outer.result +++ b/mysql-test/r/join_outer.result @@ -1254,3 +1254,33 @@ SELECT * FROM t1 LEFT JOIN t2 ON e<>0 WHERE c=1 AND d<=>NULL; c e d 1 0 NULL DROP TABLE t1,t2; +# +# Bug#52357: Assertion failed: join->best_read in greedy_search +# optimizer_search_depth=0 +# +CREATE TABLE t1( a INT ); +INSERT INTO t1 VALUES (1),(2); +SET optimizer_search_depth = 0; +# Should not core dump on query preparation +EXPLAIN +SELECT 1 +FROM t1 tt3 LEFT OUTER JOIN t1 tt4 ON 1 +LEFT OUTER JOIN t1 tt5 ON 1 +LEFT OUTER JOIN t1 tt6 ON 1 +LEFT OUTER JOIN t1 tt7 ON 1 +LEFT OUTER JOIN t1 tt8 ON 1 +RIGHT OUTER JOIN t1 tt2 ON 1 +RIGHT OUTER JOIN t1 tt1 ON 1 +STRAIGHT_JOIN t1 tt9 ON 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE tt1 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt2 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt3 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt4 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt5 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt6 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt7 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt8 ALL NULL NULL NULL NULL 2 +1 SIMPLE tt9 ALL NULL NULL NULL NULL 2 +SET optimizer_search_depth = DEFAULT; +DROP TABLE t1; diff --git a/mysql-test/t/join_outer.test b/mysql-test/t/join_outer.test index 1a59dbf8fc2..0d75ac97266 100644 --- a/mysql-test/t/join_outer.test +++ b/mysql-test/t/join_outer.test @@ -861,3 +861,27 @@ SELECT * FROM t1 LEFT JOIN t2 ON e<>0 WHERE c=1 AND d<=>NULL; DROP TABLE t1,t2; + +--echo # +--echo # Bug#52357: Assertion failed: join->best_read in greedy_search +--echo # optimizer_search_depth=0 +--echo # +CREATE TABLE t1( a INT ); + +INSERT INTO t1 VALUES (1),(2); +SET optimizer_search_depth = 0; + +--echo # Should not core dump on query preparation +EXPLAIN +SELECT 1 +FROM t1 tt3 LEFT OUTER JOIN t1 tt4 ON 1 + LEFT OUTER JOIN t1 tt5 ON 1 + LEFT OUTER JOIN t1 tt6 ON 1 + LEFT OUTER JOIN t1 tt7 ON 1 + LEFT OUTER JOIN t1 tt8 ON 1 + RIGHT OUTER JOIN t1 tt2 ON 1 + RIGHT OUTER JOIN t1 tt1 ON 1 + STRAIGHT_JOIN t1 tt9 ON 1; + +SET optimizer_search_depth = DEFAULT; +DROP TABLE t1; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f0177893840..53a6b699022 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8706,6 +8706,39 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab) /* Nested joins perspective: Remove the last table from the join order + The algorithm is the reciprocal of check_interleaving_with_nj(), hence + parent join nest nodes are updated only when the last table in its child + node is removed. The ASCII graphic below will clarify. + + %A table nesting such as t1 x [ ( t2 x t3 ) x ( t4 x t5 ) ] is + represented by the below join nest tree. + + @verbatim + NJ1 + _/ / \ + _/ / NJ2 + _/ / / \ + / / / \ + t1 x [ (t2 x t3) x (t4 x t5) ] + @endverbatim + + At the point in time when check_interleaving_with_nj() adds the table t5 to + the query execution plan, QEP, it also directs the node named NJ2 to mark + the table as covered. NJ2 does so by incrementing its @c counter + member. Since all of NJ2's tables are now covered by the QEP, the algorithm + proceeds up the tree to NJ1, incrementing its counter as well. All join + nests are now completely covered by the QEP. + + restore_prev_nj_state() does the above in reverse. As seen above, the node + NJ1 contains the nodes t2, t3, and NJ2. Its counter being equal to 3 means + that the plan covers t2, t3, and NJ2, @e and that the sub-plan (t4 x t5) + completely covers NJ2. The removal of t5 from the partial plan will first + decrement NJ2's counter to 1. It will then detect that NJ2 went from being + completely to partially covered, and hence the algorithm must continue + upwards to NJ1 and decrement its counter to 2. %A subsequent removal of t4 + will however not influence NJ1 since it did not un-cover the last table in + NJ2. + SYNOPSIS restore_prev_nj_state() last join table to remove, it is assumed to be the last in current @@ -8722,19 +8755,20 @@ static void restore_prev_nj_state(JOIN_TAB *last) { TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding; JOIN *join= last->join; - while (last_emb) + for (;last_emb != NULL; last_emb= last_emb->embedding) { - if (!(--last_emb->nested_join->counter)) - join->cur_embedding_map&= ~last_emb->nested_join->nj_map; - else if (last_emb->nested_join->join_list.elements-1 == - last_emb->nested_join->counter) - { - join->cur_embedding_map|= last_emb->nested_join->nj_map; + NESTED_JOIN *nest= last_emb->nested_join; + DBUG_ASSERT(nest->counter > 0); + + bool was_fully_covered= nest->is_fully_covered(); + + if (--nest->counter == 0) + join->cur_embedding_map&= ~nest->nj_map; + + if (!was_fully_covered) break; - } - else - break; - last_emb= last_emb->embedding; + + join->cur_embedding_map|= nest->nj_map; } } diff --git a/sql/table.h b/sql/table.h index db996d45320..f162c2ed8ca 100644 --- a/sql/table.h +++ b/sql/table.h @@ -910,7 +910,11 @@ typedef struct st_nested_join List join_list; /* list of elements in the nested join */ table_map used_tables; /* bitmap of tables in the nested join */ table_map not_null_tables; /* tables that rejects nulls */ - struct st_join_table *first_nested;/* the first nested table in the plan */ + /** + Used for pointing out the first table in the plan being covered by this + join nest. It is used exclusively within make_outerjoin_info(). + */ + struct st_join_table *first_nested; /* Used to count tables in the nested join in 2 isolated places: 1. In make_outerjoin_info(). @@ -920,6 +924,15 @@ typedef struct st_nested_join */ uint counter; nested_join_map nj_map; /* Bit used to identify this nested join*/ + /** + True if this join nest node is completely covered by the query execution + plan. This means two things. + + 1. All tables on its @c join_list are covered by the plan. + + 2. All child join nest nodes are fully covered. + */ + bool is_fully_covered() const { return join_list.elements == counter; } } NESTED_JOIN; From bd2a517b2381465eb2cf366fb5d4ad08d6770708 Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Fri, 7 May 2010 00:41:37 +0400 Subject: [PATCH 7/8] Bug #53088: mysqldump with -T & --default-character-set set truncates text/blob to 766 chars mysqldump and SELECT ... INTO OUTFILE truncated long BLOB/TEXT values to size of 766 bytes (MAX_FIELD_WIDTH or 255 * 3 + 1). The select_export::send_data method has been modified to reallocate a conversion buffer for long field data. mysql-test/r/mysqldump.result: Test case for bug #53088. mysql-test/r/outfile_loaddata.result: Test case for bug #53088. mysql-test/t/mysqldump.test: Test case for bug #53088. mysql-test/t/outfile_loaddata.test: Test case for bug #53088. sql/sql_class.cc: Bug #53088: mysqldump with -T & --default-character-set set truncates text/blob to 766 chars The select_export::send_data method has been modified to reallocate a conversion buffer for long field data. --- mysql-test/r/mysqldump.result | 15 ++++++++++++ mysql-test/r/outfile_loaddata.result | 20 ++++++++++++++++ mysql-test/t/mysqldump.test | 29 ++++++++++++++++++++++++ mysql-test/t/outfile_loaddata.test | 34 ++++++++++++++++++++++++++++ sql/sql_class.cc | 25 ++++++++++++++++++-- 5 files changed, 121 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/mysqldump.result b/mysql-test/r/mysqldump.result index 8162e1aca05..d26eaac7a93 100644 --- a/mysql-test/r/mysqldump.result +++ b/mysql-test/r/mysqldump.result @@ -4561,5 +4561,20 @@ a b c SET NAMES default; DROP TABLE t1, t2; # +# Bug #53088: mysqldump with -T & --default-character-set set +# truncates text/blob to 766 chars +# +# Also see outfile_loaddata.test +# +CREATE TABLE t1 (a BLOB) CHARSET latin1; +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (REPEAT('.', 800)); +LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/t1.txt' INTO TABLE t2 CHARACTER SET latin1; +# should be 800 +SELECT LENGTH(a) FROM t2; +LENGTH(a) +800 +DROP TABLE t1, t2; +# # End of 5.1 tests # diff --git a/mysql-test/r/outfile_loaddata.result b/mysql-test/r/outfile_loaddata.result index 453e3adb54c..36a72fd84ce 100644 --- a/mysql-test/r/outfile_loaddata.result +++ b/mysql-test/r/outfile_loaddata.result @@ -239,4 +239,24 @@ a b c 2 NULL NULL SET NAMES default; DROP TABLE t1, t2; +# +# Bug #53088: mysqldump with -T & --default-character-set set +# truncates text/blob to 766 chars +# +# Also see mysqldump.test +# +CREATE TABLE t1 (a BLOB) CHARSET latin1; +CREATE TABLE t2 LIKE t1; +INSERT INTO t1 VALUES (REPEAT('.', 800)); +SELECT * INTO OUTFILE 'MYSQLTEST_VARDIR/tmp/bug53088.txt' CHARACTER SET latin1 FROM t1; +# should be greater than 800 +SELECT LENGTH(LOAD_FILE('MYSQLTEST_VARDIR/tmp/bug53088.txt')); +LENGTH(LOAD_FILE('MYSQLTEST_VARDIR/tmp/bug53088.txt')) +801 +LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/bug53088.txt' INTO TABLE t2; +# should be 800 +SELECT LENGTH(a) FROM t2; +LENGTH(a) +800 +DROP TABLE t1, t2; # End of 5.1 tests. diff --git a/mysql-test/t/mysqldump.test b/mysql-test/t/mysqldump.test index 07ab9cecd28..0bf9916dcd5 100644 --- a/mysql-test/t/mysqldump.test +++ b/mysql-test/t/mysqldump.test @@ -2131,6 +2131,35 @@ SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY a, b, c; SET NAMES default; +DROP TABLE t1, t2; +########################################################################### + +--echo # +--echo # Bug #53088: mysqldump with -T & --default-character-set set +--echo # truncates text/blob to 766 chars +--echo # +--echo # Also see outfile_loaddata.test +--echo # + +CREATE TABLE t1 (a BLOB) CHARSET latin1; +CREATE TABLE t2 LIKE t1; + +let $table= t1; +let $dir= $MYSQLTEST_VARDIR/tmp; +let $file= $dir/$table.txt; +let $length= 800; + +--eval INSERT INTO t1 VALUES (REPEAT('.', $length)) + +--exec $MYSQL_DUMP --character-sets-dir=$CHARSETSDIR --default-character-set=latin1 --tab=$dir/ test $table +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR + +--eval LOAD DATA INFILE '$file' INTO TABLE t2 CHARACTER SET latin1 +--remove_file $file + +--echo # should be $length +SELECT LENGTH(a) FROM t2; + DROP TABLE t1, t2; ########################################################################### diff --git a/mysql-test/t/outfile_loaddata.test b/mysql-test/t/outfile_loaddata.test index 3f62acbd214..26760f9a1b2 100644 --- a/mysql-test/t/outfile_loaddata.test +++ b/mysql-test/t/outfile_loaddata.test @@ -251,6 +251,40 @@ SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY a, b, c; SET NAMES default; DROP TABLE t1, t2; +########################################################################### + +--echo # +--echo # Bug #53088: mysqldump with -T & --default-character-set set +--echo # truncates text/blob to 766 chars +--echo # +--echo # Also see mysqldump.test +--echo # + +CREATE TABLE t1 (a BLOB) CHARSET latin1; +CREATE TABLE t2 LIKE t1; + +let $file= '$MYSQLTEST_VARDIR/tmp/bug53088.txt'; +let $length= 800; + +--eval INSERT INTO t1 VALUES (REPEAT('.', $length)) + +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--eval SELECT * INTO OUTFILE $file CHARACTER SET latin1 FROM t1 + +--echo # should be greater than $length +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--eval SELECT LENGTH(LOAD_FILE($file)) + +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--eval LOAD DATA INFILE $file INTO TABLE t2 + +--remove_file $MYSQLTEST_VARDIR/tmp/bug53088.txt + +--echo # should be $length +SELECT LENGTH(a) FROM t2; + +DROP TABLE t1, t2; + ########################################################################### --echo # End of 5.1 tests. diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 2633f03f2d6..b639e590bbc 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1998,9 +1998,21 @@ bool select_export::send_data(List &items) const char *from_end_pos; const char *error_pos; uint32 bytes; - bytes= well_formed_copy_nchars(write_cs, cvt_buff, sizeof(cvt_buff), + uint64 estimated_bytes= + ((uint64) res->length() / res->charset()->mbminlen + 1) * + write_cs->mbmaxlen + 1; + set_if_smaller(estimated_bytes, UINT_MAX32); + if (cvt_str.realloc((uint32) estimated_bytes)) + { + my_error(ER_OUTOFMEMORY, MYF(0), (uint32) estimated_bytes); + goto err; + } + + bytes= well_formed_copy_nchars(write_cs, (char *) cvt_str.ptr(), + cvt_str.alloced_length(), res->charset(), res->ptr(), res->length(), - sizeof(cvt_buff), + UINT_MAX32, // copy all input chars, + // i.e. ignore nchars parameter &well_formed_error_pos, &cannot_convert_error_pos, &from_end_pos); @@ -2018,6 +2030,15 @@ bool select_export::send_data(List &items) "string", printable_buff, item->name, row_count); } + else if (from_end_pos < res->ptr() + res->length()) + { + /* + result is longer than UINT_MAX32 and doesn't fit into String + */ + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_DATA_TRUNCATED, ER(WARN_DATA_TRUNCATED), + item->full_name(), row_count); + } cvt_str.length(bytes); res= &cvt_str; } From 278ae9a8d5c4598854419ccc0974f57b7f2b4ddc Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Fri, 7 May 2010 10:38:42 +0500 Subject: [PATCH 8/8] Bug#53334 Incorrect result for InnoDB in LEFT JOIN with impossible condition The fix actually reverts the change introduced by the patch for bug 51494. The fact is that patches for bugs 52177&48419 fix bugs 51194&50575 as well. mysql-test/r/innodb_mysql.result: test case mysql-test/t/innodb_mysql.test: test case sql/sql_select.cc: reverted wrong fix for bug 51494 --- mysql-test/r/innodb_mysql.result | 30 ++++++++++++++++++++++++++++++ mysql-test/t/innodb_mysql.test | 30 ++++++++++++++++++++++++++++++ sql/sql_select.cc | 3 +-- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index 2bf1ef8fbf0..8257b291cf1 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -2378,4 +2378,34 @@ SELECT SECOND(c)-@bug47453 FROM t1 JOIN t2 ON d=a; SECOND(c)-@bug47453 0 DROP TABLE t1, t2; +# +# Bug #53334: wrong result for outer join with impossible ON condition +# (see the same test case for MyISAM in join.test) +# +CREATE TABLE t1 (id INT PRIMARY KEY); +CREATE TABLE t2 (id INT); +INSERT INTO t1 VALUES (75); +INSERT INTO t1 VALUES (79); +INSERT INTO t1 VALUES (78); +INSERT INTO t1 VALUES (77); +REPLACE INTO t1 VALUES (76); +REPLACE INTO t1 VALUES (76); +INSERT INTO t1 VALUES (104); +INSERT INTO t1 VALUES (103); +INSERT INTO t1 VALUES (102); +INSERT INTO t1 VALUES (101); +INSERT INTO t1 VALUES (105); +INSERT INTO t1 VALUES (106); +INSERT INTO t1 VALUES (107); +INSERT INTO t2 VALUES (107),(75),(1000); +SELECT t1.id,t2.id FROM t2 LEFT JOIN t1 ON t1.id>=74 AND t1.id<=0 +WHERE t2.id=75 AND t1.id IS NULL; +id id +NULL 75 +EXPLAIN SELECT t1.id,t2.id FROM t2 LEFT JOIN t1 ON t1.id>=74 AND t1.id<=0 +WHERE t2.id=75 AND t1.id IS NULL; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 const PRIMARY NULL NULL NULL 1 Impossible ON condition +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 Using where +DROP TABLE t1,t2; End of 5.1 tests diff --git a/mysql-test/t/innodb_mysql.test b/mysql-test/t/innodb_mysql.test index 9564d3b41fb..f179f3960a9 100644 --- a/mysql-test/t/innodb_mysql.test +++ b/mysql-test/t/innodb_mysql.test @@ -618,5 +618,35 @@ SELECT SECOND(c)-@bug47453 FROM t1 JOIN t2 ON d=a; DROP TABLE t1, t2; +--echo # +--echo # Bug #53334: wrong result for outer join with impossible ON condition +--echo # (see the same test case for MyISAM in join.test) +--echo # + +CREATE TABLE t1 (id INT PRIMARY KEY); +CREATE TABLE t2 (id INT); + +INSERT INTO t1 VALUES (75); +INSERT INTO t1 VALUES (79); +INSERT INTO t1 VALUES (78); +INSERT INTO t1 VALUES (77); +REPLACE INTO t1 VALUES (76); +REPLACE INTO t1 VALUES (76); +INSERT INTO t1 VALUES (104); +INSERT INTO t1 VALUES (103); +INSERT INTO t1 VALUES (102); +INSERT INTO t1 VALUES (101); +INSERT INTO t1 VALUES (105); +INSERT INTO t1 VALUES (106); +INSERT INTO t1 VALUES (107); + +INSERT INTO t2 VALUES (107),(75),(1000); + +SELECT t1.id,t2.id FROM t2 LEFT JOIN t1 ON t1.id>=74 AND t1.id<=0 + WHERE t2.id=75 AND t1.id IS NULL; +EXPLAIN SELECT t1.id,t2.id FROM t2 LEFT JOIN t1 ON t1.id>=74 AND t1.id<=0 + WHERE t2.id=75 AND t1.id IS NULL; + +DROP TABLE t1,t2; --echo End of 5.1 tests diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 291432c2bb6..5479c1bdb36 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2967,8 +2967,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, s->quick=select->quick; s->needed_reg=select->needed_reg; select->quick=0; - if (records == 0 && s->table->reginfo.impossible_range && - (s->table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) + if (records == 0 && s->table->reginfo.impossible_range) { /* Impossible WHERE or ON expression