Re: [Maria-developers] Please review: MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value

2015-06-25 Thread Alexander Barkov

Hi Sergei,

This is a new version.

Please also see some comments below.

Thanks.


On 06/25/2015 01:00 PM, Sergei Golubchik wrote:

Hi, Alexander!

On Jun 25, Alexander Barkov wrote:

Hi Sergei,


diff --git a/sql/field_conv.cc b/sql/field_conv.cc
index e31f7c5..14f2947 100644
--- a/sql/field_conv.cc
+++ b/sql/field_conv.cc
@@ -859,7 +859,8 @@ bool memcpy_field_possible(Field *to,Field *from)
 from-charset() == to-charset() 
 (!sql_mode_for_dates(to-table-in_use) ||
  (from-type()!= MYSQL_TYPE_DATE 
-from-type()!= MYSQL_TYPE_DATETIME)) 
+from-type()!= MYSQL_TYPE_DATETIME 
+from-type()!= MYSQL_TYPE_TIMESTAMP)) 


what is this for - to apply TIME_NO_ZERO_DATE to timestamps?


Right, this is for CREATE TABLE AS SELECT, like in here:

set sql_mode=default;
drop table if exists t1;
create table t1 (a timestamp);
insert into t1 values (0);
set sql_mode='TRADITIONAL';
drop table if exists t2;
create table t2 as select * from t1;

The last statement should fail. It does not fail before the patch.

This is not strictly DEFAULT. But a very related thing,
when a wrong value sneaks in going around Field_xxx::store().

Would you mind if I have this change in this patch?


I created a separate MDEV-8373 for CREATE AS SELECT.



better not.

it's trivial to put it in a separate commit with git citool
if it'd be difficult to extract it - then yes, but mention is
explicitly in the commit comment (also fixes the case).



I am adding tests for both bugs:

MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode 
through a default value


MDEV-8373 Zero date can be inserted in strict no-zero mode through 
CREATE TABLE AS SELECT timestamp_field


into a new shared file mysql-test/include/type_temporal_zero_default.inc


Does git citool support partial commit in such case?
I.e. if I want to add a part of a new file in the first commit, and then 
add the rest of the new file in the second commit?







diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
index 0846740..d79fbcc 100644
--- a/sql/share/errmsg-utf8.txt
+++ b/sql/share/errmsg-utf8.txt
@@ -7127,3 +7127,5 @@ ER_ROLE_DROP_EXISTS
   eng Can't drop role '%-.64s'; it doesn't exist
   ER_CANNOT_CONVERT_CHARACTER
   eng Cannot convert '%s' character 0x%-.64s to '%s'
+ER_INVALID_DEFAULT_VALUE_FOR_FIELD  22007
+eng Incorrect default value '%-.128s' for column '%.192s'



Do you agree with a new error?


yes, okay.

Regards,
Sergei

