From 8b6c2d312b51f10de453aca1ca2164e7432acf3c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 19 Jun 2006 22:11:01 +0500 Subject: [PATCH 01/12] bug #20318 (ctype_ucs2_def test fails with embedded) there was two problems about charsets in embedded server 1. mysys/charset.c - defined there default_charset_info variable is modified by both server and client code (particularly when --default-charset option is handled) In embedded server we get two codelines modifying one variable. I created separate default_client_charset_info for client code 2. mysql->charset and mysql->options.charset initialization isn't properly done for embedded server - necessary calls added include/sql_common.h: client charset info default declared libmysqld/lib_sql.cc: thd_init_client_charset calls added libmysqld/libmysqld.c: check_embedded_connection moved to client.c to avoid code duplication sql-common/client.c: charset initialization moved to mysql_init_character_set to be used in embedded server sql/sql_parse.cc: thread client charset initialization moved to thd_init_client_charset to avoid code duplication --- include/sql_common.h | 1 + libmysqld/lib_sql.cc | 7 ++++ libmysqld/libmysqld.c | 48 ++----------------------- sql-common/client.c | 84 ++++++++++++++++++++++++------------------- sql/sql_parse.cc | 59 ++++++++++++++++-------------- 5 files changed, 91 insertions(+), 108 deletions(-) diff --git a/include/sql_common.h b/include/sql_common.h index c07a4a831bb..9fc8d4f457b 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -22,6 +22,7 @@ extern const char *not_error_sqlstate; extern "C" { #endif +extern CHARSET_INFO *default_client_charset_info; MYSQL_FIELD *unpack_fields(MYSQL_DATA *data,MEM_ROOT *alloc,uint fields, my_bool default_value, uint server_capabilities); void free_rows(MYSQL_DATA *cur); diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index bf8c17a71af..56f4200e695 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -41,6 +41,8 @@ static const char *fake_groups[] = { "server", "embedded", 0 }; int check_user(THD *thd, enum enum_server_command command, const char *passwd, uint passwd_len, const char *db, bool check_count); +void thd_init_client_charset(THD *thd, uint cs_number); + C_MODE_START #include #undef ER @@ -532,10 +534,13 @@ err: return NULL; } + #ifdef NO_EMBEDDED_ACCESS_CHECKS int check_embedded_connection(MYSQL *mysql) { THD *thd= (THD*)mysql->thd; + thd_init_client_charset(thd, mysql->charset->number); + thd->update_charset(); thd->host= (char*)my_localhost; thd->host_or_ip= thd->host; thd->user= my_strdup(mysql->user, MYF(0)); @@ -551,6 +556,8 @@ int check_embedded_connection(MYSQL *mysql) char scramble_buff[SCRAMBLE_LENGTH]; int passwd_len; + thd_init_client_charset(thd, mysql->charset->number); + thd->update_charset(); if (mysql->options.client_ip) { thd->host= my_strdup(mysql->options.client_ip, MYF(0)); diff --git a/libmysqld/libmysqld.c b/libmysqld/libmysqld.c index 6fa41fb3fd0..a2bd4242c3d 100644 --- a/libmysqld/libmysqld.c +++ b/libmysqld/libmysqld.c @@ -85,49 +85,7 @@ static void end_server(MYSQL *mysql) } -static int mysql_init_charset(MYSQL *mysql) -{ - char charset_name_buff[16], *charset_name; - - if ((charset_name=mysql->options.charset_name)) - { - const char *save=charsets_dir; - if (mysql->options.charset_dir) - charsets_dir=mysql->options.charset_dir; - mysql->charset=get_charset_by_name(mysql->options.charset_name, - MYF(MY_WME)); - charsets_dir=save; - } - else if (mysql->server_language) - { - charset_name=charset_name_buff; - sprintf(charset_name,"%d",mysql->server_language); /* In case of errors */ - mysql->charset=get_charset((uint8) mysql->server_language, MYF(MY_WME)); - } - else - mysql->charset=default_charset_info; - - if (!mysql->charset) - { - mysql->net.last_errno=CR_CANT_READ_CHARSET; - strmov(mysql->net.sqlstate, "HY0000"); - if (mysql->options.charset_dir) - sprintf(mysql->net.last_error,ER(mysql->net.last_errno), - charset_name ? charset_name : "unknown", - mysql->options.charset_dir); - else - { - char cs_dir_name[FN_REFLEN]; - get_charsets_dir(cs_dir_name); - sprintf(mysql->net.last_error,ER(mysql->net.last_errno), - charset_name ? charset_name : "unknown", - cs_dir_name); - } - return mysql->net.last_errno; - } - return 0; -} - +int mysql_init_character_set(MYSQL *mysql); MYSQL * STDCALL mysql_real_connect(MYSQL *mysql,const char *host, const char *user, @@ -203,10 +161,10 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, init_embedded_mysql(mysql, client_flag, db_name); - if (check_embedded_connection(mysql)) + if (mysql_init_character_set(mysql)) goto error; - if (mysql_init_charset(mysql)) + if (check_embedded_connection(mysql)) goto error; /* Send client information for access check */ diff --git a/sql-common/client.c b/sql-common/client.c index 3a598832253..ea8baeeffc7 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -133,6 +133,8 @@ static void mysql_close_free(MYSQL *mysql); static int wait_for_data(my_socket fd, uint timeout); #endif +CHARSET_INFO *default_client_charset_info = &my_charset_latin1; + /**************************************************************************** A modified version of connect(). my_connect() allows you to specify @@ -1424,7 +1426,7 @@ mysql_init(MYSQL *mysql) bzero((char*) (mysql),sizeof(*(mysql))); mysql->options.connect_timeout= CONNECT_TIMEOUT; mysql->last_used_con= mysql->next_slave= mysql->master = mysql; - mysql->charset=default_charset_info; + mysql->charset=default_client_charset_info; strmov(mysql->net.sqlstate, not_error_sqlstate); /* By default, we are a replication pivot. The caller must reset it @@ -1537,6 +1539,50 @@ static MYSQL_METHODS client_methods= #endif }; +C_MODE_START +int mysql_init_character_set(MYSQL *mysql) +{ + NET *net= &mysql->net; + /* Set character set */ + if (!mysql->options.charset_name && + !(mysql->options.charset_name= + my_strdup(MYSQL_DEFAULT_CHARSET_NAME,MYF(MY_WME)))) + return 1; + + { + const char *save= charsets_dir; + if (mysql->options.charset_dir) + charsets_dir=mysql->options.charset_dir; + mysql->charset=get_charset_by_csname(mysql->options.charset_name, + MY_CS_PRIMARY, MYF(MY_WME)); + charsets_dir= save; + } + + if (!mysql->charset) + { + net->last_errno=CR_CANT_READ_CHARSET; + strmov(net->sqlstate, unknown_sqlstate); + if (mysql->options.charset_dir) + my_snprintf(net->last_error, sizeof(net->last_error)-1, + ER(net->last_errno), + mysql->options.charset_name, + mysql->options.charset_dir); + else + { + char cs_dir_name[FN_REFLEN]; + get_charsets_dir(cs_dir_name); + my_snprintf(net->last_error, sizeof(net->last_error)-1, + ER(net->last_errno), + mysql->options.charset_name, + cs_dir_name); + } + return 1; + } + return 0; +} +C_MODE_END + + MYSQL * STDCALL CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, const char *passwd, const char *db, @@ -1875,42 +1921,8 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, goto error; } - /* Set character set */ - if (!mysql->options.charset_name && - !(mysql->options.charset_name= - my_strdup(MYSQL_DEFAULT_CHARSET_NAME,MYF(MY_WME)))) + if (mysql_init_character_set(mysql)) goto error; - - { - const char *save= charsets_dir; - if (mysql->options.charset_dir) - charsets_dir=mysql->options.charset_dir; - mysql->charset=get_charset_by_csname(mysql->options.charset_name, - MY_CS_PRIMARY, MYF(MY_WME)); - charsets_dir= save; - } - - if (!mysql->charset) - { - net->last_errno=CR_CANT_READ_CHARSET; - strmov(net->sqlstate, unknown_sqlstate); - if (mysql->options.charset_dir) - my_snprintf(net->last_error, sizeof(net->last_error)-1, - ER(net->last_errno), - mysql->options.charset_name, - mysql->options.charset_dir); - else - { - char cs_dir_name[FN_REFLEN]; - get_charsets_dir(cs_dir_name); - my_snprintf(net->last_error, sizeof(net->last_error)-1, - ER(net->last_errno), - mysql->options.charset_name, - cs_dir_name); - } - goto error; - } - /* Save connection information */ if (!my_multi_malloc(MYF(0), diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 51ef3f31b26..4c0221c9e9c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -667,6 +667,37 @@ static void reset_mqh(THD *thd, LEX_USER *lu, bool get_them= 0) #endif /* NO_EMBEDDED_ACCESS_CHECKS */ } +void thd_init_client_charset(THD *thd, uint cs_number) +{ + /* + Use server character set and collation if + - opt_character_set_client_handshake is not set + - client has not specified a character set + - client character set is the same as the servers + - client character set doesn't exists in server + */ + if (!opt_character_set_client_handshake || + !(thd->variables.character_set_client= get_charset(cs_number, MYF(0))) || + !my_strcasecmp(&my_charset_latin1, + global_system_variables.character_set_client->name, + thd->variables.character_set_client->name)) + { + thd->variables.character_set_client= + global_system_variables.character_set_client; + thd->variables.collation_connection= + global_system_variables.collation_connection; + thd->variables.character_set_results= + global_system_variables.character_set_results; + } + else + { + thd->variables.character_set_results= + thd->variables.collation_connection= + thd->variables.character_set_client; + } +} + + /* Perform handshake, authorize client and update thd ACL variables. SYNOPSIS @@ -809,33 +840,7 @@ static int check_connection(THD *thd) thd->client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; thd->max_client_packet_length= uint4korr(net->read_pos+4); DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8])); - /* - Use server character set and collation if - - opt_character_set_client_handshake is not set - - client has not specified a character set - - client character set is the same as the servers - - client character set doesn't exists in server - */ - if (!opt_character_set_client_handshake || - !(thd->variables.character_set_client= - get_charset((uint) net->read_pos[8], MYF(0))) || - !my_strcasecmp(&my_charset_latin1, - global_system_variables.character_set_client->name, - thd->variables.character_set_client->name)) - { - thd->variables.character_set_client= - global_system_variables.character_set_client; - thd->variables.collation_connection= - global_system_variables.collation_connection; - thd->variables.character_set_results= - global_system_variables.character_set_results; - } - else - { - thd->variables.character_set_results= - thd->variables.collation_connection= - thd->variables.character_set_client; - } + thd_init_client_charset(thd, (uint) net->read_pos[8]); thd->update_charset(); end= (char*) net->read_pos+32; } From 9a4b76ed64c58204868236f092f3f64dcfea96e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 22 Jun 2006 22:11:27 +0500 Subject: [PATCH 02/12] bug #10166 (Signed byte values cause data to be padded) The AsBinary function returns VARCHAR data type with binary collation. It can cause problem for clients that treat that kind of data as different from BLOB type. So now AsBinary returns BLOB. mysql-test/r/gis.result: result fixed mysql-test/t/gis.test: test case added sql/item_geofunc.h: Now we return MYSQL_TYPE_BLOB for asBinary function --- mysql-test/r/gis.result | 10 ++++++++++ mysql-test/t/gis.test | 7 +++++++ sql/item_geofunc.h | 2 ++ 3 files changed, 19 insertions(+) diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result index bf2f3e2bf03..f7066e7edca 100644 --- a/mysql-test/r/gis.result +++ b/mysql-test/r/gis.result @@ -661,3 +661,13 @@ POINT(10 10) select (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))); (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))) POINT(10 10) +create table t1 (g GEOMETRY); +select * from t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def test t1 t1 g g 255 4294967295 0 Y 144 0 63 +g +select asbinary(g) from t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def asbinary(g) 252 8192 0 Y 128 0 63 +asbinary(g) +drop table t1; diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test index 3eb17f3a484..b66b97c2c41 100644 --- a/mysql-test/t/gis.test +++ b/mysql-test/t/gis.test @@ -364,3 +364,10 @@ select (asWKT(geomfromwkb((0x000000000140240000000000004024000000000000)))); select (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))); # End of 4.1 tests + +--enable_metadata +create table t1 (g GEOMETRY); +select * from t1; +select asbinary(g) from t1; +--disable_metadata +drop table t1; diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h index 5f060416ff3..a466b606dc1 100644 --- a/sql/item_geofunc.h +++ b/sql/item_geofunc.h @@ -32,6 +32,7 @@ public: Item_geometry_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} Item_geometry_func(List &list) :Item_str_func(list) {} void fix_length_and_dec(); + enum_field_types field_type() const { return MYSQL_TYPE_GEOMETRY; } }; class Item_func_geometry_from_text: public Item_geometry_func @@ -67,6 +68,7 @@ public: Item_func_as_wkb(Item *a): Item_geometry_func(a) {} const char *func_name() const { return "aswkb"; } String *val_str(String *); + enum_field_types field_type() const { return MYSQL_TYPE_BLOB; } }; class Item_func_geometry_type: public Item_str_func From ab5ebc0fb7ce9e6af5bf3ac25425d518ee1a1050 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 26 Jun 2006 20:57:18 +0200 Subject: [PATCH 03/12] Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. INSERT DELAYED crashed in 5.0 on a table with a varchar that could be NULL and was created pre-5.0 (Bugs 16218 and 13707). INSERT DELAYED corrupted data in 5.0 on a table with varchar fields that was created pre-5.0 (Bugs 17294 and 16611). In case of INSERT DELAYED the open table is copied from the delayed insert thread to be able to create a record for the queue. When copying the fields, a method was used that did convert old varchar to new varchar fields and did not set up some pointers into the record buffer of the table. The field conversion was guilty for the misinterpretation of the record contents by the delayed insert thread. The wrong pointer setup was guilty for the crashes. For Bug 13707 (Server crash with INSERT DELAYED on MyISAM table) I fixed the above mentioned method to set up one of the pointers. For Bug 16218 I set up the other pointers too. But when looking at the corruptions I got aware that converting the field type was totally wrong for INSERT DELAYED. The copied table is used to create a record that is to be sent to the delayed insert thread. Of course it can interpret the record correctly only if all field types are the same in both table objects. So I revoked the fix for Bug 13707 and changed the new_field() method so that it can suppress conversions. No test case as this is a migration problem. One needs to create a table with 4.x and use it with 5.x. I added two test scripts to the bug report. sql/field.cc: Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added parameter 'keep_type' to Field::new_field(). Undid the change from Bug 13707 (Server crash with INSERT DELAYED on MyISAM table). I solved all four bugs in sql/sql_insert.cc by making exact duplicates of the fields. The new_field() method converts certain field types, which is wrong for INSERT DELAYED. sql/field.h: Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added parameter 'keep_type' to Field::new_field(). sql/sql_insert.cc: Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added comments. Made small style fixes. Used the new parameter 'keep_type' of Field::new_field() to avoid field type conversion. The table copy must have exactly the same types of fields as the original table. Otherwise the record contents created by the foreground thread could be misinterpreted by the delayed insert thread. sql/sql_select.cc: Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added parameter 'keep_type' to Field::new_field(). Undid the change from Bug 13707 (Server crash with INSERT DELAYED on MyISAM table). I solved all four bugs in sql/sql_insert.cc by making exact duplicates of the fields. The new_field() method converts certain field types, which is wrong for INSERT DELAYED. sql/sql_trigger.cc: Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added parameter 'keep_type' to Field::new_field(). Undid the change from Bug 13707 (Server crash with INSERT DELAYED on MyISAM table). I solved all four bugs in sql/sql_insert.cc by making exact duplicates of the fields. The new_field() method converts certain field types, which is wrong for INSERT DELAYED. sql/table.cc: Bug#16218 - Crash on insert delayed Bug#17294 - INSERT DELAYED puting an \n before data Bug#16611 - INSERT DELAYED corrupts data Bug#13707 - Server crash with INSERT DELAYED on MyISAM table Combined as Bug#16218. Added parameter 'keep_type' to Field::new_field(). Undid the change from Bug 13707 (Server crash with INSERT DELAYED on MyISAM table). I solved all four bugs in sql/sql_insert.cc by making exact duplicates of the fields. The new_field() method converts certain field types, which is wrong for INSERT DELAYED. --- sql/field.cc | 31 ++++++++---------- sql/field.h | 7 ++-- sql/sql_insert.cc | 79 +++++++++++++++++++++++++++++++++++++++------- sql/sql_select.cc | 5 +-- sql/sql_trigger.cc | 3 +- sql/table.cc | 3 +- 6 files changed, 91 insertions(+), 37 deletions(-) diff --git a/sql/field.cc b/sql/field.cc index bdf84c588e1..ff858813ca6 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1515,7 +1515,8 @@ bool Field::optimize_range(uint idx, uint part) } -Field *Field::new_field(MEM_ROOT *root, struct st_table *new_table) +Field *Field::new_field(MEM_ROOT *root, struct st_table *new_table, + bool keep_type __attribute__((unused))) { Field *tmp; if (!(tmp= (Field*) memdup_root(root,(char*) this,size_of()))) @@ -1540,7 +1541,7 @@ Field *Field::new_key_field(MEM_ROOT *root, struct st_table *new_table, uint new_null_bit) { Field *tmp; - if ((tmp= new_field(root, new_table))) + if ((tmp= new_field(root, new_table, table == new_table))) { tmp->ptr= new_ptr; tmp->null_ptr= new_null_ptr; @@ -6224,29 +6225,21 @@ uint Field_string::max_packed_col_length(uint max_length) } -Field *Field_string::new_field(MEM_ROOT *root, struct st_table *new_table) +Field *Field_string::new_field(MEM_ROOT *root, struct st_table *new_table, + bool keep_type) { Field *new_field; - if (type() != MYSQL_TYPE_VAR_STRING || table == new_table) - return Field::new_field(root, new_table); + if (type() != MYSQL_TYPE_VAR_STRING || keep_type) + return Field::new_field(root, new_table, keep_type); /* Old VARCHAR field which should be modified to a VARCHAR on copy This is done to ensure that ALTER TABLE will convert old VARCHAR fields to now VARCHAR fields. */ - if ((new_field= new Field_varstring(field_length, maybe_null(), - field_name, new_table, charset()))) - { - /* - delayed_insert::get_local_table() needs a ptr copied from old table. - This is what other new_field() methods do too. The above method of - Field_varstring sets ptr to NULL. - */ - new_field->ptr= ptr; - } - return new_field; + return new Field_varstring(field_length, maybe_null(), + field_name, new_table, charset()); } /**************************************************************************** @@ -6738,9 +6731,11 @@ int Field_varstring::cmp_binary(const char *a_ptr, const char *b_ptr, } -Field *Field_varstring::new_field(MEM_ROOT *root, struct st_table *new_table) +Field *Field_varstring::new_field(MEM_ROOT *root, struct st_table *new_table, + bool keep_type) { - Field_varstring *res= (Field_varstring*) Field::new_field(root, new_table); + Field_varstring *res= (Field_varstring*) Field::new_field(root, new_table, + keep_type); if (res) res->length_bytes= length_bytes; return res; diff --git a/sql/field.h b/sql/field.h index f4d27e46877..18fcd20c97b 100644 --- a/sql/field.h +++ b/sql/field.h @@ -211,7 +211,8 @@ public: */ virtual bool can_be_compared_as_longlong() const { return FALSE; } virtual void free() {} - virtual Field *new_field(MEM_ROOT *root, struct st_table *new_table); + virtual Field *new_field(MEM_ROOT *root, struct st_table *new_table, + bool keep_type); virtual Field *new_key_field(MEM_ROOT *root, struct st_table *new_table, char *new_ptr, uchar *new_null_ptr, uint new_null_bit); @@ -1033,7 +1034,7 @@ public: enum_field_types real_type() const { return FIELD_TYPE_STRING; } bool has_charset(void) const { return charset() == &my_charset_bin ? FALSE : TRUE; } - Field *new_field(MEM_ROOT *root, struct st_table *new_table); + Field *new_field(MEM_ROOT *root, struct st_table *new_table, bool keep_type); }; @@ -1105,7 +1106,7 @@ public: enum_field_types real_type() const { return MYSQL_TYPE_VARCHAR; } bool has_charset(void) const { return charset() == &my_charset_bin ? FALSE : TRUE; } - Field *new_field(MEM_ROOT *root, struct st_table *new_table); + Field *new_field(MEM_ROOT *root, struct st_table *new_table, bool keep_type); Field *new_key_field(MEM_ROOT *root, struct st_table *new_table, char *new_ptr, uchar *new_null_ptr, uint new_null_bit); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 320b8e1df9d..1fdfb2c8cfa 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -17,6 +17,44 @@ /* Insert of records */ +/* + INSERT DELAYED + + Insert delayed is distinguished from a normal insert by lock_type == + TL_WRITE_DELAYED instead of TL_WRITE. It first tries to open a + "delayed" table (delayed_get_table()), but falls back to + open_and_lock_tables() on error and proceeds as normal insert then. + + Opening a "delayed" table means to find a delayed insert thread that + has the table open already. If this fails, a new thread is created and + waited for to open and lock the table. + + If accessing the thread succeeded, in + delayed_insert::get_local_table() the table of the thread is copied + for local use. A copy is required because the normal insert logic + works on a target table, but the other threads table object must not + be used. The insert logic uses the record buffer to create a record. + And the delayed insert thread uses the record buffer to pass the + record to the table handler. So there must be different objects. Also + the copied table is not included in the lock, so that the statement + can proceed even if the real table cannot be accessed at this moment. + + Copying a table object is not a trivial operation. Besides the TABLE + object there are the field pointer array, the field objects and the + record buffer. After copying the field objects, their pointers into + the record must be "moved" to point to the new record buffer. + + After this setup the normal insert logic is used. Only that for + delayed inserts write_delayed() is called instead of write_record(). + It inserts the rows into a queue and signals the delayed insert thread + instead of writing directly to the table. + + The delayed insert thread awakes from the signal. It locks the table, + inserts the rows from the queue, unlocks the table, and waits for the + next signal. It does normally live until a FLUSH TABLES or SHUTDOWN. + +*/ + #include "mysql_priv.h" #include "sp_head.h" #include "sql_trigger.h" @@ -1466,6 +1504,7 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) my_ptrdiff_t adjust_ptrs; Field **field,**org_field, *found_next_number_field; TABLE *copy; + DBUG_ENTER("delayed_insert::get_local_table"); /* First request insert thread to get a lock */ status=1; @@ -1489,31 +1528,47 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) } } + /* + Allocate memory for the TABLE object, the field pointers array, and + one record buffer of reclength size. Normally a table has three + record buffers of rec_buff_length size, which includes alignment + bytes. Since the table copy is used for creating one record only, + the other record buffers and alignment are unnecessary. + */ client_thd->proc_info="allocating local table"; copy= (TABLE*) client_thd->alloc(sizeof(*copy)+ (table->s->fields+1)*sizeof(Field**)+ table->s->reclength); if (!copy) goto error; + + /* Copy the TABLE object. */ *copy= *table; copy->s= ©->share_not_to_be_used; // No name hashing bzero((char*) ©->s->name_hash,sizeof(copy->s->name_hash)); /* We don't need to change the file handler here */ - field=copy->field=(Field**) (copy+1); - copy->record[0]=(byte*) (field+table->s->fields+1); - memcpy((char*) copy->record[0],(char*) table->record[0],table->s->reclength); + /* Assign the pointers for the field pointers array and the record. */ + field= copy->field= (Field**) (copy + 1); + copy->record[0]= (byte*) (field + table->s->fields + 1); + memcpy((char*) copy->record[0], (char*) table->record[0], + table->s->reclength); - /* Make a copy of all fields */ + /* + Make a copy of all fields. + The copied fields need to point into the copied record. This is done + by copying the field objects with their old pointer values and then + "move" the pointers by the distance between the original and copied + records. That way we preserve the relative positions in the records. + */ + adjust_ptrs= PTR_BYTE_DIFF(copy->record[0], table->record[0]); - adjust_ptrs=PTR_BYTE_DIFF(copy->record[0],table->record[0]); - - found_next_number_field=table->found_next_number_field; - for (org_field=table->field ; *org_field ; org_field++,field++) + found_next_number_field= table->found_next_number_field; + for (org_field= table->field; *org_field; org_field++, field++) { - if (!(*field= (*org_field)->new_field(client_thd->mem_root,copy))) - return 0; + if (!(*field= (*org_field)->new_field(client_thd->mem_root, copy, 1))) + DBUG_RETURN(0); (*field)->orig_table= copy; // Remove connection (*field)->move_field(adjust_ptrs); // Point at copy->record[0] if (*org_field == found_next_number_field) @@ -1540,14 +1595,14 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) /* Adjust lock_count. This table object is not part of a lock. */ copy->lock_count= 0; - return copy; + DBUG_RETURN(copy); /* Got fatal error */ error: tables_in_use--; status=1; pthread_cond_signal(&cond); // Inform thread about abort - return 0; + DBUG_RETURN(0); } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 465f41fa8de..d25f1291a7b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8217,7 +8217,8 @@ Field* create_tmp_field_from_field(THD *thd, Field* org_field, org_field->field_name, table, org_field->charset()); else - new_field= org_field->new_field(thd->mem_root, table); + new_field= org_field->new_field(thd->mem_root, table, + table == org_field->table); if (new_field) { if (item) @@ -13072,7 +13073,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, saved value */ Field *field= item->field; - item->result_field=field->new_field(thd->mem_root,field->table); + item->result_field=field->new_field(thd->mem_root,field->table, 1); char *tmp=(char*) sql_alloc(field->pack_length()+1); if (!tmp) goto err; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index f943b014118..91910227ec7 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -736,7 +736,8 @@ bool Table_triggers_list::prepare_record1_accessors(TABLE *table) QQ: it is supposed that it is ok to use this function for field cloning... */ - if (!(*old_fld= (*fld)->new_field(&table->mem_root, table))) + if (!(*old_fld= (*fld)->new_field(&table->mem_root, table, + table == (*fld)->table))) return 1; (*old_fld)->move_field((my_ptrdiff_t)(table->record[1] - table->record[0])); diff --git a/sql/table.cc b/sql/table.cc index 8e23bea2540..8f6b5ecf1eb 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -802,7 +802,8 @@ int openfrm(THD *thd, const char *name, const char *alias, uint db_stat, if (!(field->flags & BLOB_FLAG)) { // Create a new field field=key_part->field=field->new_field(&outparam->mem_root, - outparam); + outparam, + outparam == field->table); field->field_length=key_part->length; } } From 16ce188ded0305b4fe629546a0bd28592371ceeb Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 11:26:41 +0200 Subject: [PATCH 04/12] Bug#11824 - internal /tmp/*.{MYD,MYI} files remain, causing subsequent queries to fail Very complex select statements can create temporary tables that are too big to be represented as a MyISAM table. This was not checked at table creation time, but only at open time. The result was an attempt to delete the "impossible" table. But if the server is built --with-raid, MyISAM tries to open the table before deleting the files. It needs to find out if the table uses the raid support and how many raid chunks there are. This is done with an open "for repair", which will almost always succeed. But in this case we have an "impossible" table. The open failed. Hence the files were not deleted. Also the error message was a bit unspecific. I turned an open error in this situation into the assumption of having no raid support on the table. Thus the normal data file is tried to be deleted. This may however leave existing raid chunks behind. I also added a check in mi_create() to prevent the creation of an "impossible" table. A more decriptive error message is given in this case. No test case. The required select statement is way too large for the test suite. I added a test script to the bug report. myisam/mi_create.c: Bug#11824 - internal /tmp/*.{MYD,MYI} files remain, causing subsequent queries to fail Added a check to mi_create() that the table description header of the index file does not exceed 64KB. The header has only 16 bits to encode its length. myisam/mi_delete_table.c: Bug#11824 - internal /tmp/*.{MYD,MYI} files remain, causing subsequent queries to fail Interpret error in table open as not having a raid configuration on the tbale. Thus try to delete the normal data file, but leave behind raid chunks if they exist. --- myisam/mi_create.c | 17 +++++++++++++++++ myisam/mi_delete_table.c | 24 ++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/myisam/mi_create.c b/myisam/mi_create.c index 41c965c7c80..4183040500b 100644 --- a/myisam/mi_create.c +++ b/myisam/mi_create.c @@ -59,6 +59,8 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, my_off_t key_root[MI_MAX_POSSIBLE_KEY],key_del[MI_MAX_KEY_BLOCK_SIZE]; MI_CREATE_INFO tmp_create_info; DBUG_ENTER("mi_create"); + DBUG_PRINT("enter", ("keys: %u columns: %u uniques: %u flags: %u", + keys, columns, uniques, flags)); if (!ci) { @@ -447,6 +449,16 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, uniques * MI_UNIQUEDEF_SIZE + (key_segs + unique_key_parts)*HA_KEYSEG_SIZE+ columns*MI_COLUMNDEF_SIZE); + DBUG_PRINT("info", ("info_length: %u", info_length)); + /* There are only 16 bits for the total header length. */ + if (info_length > 65535) + { + my_printf_error(0, "MyISAM table '%s' has too many columns and/or " + "indexes and/or unique constraints.", + MYF(0), name + dirname_length(name)); + my_errno= HA_WRONG_CREATE_OPTION; + goto err; + } bmove(share.state.header.file_version,(byte*) myisam_file_magic,4); ci->old_options=options| (ci->old_options & HA_OPTION_TEMP_COMPRESS_RECORD ? @@ -594,6 +606,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, errpos=3; } + DBUG_PRINT("info", ("write state info and base info")); if (mi_state_info_write(file, &share.state, 2) || mi_base_info_write(file, &share.base)) goto err; @@ -607,6 +620,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, #endif /* Write key and keyseg definitions */ + DBUG_PRINT("info", ("write key and keyseg definitions")); for (i=0 ; i < share.base.keys - uniques; i++) { uint sp_segs=(keydefs[i].flag & HA_SPATIAL) ? 2*SPDIMS : 0; @@ -655,6 +669,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, } /* Save unique definition */ + DBUG_PRINT("info", ("write unique definitions")); for (i=0 ; i < share.state.header.uniques ; i++) { if (mi_uniquedef_write(file, &uniquedefs[i])) @@ -665,6 +680,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, goto err; } } + DBUG_PRINT("info", ("write field definitions")); for (i=0 ; i < share.base.fields ; i++) if (mi_recinfo_write(file, &recinfo[i])) goto err; @@ -679,6 +695,7 @@ int mi_create(const char *name,uint keys,MI_KEYDEF *keydefs, #endif /* Enlarge files */ + DBUG_PRINT("info", ("enlarge to keystart: %lu", (ulong) share.base.keystart)); if (my_chsize(file,(ulong) share.base.keystart,0,MYF(0))) goto err; diff --git a/myisam/mi_delete_table.c b/myisam/mi_delete_table.c index 6843881568d..2fba31cf8be 100644 --- a/myisam/mi_delete_table.c +++ b/myisam/mi_delete_table.c @@ -34,12 +34,24 @@ int mi_delete_table(const char *name) #ifdef USE_RAID { MI_INFO *info; - /* we use 'open_for_repair' to be able to delete a crashed table */ - if (!(info=mi_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR))) - DBUG_RETURN(my_errno); - raid_type = info->s->base.raid_type; - raid_chunks = info->s->base.raid_chunks; - mi_close(info); + /* + When built with RAID support, we need to determine if this table + makes use of the raid feature. If yes, we need to remove all raid + chunks. This is done with my_raid_delete(). Unfortunately it is + necessary to open the table just to check this. We use + 'open_for_repair' to be able to open even a crashed table. If even + this open fails, we assume no raid configuration for this table + and try to remove the normal data file only. This may however + leave the raid chunks behind. + */ + if (!(info= mi_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR))) + raid_type= 0; + else + { + raid_type= info->s->base.raid_type; + raid_chunks= info->s->base.raid_chunks; + mi_close(info); + } } #ifdef EXTRA_DEBUG check_table_is_closed(name,"delete"); From 82d127b55bbbeaf7fa263122365143f7727b7f10 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 19:33:59 +0400 Subject: [PATCH 05/12] Dec. 31st, 9999 is still a valid date, only starting with Jan 1st 10000 things become invalid (Bug #12356) mysql-test/r/func_sapdb.result: test cases for date range edge cases added mysql-test/r/func_time.result: test cases for date range edge cases added mysql-test/t/func_sapdb.test: test cases for date range edge cases added mysql-test/t/func_time.test: test cases for date range edge cases added --- mysql-test/r/func_sapdb.result | 6 ++++++ mysql-test/r/func_time.result | 6 ++++++ mysql-test/t/func_sapdb.test | 2 ++ mysql-test/t/func_time.test | 6 ++++++ sql/item_timefunc.cc | 11 ++++++----- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/func_sapdb.result b/mysql-test/r/func_sapdb.result index ea40e1559fd..b18885e218a 100644 --- a/mysql-test/r/func_sapdb.result +++ b/mysql-test/r/func_sapdb.result @@ -71,6 +71,12 @@ makedate(1997,1) select makedate(1997,0); makedate(1997,0) NULL +select makedate(9999,365); +makedate(9999,365) +9999-12-31 +select makedate(9999,366); +makedate(9999,366) +NULL select addtime("1997-12-31 23:59:59.999999", "1 1:1:1.000002"); addtime("1997-12-31 23:59:59.999999", "1 1:1:1.000002") 1998-01-02 01:01:01.000001 diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result index c90a4258036..fab0bf01f58 100644 --- a/mysql-test/r/func_time.result +++ b/mysql-test/r/func_time.result @@ -352,6 +352,12 @@ extract(SECOND FROM "1999-01-02 10:11:12") select extract(MONTH FROM "2001-02-00"); extract(MONTH FROM "2001-02-00") 2 +SELECT DATE_SUB(str_to_date('9999-12-31 00:01:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE); +DATE_SUB(str_to_date('9999-12-31 00:01:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE) +9999-12-31 00:00:00 +SELECT DATE_ADD(str_to_date('9999-12-30 23:59:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE); +DATE_ADD(str_to_date('9999-12-30 23:59:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE) +9999-12-31 00:00:00 SELECT "1900-01-01 00:00:00" + INTERVAL 2147483648 SECOND; "1900-01-01 00:00:00" + INTERVAL 2147483648 SECOND 1968-01-20 03:14:08 diff --git a/mysql-test/t/func_sapdb.test b/mysql-test/t/func_sapdb.test index 8fd793f067b..930ad37c60c 100644 --- a/mysql-test/t/func_sapdb.test +++ b/mysql-test/t/func_sapdb.test @@ -37,6 +37,8 @@ select weekofyear("1997-11-31 23:59:59.000001"); select makedate(1997,1); select makedate(1997,0); +select makedate(9999,365); +select makedate(9999,366); #Time functions diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test index d69545712c8..b232fb14e1e 100644 --- a/mysql-test/t/func_time.test +++ b/mysql-test/t/func_time.test @@ -139,6 +139,12 @@ select extract(MINUTE_SECOND FROM "10:11:12"); select extract(SECOND FROM "1999-01-02 10:11:12"); select extract(MONTH FROM "2001-02-00"); +# +# MySQL Bugs: #12356: DATE_SUB or DATE_ADD incorrectly returns null +# +SELECT DATE_SUB(str_to_date('9999-12-31 00:01:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE); +SELECT DATE_ADD(str_to_date('9999-12-30 23:59:00','%Y-%m-%d %H:%i:%s'), INTERVAL 1 MINUTE); + # # Test big intervals (Bug #3498) # diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 8d3e768b74e..27876096bc5 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -27,6 +27,7 @@ /* TODO: Move month and days to language files */ +/* Day number for Dec 31st, 9999 */ #define MAX_DAY_NUMBER 3652424L static const char *month_names[]= @@ -401,7 +402,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, if (yearday > 0) { uint days= calc_daynr(l_time->year,1,1) + yearday - 1; - if (days <= 0 || days >= MAX_DAY_NUMBER) + if (days <= 0 || days > MAX_DAY_NUMBER) goto err; get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day); } @@ -447,7 +448,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, (weekday - 1); } - if (days <= 0 || days >= MAX_DAY_NUMBER) + if (days <= 0 || days > MAX_DAY_NUMBER) goto err; get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day); } @@ -1931,7 +1932,7 @@ bool Item_date_add_interval::get_date(TIME *ltime, uint fuzzy_date) ltime->hour= (uint) (sec/3600); daynr= calc_daynr(ltime->year,ltime->month,1) + days; /* Day number from year 0 to 9999-12-31 */ - if ((ulonglong) daynr >= MAX_DAY_NUMBER) + if ((ulonglong) daynr > MAX_DAY_NUMBER) goto null_date; get_date_from_daynr((long) daynr, <ime->year, <ime->month, <ime->day); @@ -1941,7 +1942,7 @@ bool Item_date_add_interval::get_date(TIME *ltime, uint fuzzy_date) period= (calc_daynr(ltime->year,ltime->month,ltime->day) + sign * (long) interval.day); /* Daynumber from year 0 to 9999-12-31 */ - if ((ulong) period >= MAX_DAY_NUMBER) + if ((ulong) period > MAX_DAY_NUMBER) goto null_date; get_date_from_daynr((long) period,<ime->year,<ime->month,<ime->day); break; @@ -2412,7 +2413,7 @@ String *Item_func_makedate::val_str(String *str) days= calc_daynr(yearnr,1,1) + daynr - 1; /* Day number from year 0 to 9999-12-31 */ - if (days >= 0 && days < MAX_DAY_NUMBER) + if (days >= 0 && days <= MAX_DAY_NUMBER) { null_value=0; get_date_from_daynr(days,&l_time.year,&l_time.month,&l_time.day); From f4a07612efd66a559fe06810deee5e38cd96d03c Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 22:22:43 +0500 Subject: [PATCH 06/12] BUG#1662 - ALTER TABLE LIKE ignores DATA/INDEX DIRECTPORY Produce a warning if DATA/INDEX DIRECTORY is specified in ALTER TABLE statement. Ignoring of these options is documented in the symbolic links section of the manual. mysql-test/r/symlink.result: Modified test result according to fix for BUG#1662. sql/sql_parse.cc: Produce a warning if DATA/INDEX DIRECTORY is specified in ALTER TABLE statement. --- mysql-test/r/symlink.result | 6 ++++++ sql/sql_parse.cc | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/mysql-test/r/symlink.result b/mysql-test/r/symlink.result index caff53f8fd7..f6779689133 100644 --- a/mysql-test/r/symlink.result +++ b/mysql-test/r/symlink.result @@ -65,18 +65,24 @@ t9 CREATE TABLE `t9` ( ) ENGINE=MyISAM AUTO_INCREMENT=16725 DEFAULT CHARSET=latin1 DATA DIRECTORY='TEST_DIR/var/tmp/' INDEX DIRECTORY='TEST_DIR/var/run/' drop database mysqltest; create table t1 (a int not null) engine=myisam; +Warnings: +Warning 0 DATA DIRECTORY option ignored show create table t1; Table Create Table t1 CREATE TABLE `t1` ( `a` int(11) NOT NULL default '0' ) ENGINE=MyISAM DEFAULT CHARSET=latin1 alter table t1 add b int; +Warnings: +Warning 0 DATA DIRECTORY option ignored show create table t1; Table Create Table t1 CREATE TABLE `t1` ( `a` int(11) NOT NULL default '0', `b` int(11) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 +Warnings: +Warning 0 INDEX DIRECTORY option ignored show create table t1; Table Create Table t1 CREATE TABLE `t1` ( diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 504339684ce..fbe36bfdc4a 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2678,6 +2678,12 @@ unsent_create_error: } } /* Don't yet allow changing of symlinks with ALTER TABLE */ + if (lex->create_info.data_file_name) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0, + "DATA DIRECTORY option ignored"); + if (lex->create_info.index_file_name) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0, + "INDEX DIRECTORY option ignored"); lex->create_info.data_file_name=lex->create_info.index_file_name=0; /* ALTER TABLE ends previous transaction */ if (end_active_trans(thd)) From 0b235009e60929ef254e20b48ae1254196580372 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 14:27:37 +0200 Subject: [PATCH 07/12] Bug#17877 - Corrupted spatial index CHECK TABLE could complain about a fully intact spatial index. A wrong comparison operator was used for table checking. The result was that it checked for non-matching spatial keys. This succeeded if at least two different keys were present, but failed if only the matching key was present. I fixed the key comparison. myisam/mi_check.c: Bug#17877 - Corrupted spatial index Fixed the comparison operator for checking a spatial index. Using MBR_EQUAL | MBR_DATA to compare for equality and include the data pointer in the comparison. The latter finds the index entry that points to the current record. This is necessary for non-unique indexes. The old operator, SEARCH_SAME, is unknown to the rtree search functions and handled like MBR_DISJOINT. myisam/mi_key.c: Bug#17877 - Corrupted spatial index Added a missing DBUG_RETURN. myisam/rt_index.c: Bug#17877 - Corrupted spatial index Included the data pointer in the copy of the search key. This is necessary for searching the index entry that points to a specific record if the search_flag contains MBR_DATA. myisam/rt_mbr.c: Bug#17877 - Corrupted spatial index Extended the RT_CMP() macro with an assert for an unexpected comparison operator. mysql-test/r/gis-rtree.result: Bug#17877 - Corrupted spatial index The test result. mysql-test/t/gis-rtree.test: Bug#17877 - Corrupted spatial index The test case. --- myisam/mi_check.c | 5 ++-- myisam/mi_key.c | 2 +- myisam/rt_index.c | 8 ++++--- myisam/rt_mbr.c | 6 ++++- mysql-test/r/gis-rtree.result | 40 +++++++++++++++++++++++++++++++ mysql-test/t/gis-rtree.test | 44 +++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) diff --git a/myisam/mi_check.c b/myisam/mi_check.c index 2395640d5bf..1e62e5e641d 100644 --- a/myisam/mi_check.c +++ b/myisam/mi_check.c @@ -1155,12 +1155,13 @@ int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend) */ int search_result= (keyinfo->flag & HA_SPATIAL) ? rtree_find_first(info, key, info->lastkey, key_length, - SEARCH_SAME) : + MBR_EQUAL | MBR_DATA) : _mi_search(info,keyinfo,info->lastkey,key_length, SEARCH_SAME, info->s->state.key_root[key]); if (search_result) { - mi_check_print_error(param,"Record at: %10s Can't find key for index: %2d", + mi_check_print_error(param,"Record at: %10s " + "Can't find key for index: %2d", llstr(start_recpos,llbuff),key+1); if (error++ > MAXERR || !(param->testflag & T_VERBOSE)) goto err2; diff --git a/myisam/mi_key.c b/myisam/mi_key.c index cb85febd869..eaa854b1a37 100644 --- a/myisam/mi_key.c +++ b/myisam/mi_key.c @@ -54,7 +54,7 @@ uint _mi_make_key(register MI_INFO *info, uint keynr, uchar *key, TODO: nulls processing */ #ifdef HAVE_SPATIAL - return sp_make_key(info,keynr,key,record,filepos); + DBUG_RETURN(sp_make_key(info,keynr,key,record,filepos)); #else DBUG_ASSERT(0); /* mi_open should check that this never happens*/ #endif diff --git a/myisam/rt_index.c b/myisam/rt_index.c index 97554dca4e6..1806476dc39 100644 --- a/myisam/rt_index.c +++ b/myisam/rt_index.c @@ -183,9 +183,11 @@ int rtree_find_first(MI_INFO *info, uint keynr, uchar *key, uint key_length, return -1; } - /* Save searched key */ - memcpy(info->first_mbr_key, key, keyinfo->keylength - - info->s->base.rec_reflength); + /* + Save searched key, include data pointer. + The data pointer is required if the search_flag contains MBR_DATA. + */ + memcpy(info->first_mbr_key, key, keyinfo->keylength); info->last_rkey_length = key_length; info->rtree_recursion_depth = -1; diff --git a/myisam/rt_mbr.c b/myisam/rt_mbr.c index c43daec2f7c..897862c1c9a 100644 --- a/myisam/rt_mbr.c +++ b/myisam/rt_mbr.c @@ -52,10 +52,14 @@ if (EQUAL_CMP(amin, amax, bmin, bmax)) \ return 1; \ } \ - else /* if (nextflag & MBR_DISJOINT) */ \ + else if (nextflag & MBR_DISJOINT) \ { \ if (DISJOINT_CMP(amin, amax, bmin, bmax)) \ return 1; \ + }\ + else /* if unknown comparison operator */ \ + { \ + DBUG_ASSERT(0); \ } #define RT_CMP_KORR(type, korr_func, len, nextflag) \ diff --git a/mysql-test/r/gis-rtree.result b/mysql-test/r/gis-rtree.result index f479fc41ffb..3fcae8843c0 100644 --- a/mysql-test/r/gis-rtree.result +++ b/mysql-test/r/gis-rtree.result @@ -817,3 +817,43 @@ check table t1 extended; Table Op Msg_type Msg_text test.t1 check status OK drop table t1; +CREATE TABLE t1 ( +c1 geometry NOT NULL default '', +SPATIAL KEY i1 (c1(32)) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 (c1) VALUES ( +PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; +CREATE TABLE t1 ( +c1 geometry NOT NULL default '', +SPATIAL KEY i1 (c1(32)) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 (c1) VALUES ( +PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +INSERT INTO t1 (c1) VALUES ( +PolygonFromText('POLYGON((-65.7402776999 -96.6686111000, + -65.7372222000 -96.5516666000, + -65.8502777000 -96.5461111000, + -65.8527777000 -96.6627777000, + -65.7402776999 -96.6686111000))')); +INSERT INTO t1 (c1) VALUES ( +PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; diff --git a/mysql-test/t/gis-rtree.test b/mysql-test/t/gis-rtree.test index 682f67c61c4..eba53a8a9c5 100644 --- a/mysql-test/t/gis-rtree.test +++ b/mysql-test/t/gis-rtree.test @@ -188,4 +188,48 @@ check table t1 extended; drop table t1; +# +# Bug#17877 - Corrupted spatial index +# +CREATE TABLE t1 ( + c1 geometry NOT NULL default '', + SPATIAL KEY i1 (c1(32)) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 (c1) VALUES ( + PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +# This showed a missing key. +CHECK TABLE t1 EXTENDED; +DROP TABLE t1; +# +CREATE TABLE t1 ( + c1 geometry NOT NULL default '', + SPATIAL KEY i1 (c1(32)) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 (c1) VALUES ( + PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +INSERT INTO t1 (c1) VALUES ( + PolygonFromText('POLYGON((-65.7402776999 -96.6686111000, + -65.7372222000 -96.5516666000, + -65.8502777000 -96.5461111000, + -65.8527777000 -96.6627777000, + -65.7402776999 -96.6686111000))')); +# This is the same as the first insert to get a non-unique key. +INSERT INTO t1 (c1) VALUES ( + PolygonFromText('POLYGON((-18.6086111000 -66.9327777000, + -18.6055555000 -66.8158332999, + -18.7186111000 -66.8102777000, + -18.7211111000 -66.9269443999, + -18.6086111000 -66.9327777000))')); +# This showed (and still shows) OK. +CHECK TABLE t1 EXTENDED; +DROP TABLE t1; + # End of 4.1 tests From 9ad8a373f186b6f8f1edcb15f5f4f2dd6e60df7a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 16:07:39 +0200 Subject: [PATCH 08/12] Bug#19835 - Binary copy of corrupted tables crash the server when issuing a query A corrupt table with dynamic record format can crash the server when trying to select from it. I fixed the crash that resulted from the particular type of corruption that has been reported for this bug. No test case. To test it, one needs a table with a very special corruption. The bug report contains a file with such a table. myisam/mi_dynrec.c: Bug#19835 - Binary copy of corrupted tables crash the server when issuing a query Added a protection against corrupted records. A dynamic record header with invalid 'next' pointer could trigger the assert in _mi_get_block_info(). Now I avoid this by reporting a corruption error. --- myisam/mi_dynrec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/myisam/mi_dynrec.c b/myisam/mi_dynrec.c index 43783ca2d36..1b691c955f1 100644 --- a/myisam/mi_dynrec.c +++ b/myisam/mi_dynrec.c @@ -1116,6 +1116,9 @@ int _mi_read_dynamic_record(MI_INFO *info, my_off_t filepos, byte *buf) info->rec_cache.pos_in_file <= block_info.next_filepos && flush_io_cache(&info->rec_cache)) goto err; + /* A corrupted table can have wrong pointers. (Bug# 19835) */ + if (block_info.next_filepos == HA_OFFSET_ERROR) + goto panic; info->rec_cache.seek_not_done=1; if ((b_type=_mi_get_block_info(&block_info,file, block_info.next_filepos)) From 7aa26e264582964b6e4df64980edbee8cde8cbd7 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 18:55:30 +0200 Subject: [PATCH 09/12] Bug#14400 - Query joins wrong rows from table which is subject of "concurrent insert" It was possible that fetching a record by an exact key value (including the record pointer) could return a record with a different key value. This happened only if a concurrent insert added a record with the searched key value after the fetching statement locked the table for read. The search succeded on the key value, but the record was rejected as it was past the file length that was remembered at start of the fetching statement. With other words it was rejected as being a concurrently inserted record. The action to recover from this problem was to fetch the record that is pointed at by the next key of the index. This was repeated until a record below the file length was found. I do now avoid this loop if an exact match was searched. If this match is beyond the file length, it is now treated as "key not found". There cannot be another key with the same record pointer. myisam/mi_rkey.c: Bug#14400 - Query joins wrong rows from table which is subject of "concurrent insert" Added a check for exact key match before searching for the next key that was not concurrently inserted. If an exact key match finds a concurrently inserted row, this must be treated as "key not found". sql/sql_class.cc: Bug#14400 - Query joins wrong rows from table which is subject of "concurrent insert" Fixed some DBUG_ENTER strings. --- myisam/mi_rkey.c | 16 ++++++++++++++-- sql/sql_class.cc | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/myisam/mi_rkey.c b/myisam/mi_rkey.c index 70122288d6c..41c2e173b70 100644 --- a/myisam/mi_rkey.c +++ b/myisam/mi_rkey.c @@ -66,6 +66,7 @@ int mi_rkey(MI_INFO *info, byte *buf, int inx, const byte *key, uint key_len, if (fast_mi_readinfo(info)) goto err; + if (share->concurrent_insert) rw_rdlock(&share->key_root_lock[inx]); @@ -77,14 +78,24 @@ int mi_rkey(MI_INFO *info, byte *buf, int inx, const byte *key, uint key_len, if (!_mi_search(info,keyinfo, key_buff, use_key_length, myisam_read_vec[search_flag], info->s->state.key_root[inx])) { - while (info->lastpos >= info->state->data_file_length) + /* + If we are searching for an exact key (including the data pointer) + and this was added by an concurrent insert, + then the result is "key not found". + */ + if ((search_flag == HA_READ_KEY_EXACT) && + (info->lastpos >= info->state->data_file_length)) + { + my_errno= HA_ERR_KEY_NOT_FOUND; + info->lastpos= HA_OFFSET_ERROR; + } + else while (info->lastpos >= info->state->data_file_length) { /* Skip rows that are inserted by other threads since we got a lock Note that this can only happen if we are not searching after an exact key, because the keys are sorted according to position */ - if (_mi_search_next(info, keyinfo, info->lastkey, info->lastkey_length, myisam_readnext_vec[search_flag], @@ -92,6 +103,7 @@ int mi_rkey(MI_INFO *info, byte *buf, int inx, const byte *key, uint key_len, break; } } + if (share->concurrent_insert) rw_unlock(&share->key_root_lock[inx]); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 66d23ada163..f8cf8a7a58e 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -477,7 +477,7 @@ bool select_send::send_data(List &items) { List_iterator_fast li(items); String *packet= &thd->packet; - DBUG_ENTER("send_data"); + DBUG_ENTER("select_send::send_data"); #ifdef HAVE_INNOBASE_DB /* We may be passing the control from mysqld to the client: release the @@ -611,7 +611,7 @@ select_export::prepare(List &list) bool select_export::send_data(List &items) { - DBUG_ENTER("send_data"); + DBUG_ENTER("select_export::send_data"); char buff[MAX_FIELD_WIDTH],null_buff[2],space[MAX_FIELD_WIDTH]; bool space_inited=0; String tmp(buff,sizeof(buff)),*res; @@ -828,7 +828,7 @@ bool select_dump::send_data(List &items) String tmp(buff,sizeof(buff)),*res; tmp.length(0); Item *item; - DBUG_ENTER("send_data"); + DBUG_ENTER("select_dump::send_data"); if (thd->offset_limit) { // using limit offset,count From d87e4fbffbd00558f1cce58327d6e88129db4231 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 5 Jul 2006 16:23:18 +0200 Subject: [PATCH 10/12] After merge fix --- mysql-test/r/gis.result | 19 ++++++++++--------- mysql-test/r/key.result | 4 ++-- mysql-test/t/gis.test | 12 +++++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result index 4140e13da12..7a0f689df36 100644 --- a/mysql-test/r/gis.result +++ b/mysql-test/r/gis.result @@ -671,15 +671,6 @@ POINT(10 10) select (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))); (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))) POINT(10 10) -create table t1 (g GEOMETRY); -select * from t1; -Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr -def test t1 t1 g g 255 4294967295 0 Y 144 0 63 -g -select asbinary(g) from t1; -Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr -def asbinary(g) 252 8192 0 Y 128 0 63 -asbinary(g) create table t1 (s1 geometry not null,s2 char(100)); create trigger t1_bu before update on t1 for each row set new.s1 = null; insert into t1 values (null,null); @@ -703,3 +694,13 @@ alter table t1 add primary key pti(pt); ERROR 42000: BLOB/TEXT column 'pt' used in key specification without a key length alter table t1 add primary key pti(pt(20)); drop table t1; +create table t1 (g GEOMETRY); +select * from t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def test t1 t1 g g 255 4294967295 0 Y 144 0 63 +g +select asbinary(g) from t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def asbinary(g) 252 8192 0 Y 128 0 63 +asbinary(g) +drop table t1; diff --git a/mysql-test/r/key.result b/mysql-test/r/key.result index 6c05a3dde8b..a6f05143b3e 100644 --- a/mysql-test/r/key.result +++ b/mysql-test/r/key.result @@ -336,8 +336,8 @@ UNIQUE i1idx (i1), UNIQUE i2idx (i2)); desc t1; Field Type Null Key Default Extra -i1 int(11) UNI 0 -i2 int(11) UNI 0 +i1 int(11) NO UNI +i2 int(11) NO UNI drop table t1; create table t1 ( c1 int, diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test index b25faddc1e8..4c6ff9b2fe7 100644 --- a/mysql-test/t/gis.test +++ b/mysql-test/t/gis.test @@ -377,11 +377,6 @@ select (asWKT(geomfromwkb((0x010100000000000000000024400000000000002440)))); # End of 4.1 tests ---enable_metadata -create table t1 (g GEOMETRY); -select * from t1; -select asbinary(g) from t1; ---disable_metadata # # Bug #12281 (Geometry: crash in trigger) # @@ -414,3 +409,10 @@ create table t1(pt GEOMETRY); alter table t1 add primary key pti(pt); alter table t1 add primary key pti(pt(20)); drop table t1; + +--enable_metadata +create table t1 (g GEOMETRY); +select * from t1; +select asbinary(g) from t1; +--disable_metadata +drop table t1; From 9532056d1351f0377db90738363f5b4bff176588 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 6 Jul 2006 15:38:47 +0200 Subject: [PATCH 11/12] After merge fixes. mysql-test/r/archive.result: After merge fix. It might come from the fix for bug 1662 (ALTER TABLE LIKE ignores DATA/INDEX DIRECTPORY) sql/time.cc: After merge fix. Auto resolve failed because this piece of code was moved from another file to here. --- mysql-test/r/archive.result | 2 ++ sql/time.cc | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/archive.result b/mysql-test/r/archive.result index cacf4aaf304..1dfec8ff713 100644 --- a/mysql-test/r/archive.result +++ b/mysql-test/r/archive.result @@ -13812,6 +13812,8 @@ select * from t1 where i between 2 and 4 and v in ('def','3r4f','lmn'); i v 4 3r4f alter table t1 data directory="$MYSQLTEST_VARDIR/tmp"; +Warnings: +Warning 0 DATA DIRECTORY option ignored select * from t1; i v 1 def diff --git a/sql/time.cc b/sql/time.cc index ae776a32aab..0461f7723c6 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -749,6 +749,7 @@ void make_truncated_value_warning(THD *thd, const char *str_val, ER_TRUNCATED_WRONG_VALUE, warn_buff); } +/* Daynumber from year 0 to 9999-12-31 */ #define MAX_DAY_NUMBER 3652424L bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval) @@ -804,7 +805,7 @@ bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval) ltime->hour= (uint) (sec/3600); daynr= calc_daynr(ltime->year,ltime->month,1) + days; /* Day number from year 0 to 9999-12-31 */ - if ((ulonglong) daynr >= MAX_DAY_NUMBER) + if ((ulonglong) daynr > MAX_DAY_NUMBER) goto invalid_date; get_date_from_daynr((long) daynr, <ime->year, <ime->month, <ime->day); @@ -815,7 +816,7 @@ bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval) period= (calc_daynr(ltime->year,ltime->month,ltime->day) + sign * (long) interval.day); /* Daynumber from year 0 to 9999-12-31 */ - if ((ulong) period >= MAX_DAY_NUMBER) + if ((ulong) period > MAX_DAY_NUMBER) goto invalid_date; get_date_from_daynr((long) period,<ime->year,<ime->month,<ime->day); break; From 51a89229d6a1ed176352100e616476707cea0754 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 6 Jul 2006 20:12:33 +0200 Subject: [PATCH 12/12] After merge fix. --- sql/table.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/table.cc b/sql/table.cc index a96ca0da881..8bee8bf1598 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -1456,7 +1456,7 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, Create a new field for the key part that matches the index */ field= key_part->field=field->new_field(&outparam->mem_root, - outparam); + outparam, 0); field->field_length= key_part->length; } }