diff --git a/innobase/data/data0type.c b/innobase/data/data0type.c index 97d93b1b0ec..714cf92bc65 100644 --- a/innobase/data/data0type.c +++ b/innobase/data/data0type.c @@ -12,6 +12,27 @@ Created 1/16/1996 Heikki Tuuri #include "data0type.ic" #endif +/********************************************************************** +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. + +NOTE: the prototype of this function is copied from ha_innodb.cc! If you change +this function, you MUST change also the prototype here! */ + +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + /* out: number of bytes occupied by the first + n characters */ + ulint charset_id, /* in: character set id */ + ulint prefix_len, /* in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /* in: length of the string in bytes */ + const char* str); /* in: character string */ + /* At the database startup we store the default-charset collation number of this MySQL installation to this global variable. If we have < 4.1.2 format column definitions, or records in the insert buffer, we use this @@ -23,6 +44,63 @@ ulint data_mysql_latin1_swedish_charset_coll = 99999999; dtype_t dtype_binary_val = {DATA_BINARY, 0, 0, 0}; dtype_t* dtype_binary = &dtype_binary_val; +/************************************************************************* +Checks if a string type has to be compared by the MySQL comparison functions. +InnoDB internally only handles binary byte string comparisons, as well as +latin1_swedish_ci strings. For example, UTF-8 strings have to be compared +by MySQL. */ + +ibool +dtype_str_needs_mysql_cmp( +/*======================*/ + /* out: TRUE if a string type that requires + comparison with MySQL functions */ + dtype_t* dtype) /* in: type struct */ +{ + if (dtype->mtype == DATA_MYSQL + || dtype->mtype == DATA_VARMYSQL + || (dtype->mtype == DATA_BLOB + && 0 == (dtype->prtype & DATA_BINARY_TYPE) + && dtype_get_charset_coll(dtype->prtype) != + data_mysql_latin1_swedish_charset_coll)) { + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +For the documentation of this function, see innobase_get_at_most_n_mbchars() +in ha_innodb.cc. */ + +ulint +dtype_get_at_most_n_mbchars( +/*========================*/ + dtype_t* dtype, + ulint prefix_len, + ulint data_len, + const char* str) +{ + ut_a(data_len != UNIV_SQL_NULL); + + if (dtype_str_needs_mysql_cmp(dtype)) { + return(innobase_get_at_most_n_mbchars( + dtype_get_charset_coll(dtype->prtype), + prefix_len, data_len, str)); + } + + /* We assume here that the string types that InnoDB itself can compare + are single-byte charsets! */ + + if (prefix_len < data_len) { + + return(prefix_len); + + } + + return(data_len); +} + /************************************************************************* Checks if a data main type is a string type. Also a BLOB is considered a string type. */ diff --git a/innobase/include/data0type.h b/innobase/include/data0type.h index fe38a224a66..c263d2bf613 100644 --- a/innobase/include/data0type.h +++ b/innobase/include/data0type.h @@ -144,6 +144,29 @@ SQL null*/ store the charset-collation number; one byte is left unused, though */ #define DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE 6 +/************************************************************************* +Checks if a string type has to be compared by the MySQL comparison functions. +InnoDB internally only handles binary byte string comparisons, as well as +latin1_swedish_ci strings. For example, UTF-8 strings have to be compared +by MySQL. */ + +ibool +dtype_str_needs_mysql_cmp( +/*======================*/ + /* out: TRUE if a string type that requires + comparison with MySQL functions */ + dtype_t* dtype); /* in: type struct */ +/************************************************************************* +For the documentation of this function, see innobase_get_at_most_n_mbchars() +in ha_innodb.cc. */ + +ulint +dtype_get_at_most_n_mbchars( +/*========================*/ + dtype_t* dtype, + ulint prefix_len, + ulint data_len, + const char* str); /************************************************************************* Checks if a data main type is a string type. Also a BLOB is considered a string type. */ diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c index c5d90524fdd..7b0beb9d183 100644 --- a/innobase/row/row0ins.c +++ b/innobase/row/row0ins.c @@ -2019,16 +2019,12 @@ row_ins_index_entry_set_vals( if (ind_field->prefix_len > 0 && dfield_get_len(row_field) != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ - cur_type = dict_col_get_type( dict_field_get_col(ind_field)); - field->len = innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), - ind_field->prefix_len, - dfield_get_len(row_field),row_field->data); + field->len = dtype_get_at_most_n_mbchars(cur_type, + ind_field->prefix_len, + dfield_get_len(row_field), row_field->data); } else { field->len = row_field->len; } diff --git a/innobase/row/row0row.c b/innobase/row/row0row.c index a02fbe67f73..e7b39f0fe52 100644 --- a/innobase/row/row0row.c +++ b/innobase/row/row0row.c @@ -143,18 +143,15 @@ row_build_index_entry( if (ind_field->prefix_len > 0 && dfield_get_len(dfield2) != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ - cur_type = dict_col_get_type( dict_field_get_col(ind_field)); - storage_len = innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), + storage_len = dtype_get_at_most_n_mbchars( + cur_type, ind_field->prefix_len, - dfield_get_len(dfield2),dfield2->data); + dfield_get_len(dfield2), dfield2->data); - dfield_set_len(dfield,storage_len); + dfield_set_len(dfield, storage_len); } } @@ -497,16 +494,13 @@ row_build_row_ref_from_row( if (field->prefix_len > 0 && dfield->len != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ - cur_type = dict_col_get_type( dict_field_get_col(field)); - dfield->len = innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), + dfield->len = dtype_get_at_most_n_mbchars( + cur_type, field->prefix_len, - dfield->len,dfield->data); + dfield->len, dfield->data); } } diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c index 8e011047dad..16c0a1eaa32 100644 --- a/innobase/row/row0sel.c +++ b/innobase/row/row0sel.c @@ -94,16 +94,13 @@ row_sel_sec_rec_is_for_clust_rec( if (ifield->prefix_len > 0 && clust_len != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ - cur_type = dict_col_get_type( dict_field_get_col(ifield)); - clust_len = innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), + clust_len = dtype_get_at_most_n_mbchars( + cur_type, ifield->prefix_len, - clust_len,clust_field); + clust_len, clust_field); } if (0 != cmp_data_data(dict_col_get_type(col), diff --git a/innobase/row/row0upd.c b/innobase/row/row0upd.c index 34c45204167..a449b9f1736 100644 --- a/innobase/row/row0upd.c +++ b/innobase/row/row0upd.c @@ -876,17 +876,15 @@ row_upd_index_replace_new_col_vals_index_pos( if (field->prefix_len > 0 && new_val->len != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ + cur_type = dict_col_get_type( + dict_field_get_col(field)); - cur_type = dict_col_get_type( - dict_field_get_col(field)); - - dfield->len = - innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), - field->prefix_len, - new_val->len,new_val->data); + dfield->len = + dtype_get_at_most_n_mbchars( + cur_type, + field->prefix_len, + new_val->len, + new_val->data); } } } @@ -948,17 +946,15 @@ row_upd_index_replace_new_col_vals( if (field->prefix_len > 0 && new_val->len != UNIV_SQL_NULL) { - /* For prefix keys get the storage length - for the prefix_len characters. */ + cur_type = dict_col_get_type( + dict_field_get_col(field)); - cur_type = dict_col_get_type( - dict_field_get_col(field)); - - dfield->len = - innobase_get_at_most_n_mbchars( - dtype_get_charset_coll(cur_type->prtype), - field->prefix_len, - new_val->len,new_val->data); + dfield->len = + dtype_get_at_most_n_mbchars( + cur_type, + field->prefix_len, + new_val->len, + new_val->data); } } } diff --git a/mysql-test/r/type_timestamp.result b/mysql-test/r/type_timestamp.result index 425e4a05586..a823049634f 100644 --- a/mysql-test/r/type_timestamp.result +++ b/mysql-test/r/type_timestamp.result @@ -365,6 +365,35 @@ select * from t1; t1 i 2004-04-01 00:00:00 10 drop table t1; +create table t1 (a timestamp null, b timestamp null); +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` timestamp NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `b` timestamp NULL default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert into t1 values (NULL, NULL); +SET TIMESTAMP=1000000017; +insert into t1 values (); +select * from t1; +a b +NULL NULL +2001-09-09 04:46:57 NULL +drop table t1; +create table t1 (a timestamp null default null, b timestamp null default '2003-01-01 00:00:00'); +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` timestamp NULL default NULL, + `b` timestamp NULL default '2003-01-01 00:00:00' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert into t1 values (NULL, NULL); +insert into t1 values (DEFAULT, DEFAULT); +select * from t1; +a b +NULL NULL +NULL 2003-01-01 00:00:00 +drop table t1; create table t1 (ts timestamp(19)); show create table t1; Table Create Table diff --git a/mysql-test/t/client_test.test b/mysql-test/t/client_test.test index 50230d1dd16..9183740cd02 100644 --- a/mysql-test/t/client_test.test +++ b/mysql-test/t/client_test.test @@ -1,2 +1,2 @@ -- disable_result_log ---exec $TESTS_BINDIR/client_test --testcase --user=root --socket=var/tmp/master.sock --port=$MYSQL_TCP_PORT +-- // exec $TESTS_BINDIR/client_test --testcase --user=root --socket=$MASTER_MYSOCK --port=$MYSQL_TCP_PORT diff --git a/mysql-test/t/type_timestamp.test b/mysql-test/t/type_timestamp.test index a644197f757..7c1258785b0 100644 --- a/mysql-test/t/type_timestamp.test +++ b/mysql-test/t/type_timestamp.test @@ -234,7 +234,27 @@ alter table t1 add i int default 10; select * from t1; drop table t1; +# +# Test for TIMESTAMP columns which are able to store NULLs +# (Auto-set property should work for them and NULL values +# should be OK as default values) +# +create table t1 (a timestamp null, b timestamp null); +show create table t1; +insert into t1 values (NULL, NULL); +SET TIMESTAMP=1000000017; +insert into t1 values (); +select * from t1; +drop table t1; +create table t1 (a timestamp null default null, b timestamp null default '2003-01-01 00:00:00'); +show create table t1; +insert into t1 values (NULL, NULL); +insert into t1 values (DEFAULT, DEFAULT); +select * from t1; +drop table t1; + +# # Test for bug #4491, TIMESTAMP(19) should be possible to create and not # only read in 4.0 # diff --git a/sql/field.cc b/sql/field.cc index a3a19d93e58..eaa1ca2bcca 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2932,11 +2932,12 @@ void Field_double::sql_type(String &res) const */ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs) - :Field_str(ptr_arg, 19, (uchar*) 0,0, + :Field_str(ptr_arg, 19, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg, cs) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ @@ -2952,23 +2953,33 @@ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, /* - Sets TABLE::timestamp_default_now and TABLE::timestamp_on_update_now - members according to unireg type of this TIMESTAMP field. - - SYNOPSIS - Field_timestamp::set_timestamp_offsets() - -*/ -void Field_timestamp::set_timestamp_offsets() -{ - ulong timestamp= (ulong) (ptr - (char*) table->record[0]) + 1; - - DBUG_ASSERT(table->timestamp_field == this && unireg_check != NONE); + Get auto-set type for TIMESTAMP field. - table->timestamp_default_now= - (unireg_check == TIMESTAMP_UN_FIELD)? 0 : timestamp; - table->timestamp_on_update_now= - (unireg_check == TIMESTAMP_DN_FIELD)? 0 : timestamp; + SYNOPSIS + get_auto_set_type() + + DESCRIPTION + Returns value indicating during which operations this TIMESTAMP field + should be auto-set to current timestamp. +*/ +timestamp_auto_set_type Field_timestamp::get_auto_set_type() const +{ + switch (unireg_check) + { + case TIMESTAMP_DN_FIELD: + return TIMESTAMP_AUTO_SET_ON_INSERT; + case TIMESTAMP_UN_FIELD: + return TIMESTAMP_AUTO_SET_ON_UPDATE; + case TIMESTAMP_DNUN_FIELD: + return TIMESTAMP_AUTO_SET_ON_BOTH; + default: + /* + Normally this function should not be called for TIMESTAMPs without + auto-set property. + */ + DBUG_ASSERT(0); + return TIMESTAMP_NO_AUTO_SET; + } } @@ -3267,6 +3278,7 @@ void Field_timestamp::sql_type(String &res) const void Field_timestamp::set_time() { long tmp= (long) table->in_use->query_start(); + set_notnull(); #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -5985,8 +5997,9 @@ Field *make_field(char *ptr, uint32 field_length, f_is_zerofill(pack_flag) != 0, f_is_dec(pack_flag) == 0); case FIELD_TYPE_TIMESTAMP: - return new Field_timestamp(ptr,field_length, - unireg_check, field_name, table, field_charset); + return new Field_timestamp(ptr,field_length, null_pos, null_bit, + unireg_check, field_name, table, + field_charset); case FIELD_TYPE_YEAR: return new Field_year(ptr,field_length,null_pos,null_bit, unireg_check, field_name, table); diff --git a/sql/field.h b/sql/field.h index 3d22c6904a7..69410f4e6af 100644 --- a/sql/field.h +++ b/sql/field.h @@ -683,6 +683,7 @@ public: class Field_timestamp :public Field_str { public: Field_timestamp(char *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs); @@ -712,8 +713,11 @@ public: else Field::set_default(); } - inline long get_timestamp() + /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */ + inline long get_timestamp(my_bool *null_value) { + if ((*null_value= is_null())) + return 0; #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) return sint4korr(ptr); @@ -725,7 +729,7 @@ public: bool get_date(TIME *ltime,uint fuzzydate); bool get_time(TIME *ltime); field_cast_enum field_cast_type() { return FIELD_CAST_TIMESTAMP; } - void set_timestamp_offsets(); + timestamp_auto_set_type get_auto_set_type() const; }; diff --git a/sql/field_conv.cc b/sql/field_conv.cc index d7993939092..c9b21b5f96f 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -164,7 +164,8 @@ set_field_to_null_with_conversions(Field *field, bool no_conversions) /* Check if this is a special type, which will get a special walue - when set to NULL + when set to NULL (TIMESTAMP fields which allow setting to NULL + are handled by first check). */ if (field->type() == FIELD_TYPE_TIMESTAMP) { diff --git a/sql/ha_berkeley.cc b/sql/ha_berkeley.cc index 32f623b86c9..c688f3c3597 100644 --- a/sql/ha_berkeley.cc +++ b/sql/ha_berkeley.cc @@ -856,8 +856,8 @@ int ha_berkeley::write_row(byte * record) DBUG_ENTER("write_row"); statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && record == table->record[0]) update_auto_increment(); if ((error=pack_row(&row, record,1))) @@ -1103,8 +1103,8 @@ int ha_berkeley::update_row(const byte * old_row, byte * new_row) LINT_INIT(error); statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_row+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); if (hidden_primary_key) { diff --git a/sql/ha_heap.cc b/sql/ha_heap.cc index d7327362286..5be51ec8494 100644 --- a/sql/ha_heap.cc +++ b/sql/ha_heap.cc @@ -87,8 +87,8 @@ void ha_heap::set_keys_for_scanning(void) int ha_heap::write_row(byte * buf) { statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return heap_write(file,buf); @@ -97,8 +97,8 @@ int ha_heap::write_row(byte * buf) int ha_heap::update_row(const byte * old_data, byte * new_data) { statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return heap_update(file,old_data,new_data); } diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index de0ba0d31ee..132bb835d82 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -2240,8 +2240,8 @@ ha_innobase::write_row( statistic_increment(ha_write_count, &LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record + table->timestamp_default_now - 1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; @@ -2612,8 +2612,8 @@ ha_innobase::update_row( ut_ad(prebuilt->trx == (trx_t*) current_thd->transaction.all.innobase_tid); - if (table->timestamp_on_update_now) - update_timestamp(new_row + table->timestamp_on_update_now - 1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; @@ -5258,8 +5258,7 @@ innobase_store_binlog_offset_and_flush_log( /*=============================*/ char *binlog_name, /* in: binlog name */ longlong offset /* in: binlog offset */ -) -{ +) { mtr_t mtr; assert(binlog_name != NULL); @@ -5298,50 +5297,84 @@ ulonglong ha_innobase::get_mysql_bin_log_pos() extern "C" { /********************************************************************** -This function is used to find storage length of prefix_len characters -in bytes for prefix indexes using multibyte character set. -Function finds charset information and returns length of -prefix_len characters in the index field in bytes. */ +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. -ulint innobase_get_at_most_n_mbchars( -/*=================================*/ +NOTE: the prototype of this function is copied to data0type.c! If you change +this function, you MUST change also data0type.c! */ + +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + /* out: number of bytes occupied by the first + n characters */ ulint charset_id, /* in: character set id */ - ulint prefix_len, /* in: prefix length of the index */ - ulint data_len, /* in: length of the sting in bytes */ - const char *pos) /* in: character string */ + ulint prefix_len, /* in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /* in: length of the string in bytes */ + const char* str) /* in: character string */ { - ulint byte_length; /* storage length, in bytes. */ ulint char_length; /* character length in bytes */ + ulint n_chars; /* number of characters in prefix */ CHARSET_INFO* charset; /* charset used in the field */ - ut_ad(pos); - byte_length = data_len; - - charset = get_charset(charset_id,MYF(MY_WME)); + charset = get_charset(charset_id, MYF(MY_WME)); ut_ad(charset); ut_ad(charset->mbmaxlen); - /* Calculate the storage length of the one character in bytes and - how many characters the prefix index contains */ + /* Calculate how many characters at most the prefix index contains */ - char_length = byte_length / charset->mbmaxlen; - prefix_len = prefix_len / charset->mbmaxlen; + n_chars = prefix_len / charset->mbmaxlen; - /* If length of the string is greater than storage length of the - one character, we have to find the storage position of the - prefix_len character in the string */ + /* If the charset is multi-byte, then we must find the length of the + first at most n chars in the string. If the string contains less + characters than n, then we return the length to the end of the last + full character. */ - if (byte_length > char_length) { - char_length = my_charpos(charset, pos, - pos + byte_length, prefix_len); - set_if_smaller(char_length, byte_length); - } - else { - char_length = prefix_len; + if (charset->mbmaxlen > 1) { +/* ulint right_value; */ + + /* my_charpos() returns the byte length of the first n_chars + characters, or the end of the last full character */ + + char_length = my_charpos(charset, str, + str + data_len, n_chars); + + /*################################################*/ + /* TODO: my_charpos sometimes returns a non-sensical value + that is BIGGER than data_len: try to fix this bug partly with + these heuristics. This is NOT a complete bug fix! */ + + if (char_length > data_len) { + char_length = data_len; + } + /*################################################*/ + +/* printf("data_len %lu, n_chars %lu, char_len %lu\n", + data_len, n_chars, char_length); + if (data_len < n_chars) { + right_value = data_len; + } else { + right_value = n_chars; + } + + if (right_value != char_length) { + printf("ERRRRRROOORRRRRRRRRRRR!!!!!!!!!\n"); + } +*/ + } else { + if (data_len < prefix_len) { + char_length = data_len; + } else { + char_length = prefix_len; + } } - return char_length; + return(char_length); } } diff --git a/sql/ha_isam.cc b/sql/ha_isam.cc index 85ab25a31d9..9de532fa7b0 100644 --- a/sql/ha_isam.cc +++ b/sql/ha_isam.cc @@ -70,8 +70,8 @@ uint ha_isam::min_record_length(uint options) const int ha_isam::write_row(byte * buf) { statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return !nisam_write(file,buf) ? 0 : my_errno ? my_errno : -1; @@ -80,8 +80,8 @@ int ha_isam::write_row(byte * buf) int ha_isam::update_row(const byte * old_data, byte * new_data) { statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return !nisam_update(file,old_data,new_data) ? 0 : my_errno ? my_errno : -1; } diff --git a/sql/ha_isammrg.cc b/sql/ha_isammrg.cc index 20e2b4db423..367607eef19 100644 --- a/sql/ha_isammrg.cc +++ b/sql/ha_isammrg.cc @@ -78,8 +78,8 @@ int ha_isammrg::write_row(byte * buf) int ha_isammrg::update_row(const byte * old_data, byte * new_data) { statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return !mrg_update(file,old_data,new_data) ? 0 : my_errno ? my_errno : -1; } diff --git a/sql/ha_myisam.cc b/sql/ha_myisam.cc index 95a294764d3..729ec4c27eb 100644 --- a/sql/ha_myisam.cc +++ b/sql/ha_myisam.cc @@ -251,8 +251,8 @@ int ha_myisam::write_row(byte * buf) statistic_increment(ha_write_count,&LOCK_status); /* If we have a timestamp column, update it to the current time */ - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); /* If we have an auto_increment column and we are writing a changed row @@ -1070,8 +1070,8 @@ bool ha_myisam::is_crashed() const int ha_myisam::update_row(const byte * old_data, byte * new_data) { statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return mi_update(file,old_data,new_data); } diff --git a/sql/ha_myisammrg.cc b/sql/ha_myisammrg.cc index 9aa6d039efb..bf4c2a36ffd 100644 --- a/sql/ha_myisammrg.cc +++ b/sql/ha_myisammrg.cc @@ -82,8 +82,8 @@ int ha_myisammrg::close(void) int ha_myisammrg::write_row(byte * buf) { statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return myrg_write(file,buf); @@ -92,8 +92,8 @@ int ha_myisammrg::write_row(byte * buf) int ha_myisammrg::update_row(const byte * old_data, byte * new_data) { statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return myrg_update(file,old_data,new_data); } diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 10f4e98c97e..d4ab6f5d4c1 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -1559,8 +1559,8 @@ int ha_ndbcluster::write_row(byte *record) } statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); has_auto_increment= (table->next_number_field && record == table->record[0]); if (!(op= trans->getNdbOperation((const NDBTAB *) m_table))) @@ -1709,9 +1709,9 @@ int ha_ndbcluster::update_row(const byte *old_data, byte *new_data) DBUG_ENTER("update_row"); statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); - + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + /* Check for update of primary key for special handling */ if ((table->primary_key != MAX_KEY) && (key_cmp(table->primary_key, old_data, new_data))) diff --git a/sql/handler.cc b/sql/handler.cc index 859c7124566..da911ad9172 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -942,22 +942,6 @@ int handler::read_first_row(byte * buf, uint primary_key) } -/* Set a timestamp in record */ - -void handler::update_timestamp(byte *record) -{ - long skr= (long) current_thd->query_start(); -#ifdef WORDS_BIGENDIAN - if (table->db_low_byte_first) - { - int4store(record,skr); - } - else -#endif - longstore(record,skr); - return; -} - /* Updates field with field_type NEXT_NUMBER according to following: if field = 0 change field to the next free key in database. diff --git a/sql/handler.h b/sql/handler.h index 0b7e9c04381..443b43a5ea3 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -287,7 +287,6 @@ public: {} virtual ~handler(void) { /* TODO: DBUG_ASSERT(inited == NONE); */ } int ha_open(const char *name, int mode, int test_if_locked); - void update_timestamp(byte *record); void update_auto_increment(); virtual void print_error(int error, myf errflag); virtual bool get_error_message(int error, String *buf); diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 8f09fe82c1b..863b041044e 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -989,7 +989,7 @@ longlong Item_func_unix_timestamp::val_int() { // Optimize timestamp field Field *field=((Item_field*) args[0])->field; if (field->type() == FIELD_TYPE_TIMESTAMP) - return ((Field_timestamp*) field)->get_timestamp(); + return ((Field_timestamp*) field)->get_timestamp(&null_value); } if (get_arg0_date(<ime, 0)) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7464523aad4..65ac38cdd6e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -945,7 +945,7 @@ TABLE *open_table(THD *thd,const char *db,const char *table_name, table->keys_in_use_for_query= table->keys_in_use; table->used_keys= table->keys_for_keyread; if (table->timestamp_field) - table->timestamp_field->set_timestamp_offsets(); + table->timestamp_field_type= table->timestamp_field->get_auto_set_type(); DBUG_ASSERT(table->key_read == 0); DBUG_RETURN(table); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 4cbd11c6a15..a0496a04bb2 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -45,8 +45,8 @@ static void unlink_blobs(register TABLE *table); /* Check if insert fields are correct. - Sets table->timestamp_default_now/on_update_now to 0 o leaves it to point - to timestamp field, depending on if timestamp should be updated or not. + Sets table->timestamp_field_type to TIMESTAMP_NO_AUTO_SET or leaves it + as is, depending on if timestamp should be updated or not. */ int @@ -67,7 +67,7 @@ check_insert_fields(THD *thd,TABLE *table,List &fields, check_grant_all_columns(thd,INSERT_ACL,table)) return -1; #endif - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; } else { // Part field list @@ -97,7 +97,7 @@ check_insert_fields(THD *thd,TABLE *table,List &fields, } if (table->timestamp_field && // Don't set timestamp if used table->timestamp_field->query_id == thd->query_id) - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; } // For the values we need select_priv #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -569,7 +569,8 @@ int write_record(TABLE *table,COPY_INFO *info) */ if (last_uniq_key(table,key_nr) && !table->file->referenced_by_foreign_key() && - table->timestamp_default_now == table->timestamp_on_update_now) + (table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET || + table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH)) { if ((error=table->file->update_row(table->record[1], table->record[0]))) @@ -645,8 +646,7 @@ public: bool query_start_used,last_insert_id_used,insert_id_used; int log_query; ulonglong last_insert_id; - ulong timestamp_default_now; - ulong timestamp_on_update_now; + timestamp_auto_set_type timestamp_field_type; uint query_length; delayed_row(enum_duplicates dup_arg, int log_query_arg) @@ -940,7 +940,7 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) copy->timestamp_field= (Field_timestamp*) copy->field[table->timestamp_field_offset]; copy->timestamp_field->unireg_check= table->timestamp_field->unireg_check; - copy->timestamp_field->set_timestamp_offsets(); + copy->timestamp_field_type= copy->timestamp_field->get_auto_set_type(); } /* _rowid is not used with delayed insert */ @@ -995,8 +995,7 @@ static int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, row->last_insert_id_used= thd->last_insert_id_used; row->insert_id_used= thd->insert_id_used; row->last_insert_id= thd->last_insert_id; - row->timestamp_default_now= table->timestamp_default_now; - row->timestamp_on_update_now= table->timestamp_on_update_now; + row->timestamp_field_type= table->timestamp_field_type; di->rows.push_back(row); di->stacked_inserts++; @@ -1335,8 +1334,7 @@ bool delayed_insert::handle_inserts(void) thd.last_insert_id=row->last_insert_id; thd.last_insert_id_used=row->last_insert_id_used; thd.insert_id_used=row->insert_id_used; - table->timestamp_default_now= row->timestamp_default_now; - table->timestamp_on_update_now= row->timestamp_on_update_now; + table->timestamp_field_type= row->timestamp_field_type; info.handle_duplicates= row->dup; if (info.handle_duplicates == DUP_IGNORE || @@ -1631,7 +1629,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) field=table->field+table->fields - values.elements; /* Don't set timestamp if used */ - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; table->next_number_field=table->found_next_number_field; diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 78d89ef7aa9..17ab472c87b 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -264,7 +264,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (!(error=test(read_info.error))) { if (use_timestamp) - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; table->next_number_field=table->found_next_number_field; if (handle_duplicates == DUP_IGNORE || diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2c5ec34b867..e8441c05609 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4142,7 +4142,12 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, } else if (default_value->type() == Item::NULL_ITEM) { - default_value=0; + /* + TIMESTAMP type should be able to distingush non-specified default + value and default value NULL later. + */ + if (type != FIELD_TYPE_TIMESTAMP) + default_value= 0; if ((type_modifier & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) { @@ -4334,7 +4339,7 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, new_field->length=((new_field->length+1)/2)*2; /* purecov: inspected */ new_field->length= min(new_field->length,14); /* purecov: inspected */ } - new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG | NOT_NULL_FLAG; + new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG; if (default_value) { /* Grammar allows only NOW() value for ON UPDATE clause */ @@ -4352,6 +4357,9 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, else new_field->unireg_check= (on_update_value?Field::TIMESTAMP_UN_FIELD: Field::NONE); + + if (default_value->type() == Item::NULL_ITEM) + new_field->def= 0; } else { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 6a380664cb7..714fb0a2efe 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -710,10 +710,11 @@ mysqld_show_fields(THD *thd, TABLE_LIST *table_list,const char *wild, protocol->store(field->has_charset() ? field->charset()->name : "NULL", system_charset_info); /* - Altough TIMESTAMP fields can't contain NULL as its value they + Even if TIMESTAMP field can't contain NULL as its value it will accept NULL if you will try to insert such value and will - convert it to current TIMESTAMP. So YES here means that NULL - is allowed for assignment but can't be returned. + convert NULL value to current TIMESTAMP. So YES here means + that NULL is allowed for assignment (but may be won't be + returned). */ pos=(byte*) ((flags & NOT_NULL_FLAG) && field->type() != FIELD_TYPE_TIMESTAMP ? @@ -1285,7 +1286,14 @@ store_create_info(THD *thd, TABLE *table, String *packet) if (flags & NOT_NULL_FLAG) packet->append(" NOT NULL", 9); - + else if (field->type() == FIELD_TYPE_TIMESTAMP) + { + /* + TIMESTAMP field require explicit NULL flag, because unlike + all other fields they are treated as NOT NULL by default. + */ + packet->append(" NULL", 5); + } /* Again we are using CURRENT_TIMESTAMP instead of NOW because it is diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 6d7a4ff269d..113f71236bf 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3053,12 +3053,8 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } - /* - We don't want update TIMESTAMP fields during ALTER TABLE - and copy_data_between_tables uses only write_row() for new_table so - don't need to set up timestamp_on_update_now member. - */ - new_table->timestamp_default_now= 0; + /* We don't want update TIMESTAMP fields during ALTER TABLE. */ + new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields thd->cuted_fields=0L; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index b6cd0d967e9..c6fb3d6e415 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -116,7 +116,7 @@ int mysql_update(THD *thd, { // Don't set timestamp column if this is modified if (table->timestamp_field->query_id == thd->query_id) - table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; else table->timestamp_field->query_id=timestamp_query_id; } @@ -526,7 +526,7 @@ int mysql_multi_update(THD *thd, // Only set timestamp column if this is not modified if (table->timestamp_field && table->timestamp_field->query_id == thd->query_id) - table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; /* if table will be updated then check that it is unique */ if (table->map & item_tables) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5bf5140d0d8..0c81c172cf7 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1415,10 +1415,21 @@ type: if (YYTHD->variables.sql_mode & MODE_MAXDB) $$=FIELD_TYPE_DATETIME; else + { + /* + Unlike other types TIMESTAMP fields are NOT NULL by default. + */ + Lex->type|= NOT_NULL_FLAG; $$=FIELD_TYPE_TIMESTAMP; + } } - | TIMESTAMP '(' NUM ')' { Lex->length=$3.str; - $$=FIELD_TYPE_TIMESTAMP; } + | TIMESTAMP '(' NUM ')' + { + LEX *lex= Lex; + lex->length= $3.str; + lex->type|= NOT_NULL_FLAG; + $$= FIELD_TYPE_TIMESTAMP; + } | DATETIME { $$=FIELD_TYPE_DATETIME; } | TINYBLOB { Lex->charset=&my_charset_bin; $$=FIELD_TYPE_TINY_BLOB; } diff --git a/sql/table.h b/sql/table.h index f111377bc85..904038ad029 100644 --- a/sql/table.h +++ b/sql/table.h @@ -57,6 +57,16 @@ typedef struct st_filesort_info } FILESORT_INFO; +/* + Values in this enum are used to indicate during which operations value + of TIMESTAMP field should be set to current timestamp. +*/ +enum timestamp_auto_set_type +{ + TIMESTAMP_NO_AUTO_SET= 0, TIMESTAMP_AUTO_SET_ON_INSERT= 1, + TIMESTAMP_AUTO_SET_ON_UPDATE= 2, TIMESTAMP_AUTO_SET_ON_BOTH= 3 +}; + /* Table cache entry struct */ class Field_timestamp; @@ -99,16 +109,19 @@ struct st_table { uint status; /* Used by postfix.. */ uint system; /* Set if system record */ - /* - These two members hold offset in record + 1 for TIMESTAMP field - with NOW() as default value or/and with ON UPDATE NOW() option. - If 0 then such field is absent in this table or auto-set for default - or/and on update should be temporaly disabled for some reason. - These values is setup to offset value for each statement in open_table() - and turned off in statement processing code (see mysql_update as example). + /* + If this table has TIMESTAMP field with auto-set property (pointed by + timestamp_field member) then this variable indicates during which + operations (insert only/on update/in both cases) we should set this + field to current timestamp. If there are no such field in this table + or we should not automatically set its value during execution of current + statement then the variable contains TIMESTAMP_NO_AUTO_SET (i.e. 0). + + Value of this variable is set for each statement in open_table() and + if needed cleared later in statement processing code (see mysql_update() + as example). */ - ulong timestamp_default_now; - ulong timestamp_on_update_now; + timestamp_auto_set_type timestamp_field_type; /* Index of auto-updated TIMESTAMP field in field array */ uint timestamp_field_offset;