diff --git a/mysql-test/include/type_temporal_zero_default.inc b/mysql-test/include/type_temporal_zero_default.inc
new file mode 100644
index 000..500d25e
--- /dev/null
+++ b/mysql-test/include/type_temporal_zero_default.inc
@@ -0,0 +1,75 @@
+--echo #
+--echo # MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value
+--echo #
+
+# Testing direct INSERT
+
+SET sql_mode=DEFAULT;
+eval CREATE TABLE t1 (a $type DEFAULT $defval);
+SET sql_mode=TRADITIONAL;
+--error ER_TRUNCATED_WRONG_VALUE
+eval INSERT INTO t1 VALUES ($defval);
+--error ER_INVALID_DEFAULT_VALUE_FOR_FIELD
+INSERT INTO t1 VALUES ();
+--error ER_INVALID_DEFAULT_VALUE_FOR_FIELD
+INSERT INTO t1 VALUES (DEFAULT);
+DROP TABLE t1;
+SET sql_mode=DEFAULT;
+
+
+# Testing INSERT .. SELECT
+
+eval CREATE TABLE t1 (a $type NOT NULL DEFAULT $defval, b $type NOT NULL DEFAULT $defval);
+eval CREATE TABLE t2 (a $type NOT NULL DEFAULT $defval);
+eval INSERT INTO t2 VALUES ($defval);
+SET sql_mode=TRADITIONAL;
+--error ER_INVALID_DEFAULT_VALUE_FOR_FIELD
+INSERT INTO t1 (a) SELECT a FROM t2;
+DROP TABLE t1, t2;
+SET sql_mode=DEFAULT;
+
+
+# Testing LOAD
+
+--eval CREATE TABLE t1 (a $type DEFAULT $defval, b $type DEFAULT $defval)
+--eval INSERT INTO t1 VALUES (DEFAULT,DEFAULT);
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--eval SELECT a INTO OUTFILE '$MYSQLTEST_VARDIR/tmp/mdev-7824.txt' FROM t1
+DELETE FROM t1;
+SET sql_mode=TRADITIONAL;
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_INVALID_DEFAULT_VALUE_FOR_FIELD
+--eval LOAD DATA INFILE '$MYSQLTEST_VARDIR/tmp/mdev-7824.txt' INTO TABLE t1 (a)
+--remove_file $MYSQLTEST_VARDIR/tmp/mdev-7824.txt
+DROP TABLE t1;
+SET sql_mode=DEFAULT;
+
+# Testing ALTER when an old field default becomes invalid
+# Return an error, even if there is no STRICT_XXX_TABLES set
+--eval CREATE TABLE t1 (a $type DEFAULT $defval);
+SET sql_mode='NO_ZERO_DATE';
+--error ER_INVALID_DEFAULT
+ALTER TABLE t1 ADD b INT NOT NULL;
+DROP TABLE t1;
+SET sql_mode=DEFAULT;
+
+
+--echo #
+--echo # End of MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value
+--echo #
+
+--echo #
+--echo # MDEV-8373 Zero date can be inserted in strict no-zero mode through CREATE TABLE AS SELECT timestamp_field
+--echo #
+
+SET sql_mode=DEFAULT;
+--eval CREATE TABLE t1 (a $type);
+INSERT INTO t1 VALUES (0);
+SET sql_mode='TRADITIONAL';
+--error ER_TRUNCATED_WRONG_VALUE

Re: [Maria-developers] Please review: MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value

2015-06-25 Thread Alexander Barkov

Hi Sergei,

Thanks for review.

I have a couple of questions before I can send a new version.
See the questions and other comments below.


On 06/24/2015 08:32 PM, Sergei Golubchik wrote:

Hi, Alexander!


I rewrote the patch slightly, so now we don't need to remember
all_default_are_checked or write_set_defaults_are_checked.

The default values are now checked before the query execution
and before the first restore_record() call. They are now
checked directly in table-s-default_values.

I hope it's now faster and clearer comparing to the original patch
version from MySQL-5.6.


Agree! It's much better now.

See my comments and questions below.


diff --git a/sql/field.h b/sql/field.h
index 4ca493e..2b430cb 100644
--- a/sql/field.h
+++ b/sql/field.h


This is ok, but then, please, see what other places in the code you can
change to use your new helpers. In a separate commit, of course. I
suspect there's a lot of code that's doing, like

   /* Get the value from default_values */
   diff= (my_ptrdiff_t) (orig_field-table-s-default_values-
 orig_field-table-record[0]);
   orig_field-move_field_offset(diff);  // Points now at default_values
   if (orig_field-is_real_null())
 field-set_null();
   else
   {
 field-set_notnull();
 memcpy(field-ptr, orig_field-ptr, field-pack_length());
   }
   orig_field-move_field_offset(-diff); // Back to record[0]

(this is from create_tmp_table() in sql_select.cc)


Sure, I have created a task for this, not to forget:

MDEV-8372 Use helper methods introduced in MDEV-7824 all around the code





