From 4f2ec8f3de3f32ab3c78c8ab9721334286e438d8 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@magare.gmz" <> Date: Tue, 27 Mar 2007 19:28:04 +0300 Subject: [PATCH 1/5] Bug #26815: When creating a temporary table the concise column type of a string expression is decided based on its length: - if its length is under 512 it is stored as either varchar or char. - otherwise it is stored as a BLOB. There is a flag (convert_blob_length) to create_tmp_field that, when >0 allows to force creation of a varchar if the max blob length is under convert_blob_length. However it must be verified that convert_blob_length (settable through a SQL option in some cases) is under the maximum that can be stored in a varchar column. While performing that check for expressions in create_tmp_field_from_item the max length of the blob was used instead. This causes blob columns to be created in the heap temp table used by GROUP_CONCAT (where blobs must not be created in the temp table because of the constant convert_blob_length that is passed to create_tmp_field() ). And since these blob columns are not expected in that place we get wrong results. Fixed by checking that the value of the flag variable is in the limits that fit into VARCHAR instead of the max length of the blob column. --- mysql-test/r/func_gconcat.result | 10 ++++++++++ mysql-test/t/func_gconcat.test | 12 +++++++++++- sql/item_sum.cc | 3 +-- sql/sql_select.cc | 3 +-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/func_gconcat.result b/mysql-test/r/func_gconcat.result index 6989b89833b..71419b5b2c3 100644 --- a/mysql-test/r/func_gconcat.result +++ b/mysql-test/r/func_gconcat.result @@ -728,3 +728,13 @@ f2 group_concat(f1) aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 2 drop table t1; +CREATE TABLE t1(a TEXT, b CHAR(20)); +INSERT INTO t1 VALUES ("one.1","one.1"),("two.2","two.2"),("one.3","one.3"); +SELECT GROUP_CONCAT(DISTINCT UCASE(a)) FROM t1; +GROUP_CONCAT(DISTINCT UCASE(a)) +ONE.1,TWO.2,ONE.3 +SELECT GROUP_CONCAT(DISTINCT UCASE(b)) FROM t1; +GROUP_CONCAT(DISTINCT UCASE(b)) +ONE.1,TWO.2,ONE.3 +DROP TABLE t1; +End of 5.0 tests diff --git a/mysql-test/t/func_gconcat.test b/mysql-test/t/func_gconcat.test index 3ff4b35873b..0dd82864520 100644 --- a/mysql-test/t/func_gconcat.test +++ b/mysql-test/t/func_gconcat.test @@ -497,4 +497,14 @@ select f2,group_concat(f1) from t1 group by f2; --disable_metadata drop table t1; -# End of 4.1 tests +# +# Bug #26815: Unexpected built-in function behavior: group_concat(distinct +# substring_index()) +# +CREATE TABLE t1(a TEXT, b CHAR(20)); +INSERT INTO t1 VALUES ("one.1","one.1"),("two.2","two.2"),("one.3","one.3"); +SELECT GROUP_CONCAT(DISTINCT UCASE(a)) FROM t1; +SELECT GROUP_CONCAT(DISTINCT UCASE(b)) FROM t1; +DROP TABLE t1; + +--echo End of 5.0 tests diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 368dc9a7d38..9d626cb7733 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -417,8 +417,7 @@ Field *Item_sum::create_tmp_field(bool group, TABLE *table, 2-byte lenght. */ if (max_length/collation.collation->mbmaxlen > 255 && - max_length/collation.collation->mbmaxlen < UINT_MAX16 && - convert_blob_length) + convert_blob_length < UINT_MAX16 && convert_blob_length) return new Field_varstring(convert_blob_length, maybe_null, name, table, collation.collation); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bb57764700d..7e9b1fec12e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8805,8 +8805,7 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, 2-byte lenght. */ else if (item->max_length/item->collation.collation->mbmaxlen > 255 && - item->max_length/item->collation.collation->mbmaxlen < UINT_MAX16 - && convert_blob_length) + convert_blob_length < UINT_MAX16 && convert_blob_length) new_field= new Field_varstring(convert_blob_length, maybe_null, item->name, table, item->collation.collation); From 9c55cd3e032dc85cf44042ed6d035936ca4e14ae Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@magare.gmz" <> Date: Wed, 28 Mar 2007 12:09:30 +0300 Subject: [PATCH 2/5] disabled a test reuturning wrong result (reported separately) --- mysql-test/r/subselect3.result | 6 ------ mysql-test/t/subselect3.test | 5 +++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/subselect3.result b/mysql-test/r/subselect3.result index 6a7a601ccf9..96a3ee00a59 100644 --- a/mysql-test/r/subselect3.result +++ b/mysql-test/r/subselect3.result @@ -661,12 +661,6 @@ SELECT * FROM t1 GROUP by t1.a HAVING (MAX(t1.b) > (SELECT MAX(t2.b) FROM t2 WHERE t2.c < t1.c HAVING MAX(t2.b+t1.a) < 10)); a b c -SELECT a, AVG(b), (SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=AVG(t1.b)) -AS test FROM t1 GROUP BY a; -a AVG(b) test -1 4.0000 NULL -2 2.0000 k -3 2.5000 NULL SELECT a,b,c FROM t1 WHERE b in (9,3,4) ORDER BY b,c; a b c 1 3 c diff --git a/mysql-test/t/subselect3.test b/mysql-test/t/subselect3.test index e3703c0da16..e8eae3e2452 100644 --- a/mysql-test/t/subselect3.test +++ b/mysql-test/t/subselect3.test @@ -507,8 +507,9 @@ SELECT a, MAX(b), (SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=MAX(t1.b)) SELECT * FROM t1 GROUP by t1.a HAVING (MAX(t1.b) > (SELECT MAX(t2.b) FROM t2 WHERE t2.c < t1.c HAVING MAX(t2.b+t1.a) < 10)); -SELECT a, AVG(b), (SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=AVG(t1.b)) - AS test FROM t1 GROUP BY a; +#FIXME: Enable this test after fixing bug #27321 +#SELECT a, AVG(b), (SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=AVG(t1.b)) +# AS test FROM t1 GROUP BY a; SELECT a,b,c FROM t1 WHERE b in (9,3,4) ORDER BY b,c; From c3eb3f7093b5be942d036995db543120080b206f Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@magare.gmz" <> Date: Wed, 28 Mar 2007 14:35:23 +0300 Subject: [PATCH 3/5] Bug #27300: Geometry fields have a result type string and a special subclass to cater for the differences between them and the base class (just like DATE/TIME). When creating temporary tables for results of functions that return results of type GEOMETRY we must construct fields of the derived class instead of the base class. Fixed by creating a GEOMETRY field (Field_geom) instead of a generic BLOB (Field_blob) in temp tables for the results of GIS functions that have GEOMETRY return type (Item_geometry_func). --- mysql-test/r/gis.result | 11 +++++++++++ mysql-test/t/gis.test | 11 +++++++++++ sql/item.cc | 5 ++++- sql/sql_select.cc | 6 +++--- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result index 58149d68ca3..749c84a1a6f 100644 --- a/mysql-test/r/gis.result +++ b/mysql-test/r/gis.result @@ -769,3 +769,14 @@ create table t1 (g geometry not null); insert into t1 values(default); ERROR 22003: Cannot get geometry object from data you send to the GEOMETRY field drop table t1; +CREATE TABLE t1 (a GEOMETRY); +CREATE VIEW v1 AS SELECT GeomFromwkb(ASBINARY(a)) FROM t1; +CREATE VIEW v2 AS SELECT a FROM t1; +DESCRIBE v1; +Field Type Null Key Default Extra +GeomFromwkb(ASBINARY(a)) geometry YES NULL +DESCRIBE v2; +Field Type Null Key Default Extra +a geometry YES NULL +DROP VIEW v1,v2; +DROP TABLE t1; diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test index d92ff804ccb..4f6104aab3e 100644 --- a/mysql-test/t/gis.test +++ b/mysql-test/t/gis.test @@ -479,3 +479,14 @@ create table t1 (g geometry not null); insert into t1 values(default); drop table t1; +# +# Bug #27300: create view with geometry functions lost columns types +# +CREATE TABLE t1 (a GEOMETRY); +CREATE VIEW v1 AS SELECT GeomFromwkb(ASBINARY(a)) FROM t1; +CREATE VIEW v2 AS SELECT a FROM t1; +DESCRIBE v1; +DESCRIBE v2; + +DROP VIEW v1,v2; +DROP TABLE t1; diff --git a/sql/item.cc b/sql/item.cc index 808e7925729..8568a44c547 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4275,7 +4275,6 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table) case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_GEOMETRY: if (this->type() == Item::TYPE_HOLDER) return new Field_blob(max_length, maybe_null, name, table, collation.collation, 1); @@ -4283,6 +4282,10 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table) return new Field_blob(max_length, maybe_null, name, table, collation.collation); break; // Blob handled outside of case + case MYSQL_TYPE_GEOMETRY: + return new Field_geom(max_length, maybe_null, name, table, + (Field::geometry_type) + ((Item_geometry_func *)this)->get_geometry_type()); } } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bb57764700d..d983146da93 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8793,12 +8793,12 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, enum enum_field_types type; /* - DATE/TIME fields have STRING_RESULT result type. To preserve - type they needed to be handled separately. + DATE/TIME and GEOMETRY fields have STRING_RESULT result type. + To preserve type they needed to be handled separately. */ if ((type= item->field_type()) == MYSQL_TYPE_DATETIME || type == MYSQL_TYPE_TIME || type == MYSQL_TYPE_DATE || - type == MYSQL_TYPE_TIMESTAMP) + type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_GEOMETRY) new_field= item->tmp_table_field_from_field_type(table); /* Make sure that the blob fits into a Field_varstring which has From c52e8b3e64ddfea94db4c619d1ba5465c79af36b Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@magare.gmz" <> Date: Thu, 29 Mar 2007 19:19:31 +0300 Subject: [PATCH 4/5] Bug #26815: When creating a temporary table the concise column type of a string expression is decided based on its length: - if its length is under 512 it is stored as either varchar or char. - otherwise it is stored as a BLOB. There is a flag (convert_blob_length) to create_tmp_field that, when >0 allows to force creation of a varchar if the max blob length is under convert_blob_length. However it must be verified that convert_blob_length (settable through a SQL option in some cases) is under the maximum that can be stored in a varchar column. While performing that check for expressions in create_tmp_field_from_item the max length of the blob was used instead. This causes blob columns to be created in the heap temp table used by GROUP_CONCAT (where blobs must not be created in the temp table because of the constant convert_blob_length that is passed to create_tmp_field() ). And since these blob columns are not expected in that place we get wrong results. Fixed by checking that the value of the flag variable is in the limits that fit into VARCHAR instead of the max length of the blob column. --- mysql-test/r/func_gconcat.result | 10 ++++++++++ mysql-test/t/func_gconcat.test | 12 +++++++++++- sql/item_sum.cc | 3 +-- sql/sql_select.cc | 3 +-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/func_gconcat.result b/mysql-test/r/func_gconcat.result index 6989b89833b..71419b5b2c3 100644 --- a/mysql-test/r/func_gconcat.result +++ b/mysql-test/r/func_gconcat.result @@ -728,3 +728,13 @@ f2 group_concat(f1) aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 2 drop table t1; +CREATE TABLE t1(a TEXT, b CHAR(20)); +INSERT INTO t1 VALUES ("one.1","one.1"),("two.2","two.2"),("one.3","one.3"); +SELECT GROUP_CONCAT(DISTINCT UCASE(a)) FROM t1; +GROUP_CONCAT(DISTINCT UCASE(a)) +ONE.1,TWO.2,ONE.3 +SELECT GROUP_CONCAT(DISTINCT UCASE(b)) FROM t1; +GROUP_CONCAT(DISTINCT UCASE(b)) +ONE.1,TWO.2,ONE.3 +DROP TABLE t1; +End of 5.0 tests diff --git a/mysql-test/t/func_gconcat.test b/mysql-test/t/func_gconcat.test index 3ff4b35873b..0dd82864520 100644 --- a/mysql-test/t/func_gconcat.test +++ b/mysql-test/t/func_gconcat.test @@ -497,4 +497,14 @@ select f2,group_concat(f1) from t1 group by f2; --disable_metadata drop table t1; -# End of 4.1 tests +# +# Bug #26815: Unexpected built-in function behavior: group_concat(distinct +# substring_index()) +# +CREATE TABLE t1(a TEXT, b CHAR(20)); +INSERT INTO t1 VALUES ("one.1","one.1"),("two.2","two.2"),("one.3","one.3"); +SELECT GROUP_CONCAT(DISTINCT UCASE(a)) FROM t1; +SELECT GROUP_CONCAT(DISTINCT UCASE(b)) FROM t1; +DROP TABLE t1; + +--echo End of 5.0 tests diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 368dc9a7d38..9d626cb7733 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -417,8 +417,7 @@ Field *Item_sum::create_tmp_field(bool group, TABLE *table, 2-byte lenght. */ if (max_length/collation.collation->mbmaxlen > 255 && - max_length/collation.collation->mbmaxlen < UINT_MAX16 && - convert_blob_length) + convert_blob_length < UINT_MAX16 && convert_blob_length) return new Field_varstring(convert_blob_length, maybe_null, name, table, collation.collation); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bb57764700d..7e9b1fec12e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8805,8 +8805,7 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, 2-byte lenght. */ else if (item->max_length/item->collation.collation->mbmaxlen > 255 && - item->max_length/item->collation.collation->mbmaxlen < UINT_MAX16 - && convert_blob_length) + convert_blob_length < UINT_MAX16 && convert_blob_length) new_field= new Field_varstring(convert_blob_length, maybe_null, item->name, table, item->collation.collation); From 7c42232d1de1746c163fd368806ad566f88d5917 Mon Sep 17 00:00:00 2001 From: "evgen@sunlight.local" <> Date: Fri, 30 Mar 2007 18:13:33 +0400 Subject: [PATCH 5/5] Bug#23233: 0 as LAST_INSERT_ID() after INSERT .. ON DUPLICATE in the NO_AUTO_VALUE_ON_ZERO mode. In the NO_AUTO_VALUE_ON_ZERO mode the table->auto_increment_field_not_null variable is used to indicate that a non-NULL value was specified by the user for an auto_increment column. When an INSERT .. ON DUPLICATE updates the auto_increment field this variable is set to true and stays unchanged for the next insert operation. This makes the next inserted row sometimes wrongly have 0 as the value of the auto_increment field. Now the fill_record() function resets the table->auto_increment_field_not_null variable before filling the record. The table->auto_increment_field_not_null variable is also reset by the open_table() function for a case if we missed some auto_increment_field_not_null handling bug. Now the table->auto_increment_field_not_null is reset at the end of the mysql_load() function. Reset the table->auto_increment_field_not_null variable after each write_row() call in the copy_data_between_tables() function. --- mysql-test/r/insert_update.result | 78 +++++++++++++++++++++++++++++++ mysql-test/t/insert_update.test | 52 +++++++++++++++++++++ sql/field_conv.cc | 2 +- sql/handler.cc | 6 +-- sql/sql_base.cc | 67 ++++++++++++++++++++++++-- sql/sql_insert.cc | 2 + sql/sql_load.cc | 3 +- sql/sql_table.cc | 4 +- sql/table.h | 5 ++ 9 files changed, 205 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/insert_update.result b/mysql-test/r/insert_update.result index ef0d8ec239e..fd70fcb9084 100644 --- a/mysql-test/r/insert_update.result +++ b/mysql-test/r/insert_update.result @@ -258,3 +258,81 @@ SELECT LAST_INSERT_ID(); LAST_INSERT_ID() 1 DROP TABLE t1; +SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; +CREATE TABLE `t1` ( +`id` int(11) PRIMARY KEY auto_increment, +`f1` varchar(10) NOT NULL UNIQUE +); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +1 +SELECT * FROM t1; +id f1 +1 test1 +INSERT IGNORE INTO t1 (f1) VALUES ("test2") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT * FROM t1; +id f1 +1 test1 +2 test2 +INSERT IGNORE INTO t1 (f1) VALUES ("test2") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +2 +SELECT * FROM t1; +id f1 +1 test1 +2 test2 +INSERT IGNORE INTO t1 (f1) VALUES ("test3") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +3 +SELECT * FROM t1; +id f1 +1 test1 +2 test2 +3 test3 +DROP TABLE t1; +CREATE TABLE `t1` ( +`id` int(11) PRIMARY KEY auto_increment, +`f1` varchar(10) NOT NULL UNIQUE +); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +1 +SELECT * FROM t1; +id f1 +1 test1 +INSERT IGNORE INTO t1 (f1) VALUES ("test1"),("test4") +ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +1 +SELECT * FROM t1; +id f1 +1 test1 +2 test4 +DROP TABLE t1; +CREATE TABLE `t1` ( +`id` int(11) PRIMARY KEY auto_increment, +`f1` varchar(10) NOT NULL UNIQUE, +tim1 timestamp default '2003-01-01 00:00:00' on update current_timestamp +); +INSERT INTO t1 (f1) VALUES ("test1"); +SELECT id, f1 FROM t1; +id f1 +1 test1 +REPLACE INTO t1 VALUES (0,"test1",null); +SELECT id, f1 FROM t1; +id f1 +0 test1 +DROP TABLE t1; +SET SQL_MODE=''; diff --git a/mysql-test/t/insert_update.test b/mysql-test/t/insert_update.test index b0de66f7fc6..76df4502769 100644 --- a/mysql-test/t/insert_update.test +++ b/mysql-test/t/insert_update.test @@ -195,3 +195,55 @@ SELECT LAST_INSERT_ID(); INSERT t1 (f2) VALUES ('test') ON DUPLICATE KEY UPDATE f1 = LAST_INSERT_ID(f1); SELECT LAST_INSERT_ID(); DROP TABLE t1; + +# +# Bug#23233: 0 as LAST_INSERT_ID() after INSERT .. ON DUPLICATE in the +# NO_AUTO_VALUE_ON_ZERO mode. +# +SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; +CREATE TABLE `t1` ( + `id` int(11) PRIMARY KEY auto_increment, + `f1` varchar(10) NOT NULL UNIQUE +); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +SELECT * FROM t1; +INSERT IGNORE INTO t1 (f1) VALUES ("test2") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT * FROM t1; +INSERT IGNORE INTO t1 (f1) VALUES ("test2") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +SELECT * FROM t1; +INSERT IGNORE INTO t1 (f1) VALUES ("test3") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +SELECT * FROM t1; +DROP TABLE t1; +CREATE TABLE `t1` ( + `id` int(11) PRIMARY KEY auto_increment, + `f1` varchar(10) NOT NULL UNIQUE +); +INSERT IGNORE INTO t1 (f1) VALUES ("test1") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +SELECT * FROM t1; +INSERT IGNORE INTO t1 (f1) VALUES ("test1"),("test4") + ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id); +SELECT LAST_INSERT_ID(); +SELECT * FROM t1; +DROP TABLE t1; +CREATE TABLE `t1` ( + `id` int(11) PRIMARY KEY auto_increment, + `f1` varchar(10) NOT NULL UNIQUE, + tim1 timestamp default '2003-01-01 00:00:00' on update current_timestamp +); +INSERT INTO t1 (f1) VALUES ("test1"); +SELECT id, f1 FROM t1; +REPLACE INTO t1 VALUES (0,"test1",null); +SELECT id, f1 FROM t1; +DROP TABLE t1; +SET SQL_MODE=''; diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 32180f0a93e..429d914db97 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -173,7 +173,7 @@ set_field_to_null_with_conversions(Field *field, bool no_conversions) if (field == field->table->next_number_field) { field->table->auto_increment_field_not_null= FALSE; - return 0; // field is set in handler.cc + return 0; // field is set in fill_record() } if (current_thd->count_cuted_fields == CHECK_FIELD_WARN) { diff --git a/sql/handler.cc b/sql/handler.cc index 524f47209dc..6cba079e736 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1598,7 +1598,6 @@ int handler::update_auto_increment() ulonglong nr; THD *thd= table->in_use; struct system_variables *variables= &thd->variables; - bool auto_increment_field_not_null; DBUG_ENTER("handler::update_auto_increment"); /* @@ -1606,14 +1605,11 @@ int handler::update_auto_increment() row was not inserted */ thd->prev_insert_id= thd->next_insert_id; - auto_increment_field_not_null= table->auto_increment_field_not_null; - table->auto_increment_field_not_null= FALSE; if ((nr= table->next_number_field->val_int()) != 0 || - auto_increment_field_not_null && + table->auto_increment_field_not_null && thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO) { - /* Clear flag for next row */ /* Mark that we didn't generate a new value **/ auto_increment_column_changed=0; adjust_next_insert_id_after_explicit_value(nr); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 97cb2d00689..1689e5c65c0 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1640,6 +1640,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->used_keys= table->s->keys_for_keyread; table->fulltext_searched= 0; table->file->ft_handler= 0; + /* Catch wrong handling of the auto_increment_field_not_null. */ + DBUG_ASSERT(!table->auto_increment_field_not_null); + table->auto_increment_field_not_null= FALSE; if (table->timestamp_field) table->timestamp_field_type= table->timestamp_field->get_auto_set_type(); table->pos_in_table_list= table_list; @@ -5272,6 +5275,11 @@ err_no_arena: values values to fill with ignore_errors TRUE if we should ignore errors + NOTE + fill_record() may set table->auto_increment_field_not_null and a + caller should make sure that it is reset after their last call to this + function. + RETURN FALSE OK TRUE error occured @@ -5284,27 +5292,52 @@ fill_record(THD * thd, List &fields, List &values, List_iterator_fast f(fields),v(values); Item *value, *fld; Item_field *field; + TABLE *table= 0; DBUG_ENTER("fill_record"); + /* + Reset the table->auto_increment_field_not_null as it is valid for + only one row. + */ + if (fields.elements) + { + /* + On INSERT or UPDATE fields are checked to be from the same table, + thus we safely can take table from the first field. + */ + fld= (Item_field*)f++; + if (!(field= fld->filed_for_view_update())) + { + my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name); + goto err; + } + table= field->field->table; + table->auto_increment_field_not_null= FALSE; + f.rewind(); + } while ((fld= f++)) { if (!(field= fld->filed_for_view_update())) { my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name); - DBUG_RETURN(TRUE); + goto err; } value=v++; Field *rfield= field->field; - TABLE *table= rfield->table; + table= rfield->table; if (rfield == table->next_number_field) table->auto_increment_field_not_null= TRUE; if ((value->save_in_field(rfield, 0) < 0) && !ignore_errors) { my_message(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR), MYF(0)); - DBUG_RETURN(TRUE); + goto err; } } DBUG_RETURN(thd->net.report_error); +err: + if (table) + table->auto_increment_field_not_null= FALSE; + DBUG_RETURN(TRUE); } @@ -5353,6 +5386,11 @@ fill_record_n_invoke_before_triggers(THD *thd, List &fields, values list of fields ignore_errors TRUE if we should ignore errors + NOTE + fill_record() may set table->auto_increment_field_not_null and a + caller should make sure that it is reset after their last call to this + function. + RETURN FALSE OK TRUE error occured @@ -5363,19 +5401,38 @@ fill_record(THD *thd, Field **ptr, List &values, bool ignore_errors) { List_iterator_fast v(values); Item *value; + TABLE *table= 0; DBUG_ENTER("fill_record"); Field *field; + /* + Reset the table->auto_increment_field_not_null as it is valid for + only one row. + */ + if (*ptr) + { + /* + On INSERT or UPDATE fields are checked to be from the same table, + thus we safely can take table from the first field. + */ + table= (*ptr)->table; + table->auto_increment_field_not_null= FALSE; + } while ((field = *ptr++)) { value=v++; - TABLE *table= field->table; + table= field->table; if (field == table->next_number_field) table->auto_increment_field_not_null= TRUE; if (value->save_in_field(field, 0) == -1) - DBUG_RETURN(TRUE); + goto err; } DBUG_RETURN(thd->net.report_error); + +err: + if (table) + table->auto_increment_field_not_null= FALSE; + DBUG_RETURN(TRUE); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d9d32d14321..0fa027f89d6 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -757,6 +757,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, table->next_number_field=0; thd->count_cuted_fields= CHECK_FIELD_IGNORE; thd->next_insert_id=0; // Reset this if wrongly used + table->auto_increment_field_not_null= FALSE; if (duplic != DUP_ERROR || ignore) table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); if (duplic == DUP_REPLACE && @@ -2571,6 +2572,7 @@ select_insert::~select_insert() if (table) { table->next_number_field=0; + table->auto_increment_field_not_null= FALSE; table->file->reset(); } thd->count_cuted_fields= CHECK_FIELD_IGNORE; diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 3f67a0c3f5d..7a535381c01 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -493,6 +493,7 @@ err: mysql_unlock_tables(thd, thd->lock); thd->lock=0; } + table->auto_increment_field_not_null= FALSE; thd->abort_on_warning= 0; DBUG_RETURN(error); } @@ -589,8 +590,6 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, { uint length; byte save_chr; - if (field == table->next_number_field) - table->auto_increment_field_not_null= TRUE; if ((length=(uint) (read_info.row_end-pos)) > field->field_length) length=field->field_length; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c75aff7fab6..8b3028f5370 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4058,7 +4058,9 @@ copy_data_between_tables(TABLE *from,TABLE *to, { copy_ptr->do_copy(copy_ptr); } - if ((error=to->file->write_row((byte*) to->record[0]))) + error=to->file->write_row((byte*) to->record[0]); + to->auto_increment_field_not_null= FALSE; + if (error) { if (!ignore || (error != HA_ERR_FOUND_DUPP_KEY && diff --git a/sql/table.h b/sql/table.h index e2bd5ba0a7d..b795fa78e51 100644 --- a/sql/table.h +++ b/sql/table.h @@ -273,6 +273,11 @@ struct st_table { my_bool no_cache; /* To signal that we should reset query_id for tables and cols */ my_bool clear_query_id; + /* + To indicate that a non-null value of the auto_increment field + was provided by the user or retrieved from the current record. + Used only in the MODE_NO_AUTO_VALUE_ON_ZERO mode. + */ my_bool auto_increment_field_not_null; my_bool insert_or_update; /* Can be used by the handler */ my_bool alias_name_used; /* true if table_name is alias */