diff --git a/sql/field.cc b/sql/field.cc
index ba6d4ff..4a854a3 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -5980,12 +5993,22 @@ String *Field_newdate::val_str(String *val_buffer,
  bool Field_newdate::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
  {
uint32 tmp=(uint32) uint3korr(ptr);
-  ltime-day=   tmp  31;
-  ltime-month= (tmp  5)  15;
+  ltime-day=   num_to_day(tmp);
+  ltime-month= num_to_month(tmp);
ltime-year=  (tmp  9);


may be num_to_year() ? just for consistency.


ltime-time_type= MYSQL_TIMESTAMP_DATE;
ltime-hour= ltime-minute= ltime-second= ltime-second_part= ltime-neg= 
0;
-  return validate_for_get_date(tmp, ltime, fuzzydate);
+  return validate_MMDD(tmp, ltime-month, ltime-day, fuzzydate);
+}
+
+
+bool
+Field_newdate::validate_value_in_record(THD *thd, const uchar *record) const
+{
+  DBUG_ASSERT(!is_null_in_record(record));
+  uint32 num= (uint32) uint3korr(ptr_in_record(record));
+  return validate_MMDD(num, num_to_month(num), num_to_day(num),


on the other hand, I'd rather use get_date() here, instead of
duplicating its logic. This applies to all other
validate_value_in_record() methods too. May be like

MYSQL_TIME ltime;
get_date_from_ptr(ltime, ptr_in_record(record), sql_mode_for_dates(thd));



get_date_from_ptr() will generally need to do more than
it's needed for validation. For example, for DATETIME, it will spend
CPU to initialize the TIME part unnecessarily.

But Field_datetimef does full initialization of MYSQL_TIME anyway,
as I didn't add a faster validation function in compat56.cc.

Okey, as validation is done only once per query, I'll agree that
it's better to have less duplicate code here than a super-optimized code.

I'll try your idea with get_date_from_ptr().




+   sql_mode_for_dates(thd));
  }


@@ -10268,3 +10332,29 @@ void Field::set_explicit_default(Item *value)
  return;
set_has_explicit_value();
  }
+
+
+bool Field::validate_value_in_record_with_warn(THD *thd, const uchar *record)
+{
+  if (!validate_value_in_record(thd, record))
+return false;
+  /*
+Only write_set[field_index] is set for this,
+while read_set[field_index] is not guaranteed to be set.
+Set table-read_set to NULL,
+to make ASSERT_COLUMN_MARKED_FOR_READ in val_str() happy.
+  */
+  MY_BITMAP *read_set_backup= table-read_set;
+  table-read_set= 0;


Normally one uses dbug_tmp_use_all_columns() for that (without comment).



Thanks. Will use this.



+  // Get val_str() for the DEFAULT value
+  StringBufferMAX_FIELD_WIDTH tmp;
+  val_str(tmp, ptr_in_record(record));
+  // Convert val_str() result to a printable error parameter
+  ErrConvString err(tmp);
+  push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+  ER_INVALID_DEFAULT_VALUE_FOR_FIELD,
+  ER(ER_INVALID_DEFAULT_VALUE_FOR_FIELD),
+  err.ptr(), field_name);
+  table-read_set= read_set_backup;
+  return true;
+}
diff --git a/sql/field_conv.cc b/sql/field_conv.cc
index e31f7c5..14f2947 100644
--- a/sql/field_conv.cc
+++ b/sql/field_conv.cc
@@ -859,7 +859,8 @@ bool memcpy_field_possible(Field *to,Field *from)
from-charset() == to-charset() 
(!sql_mode_for_dates(to-table-in_use) ||
 (from-type()!= MYSQL_TYPE_DATE 
-from-type()!= 

Re: [Maria-developers] Please review: MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value

2015-06-24 Thread Sergei Golubchik
Hi, Alexander!

 I rewrote the patch slightly, so now we don't need to remember
 all_default_are_checked or write_set_defaults_are_checked.
 
 The default values are now checked before the query execution
 and before the first restore_record() call. They are now
 checked directly in table-s-default_values.
 
 I hope it's now faster and clearer comparing to the original patch
 version from MySQL-5.6.

Agree! It's much better now.

See my comments and questions below.

 diff --git a/sql/field.h b/sql/field.h
 index 4ca493e..2b430cb 100644
 --- a/sql/field.h
 +++ b/sql/field.h

This is ok, but then, please, see what other places in the code you can
change to use your new helpers. In a separate commit, of course. I
suspect there's a lot of code that's doing, like

  /* Get the value from default_values */
  diff= (my_ptrdiff_t) (orig_field-table-s-default_values-
orig_field-table-record[0]);
  orig_field-move_field_offset(diff);  // Points now at default_values
  if (orig_field-is_real_null())
field-set_null();
  else
  {
field-set_notnull();
memcpy(field-ptr, orig_field-ptr, field-pack_length());
  }
  orig_field-move_field_offset(-diff); // Back to record[0]

(this is from create_tmp_table() in sql_select.cc)

 diff --git a/sql/field.cc b/sql/field.cc
 index ba6d4ff..4a854a3 100644
 --- a/sql/field.cc
 +++ b/sql/field.cc
 @@ -5980,12 +5993,22 @@ String *Field_newdate::val_str(String *val_buffer,
  bool Field_newdate::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
  {
uint32 tmp=(uint32) uint3korr(ptr);
 -  ltime-day=   tmp  31;
 -  ltime-month= (tmp  5)  15;
 +  ltime-day=   num_to_day(tmp);
 +  ltime-month= num_to_month(tmp);
ltime-year=  (tmp  9);

may be num_to_year() ? just for consistency.

ltime-time_type= MYSQL_TIMESTAMP_DATE;
ltime-hour= ltime-minute= ltime-second= ltime-second_part= ltime-neg= 
 0;
 -  return validate_for_get_date(tmp, ltime, fuzzydate);
 +  return validate_MMDD(tmp, ltime-month, ltime-day, fuzzydate);
 +}
 +
 +
 +bool
 +Field_newdate::validate_value_in_record(THD *thd, const uchar *record) const
 +{
 +  DBUG_ASSERT(!is_null_in_record(record));
 +  uint32 num= (uint32) uint3korr(ptr_in_record(record));
 +  return validate_MMDD(num, num_to_month(num), num_to_day(num),

on the other hand, I'd rather use get_date() here, instead of
duplicating its logic. This applies to all other
validate_value_in_record() methods too. May be like

   MYSQL_TIME ltime;
   get_date_from_ptr(ltime, ptr_in_record(record), sql_mode_for_dates(thd));

 +   sql_mode_for_dates(thd));
  }
  
  
 @@ -10268,3 +10332,29 @@ void Field::set_explicit_default(Item *value)
  return;
set_has_explicit_value();
  }
 +
 +
 +bool Field::validate_value_in_record_with_warn(THD *thd, const uchar *record)
 +{
 +  if (!validate_value_in_record(thd, record))
 +return false;
 +  /*
 +Only write_set[field_index] is set for this,
 +while read_set[field_index] is not guaranteed to be set.
 +Set table-read_set to NULL,
 +to make ASSERT_COLUMN_MARKED_FOR_READ in val_str() happy.
 +  */
 +  MY_BITMAP *read_set_backup= table-read_set;
 +  table-read_set= 0;

Normally one uses dbug_tmp_use_all_columns() for that (without comment).

 +  // Get val_str() for the DEFAULT value
 +  StringBufferMAX_FIELD_WIDTH tmp;
 +  val_str(tmp, ptr_in_record(record));
 +  // Convert val_str() result to a printable error parameter
 +  ErrConvString err(tmp);
 +  push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 +  ER_INVALID_DEFAULT_VALUE_FOR_FIELD,
 +  ER(ER_INVALID_DEFAULT_VALUE_FOR_FIELD),
 +  err.ptr(), field_name);
 +  table-read_set= read_set_backup;
 +  return true;
 +}
 diff --git a/sql/field_conv.cc b/sql/field_conv.cc
 index e31f7c5..14f2947 100644
 --- a/sql/field_conv.cc
 +++ b/sql/field_conv.cc
 @@ -859,7 +859,8 @@ bool memcpy_field_possible(Field *to,Field *from)
from-charset() == to-charset() 
(!sql_mode_for_dates(to-table-in_use) ||
 (from-type()!= MYSQL_TYPE_DATE 
 -from-type()!= MYSQL_TYPE_DATETIME)) 
 +from-type()!= MYSQL_TYPE_DATETIME 
 +from-type()!= MYSQL_TYPE_TIMESTAMP)) 

what is this for - to apply TIME_NO_ZERO_DATE to timestamps?

(from_real_type != MYSQL_TYPE_VARCHAR ||
 ((Field_varstring*)from)-length_bytes ==
 ((Field_varstring*)to)-length_bytes));
 diff --git a/sql/item.cc b/sql/item.cc
 index f685242..0f2813a 100644
 --- a/sql/item.cc
 +++ b/sql/item.cc
 @@ -8184,7 +8184,12 @@ int Item_default_value::save_in_field(Field 
 *field_arg, bool no_conversions)
return 1;
  }
  field_arg-set_default();
 -return 0;
 +THD *thd= current_thd;

you've recently spent a lot of efforts removing current_thd, is it
necessary to add a new one here? can field_arg-table-in_use be used here?

 +   

Re: [Maria-developers] Please review: MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value

2015-05-18 Thread Alexander Barkov

Hi Sergei,

Thanks for review! See my comments inline:


On 05/07/2015 06:57 PM, Sergei Golubchik wrote:

Hi, Alexander!

On Mar 25, Alexander Barkov wrote:

Hi Sergei,

Please review a patch for mdev-7824.

It's based on a MySQL patch for
http://bugs.mysql.com/bug.php?id=68041

and is a blocker for:

MDEV-3929 Add full support for auto-initialized/updated timestamp and
datetime


That looks good.

But I don't like it that you verify defaults for every row.
It's not very cheap. And neither defaults nor sql_mode can change in the
duration of one statement. It should be enough to test only once.



This is a good idea. I copied the patch from MySQL. So it will be nice
to have this implemented in MariaDB in a faster way.



I'm not sure, though, how to do that with a minimal overhead. A couple
of bool's in TABLE, like

bool all_default_are_checked;
bool write_set_defaults_are_checked;

that are set to false at the beginning on INSERT,INSERT...SELECT,LOAD.
And used like that

   bool TABLE::restore_record_and_validate_unset_fields(THD *thd, bool all)
   {
 restore_record(this, s-default_values);
 if (all ? all_default_are_checked : write_set_defaults_are_checked)
   return false;

 if (all) all_default_are_checked= true;
 else write_set_defaults_are_checked= true;

 return validate_default_values_of_unset_fields(thd);
   }

and in mysql_insert():

  if (fields.elements || !value_count)
  {
if (table-restore_record_and_validate_unset_fields(thd, !value_count))



Sorry, I did't understand your idea about having two separate
flags for all and write_set.
Is it for the cases when one mixes empty and non-empty parenthesized
value lists, like these:

insert into (a,b) t1 values (),(10,20);
insert into (a,b) t1 values (10,20),();

???


Note, this currently does not work and return this error:

ERROR 1136 (21S01): Column count doesn't match value count at row 2

Looks like a bug. I guess this could work.
If so, would you like me to report and fix it before mdev-7824?


Thanks.




Regards,
Sergei



___
Mailing list: https://launchpad.net/~maria-developers
Post to : maria-developers@lists.launchpad.net
Unsubscribe : https://launchpad.net/~maria-developers
More help   : https://help.launchpad.net/ListHelp


Re: [Maria-developers] Please review: MDEV-7824 [Bug #68041] Zero date can be inserted in strict no-zero mode through a default value

2015-05-07 Thread Sergei Golubchik
Hi, Alexander!

On Mar 25, Alexander Barkov wrote:
 Hi Sergei,
 
 Please review a patch for mdev-7824.
 
 It's based on a MySQL patch for
 http://bugs.mysql.com/bug.php?id=68041
 
 and is a blocker for:
 
 MDEV-3929 Add full support for auto-initialized/updated timestamp and 
 datetime

That looks good.

But I don't like it that you verify defaults for every row.
It's not very cheap. And neither defaults nor sql_mode can change in the
duration of one statement. It should be enough to test only once.

I'm not sure, though, how to do that with a minimal overhead. A couple
of bool's in TABLE, like

   bool all_default_are_checked;
   bool write_set_defaults_are_checked;

that are set to false at the beginning on INSERT,INSERT...SELECT,LOAD.
And used like that

  bool TABLE::restore_record_and_validate_unset_fields(THD *thd, bool all)
  {
restore_record(this, s-default_values);
if (all ? all_default_are_checked : write_set_defaults_are_checked)
  return false;

if (all) all_default_are_checked= true;
else write_set_defaults_are_checked= true;

return validate_default_values_of_unset_fields(thd);
  }

and in mysql_insert():

 if (fields.elements || !value_count)
 {
   if (table-restore_record_and_validate_unset_fields(thd, !value_count))

Regards,
Sergei


___
Mailing list: https://launchpad.net/~maria-developers
Post to : maria-developers@lists.launchpad.net
Unsubscribe : https://launchpad.net/~maria-developers
More help   : https://help.launchpad.net/ListHelp