From ca7fbcea6c4fe13c295cf43b80d05351aba59e95 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Wed, 24 Apr 2019 15:47:49 +0400 Subject: [PATCH] MDEV-19317 TEXT column accepts too long literals as a default value Adding new virtual methods in Field: - make_empty_rec_store_default_value() - make_empty_rec_reset() This simplifies the logic for every Field type, and makes the code more friendly to pluggable data types. --- mysql-test/main/type_blob.result | 31 ++++++++++++++++ mysql-test/main/type_blob.test | 36 +++++++++++++++++++ sql/field.cc | 20 +++++++++++ sql/field.h | 21 +++++++++++ sql/unireg.cc | 61 +++++++++++++++----------------- 5 files changed, 137 insertions(+), 32 deletions(-) diff --git a/mysql-test/main/type_blob.result b/mysql-test/main/type_blob.result index 94741ddbc0e..cfb47f7b850 100644 --- a/mysql-test/main/type_blob.result +++ b/mysql-test/main/type_blob.result @@ -1093,3 +1093,34 @@ drop table t1; # # End of 10.2 test # +# +# Start of 10.4 test +# +# +# MDEV-19317 TEXT column accepts too long literals as a default value +# +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT ?)' USING REPEAT('a', 255); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +LENGTH(a) LENGTH(DEFAULT(a)) +255 255 +DROP TABLE t1; +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT ?)' USING REPEAT('a', 256); +ERROR 42000: Invalid default value for 'a' +CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); +ERROR 42000: Invalid default value for 'a' +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TEXT DEFAULT ?)' USING REPEAT('a', 256); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +LENGTH(a) LENGTH(DEFAULT(a)) +256 256 +DROP TABLE t1; +CREATE OR REPLACE TABLE t1 (a TEXT DEFAULT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +LENGTH(a) LENGTH(DEFAULT(a)) +256 256 +DROP TABLE t1; +# +# End of 10.4 test +# diff --git a/mysql-test/main/type_blob.test b/mysql-test/main/type_blob.test index 910050f067a..df565b187b4 100644 --- a/mysql-test/main/type_blob.test +++ b/mysql-test/main/type_blob.test @@ -702,3 +702,39 @@ drop table t1; --echo # --echo # End of 10.2 test --echo # + + +--echo # +--echo # Start of 10.4 test +--echo # + +--echo # +--echo # MDEV-19317 TEXT column accepts too long literals as a default value +--echo # + +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT ?)' USING REPEAT('a', 255); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +DROP TABLE t1; + +--error ER_INVALID_DEFAULT +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT ?)' USING REPEAT('a', 256); + +--error ER_INVALID_DEFAULT +CREATE OR REPLACE TABLE t1 (a TINYTEXT DEFAULT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + + +EXECUTE IMMEDIATE 'CREATE OR REPLACE TABLE t1 (a TEXT DEFAULT ?)' USING REPEAT('a', 256); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +DROP TABLE t1; + +CREATE OR REPLACE TABLE t1 (a TEXT DEFAULT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); +INSERT INTO t1 VALUES (); +SELECT LENGTH(a), LENGTH(DEFAULT(a)) FROM t1; +DROP TABLE t1; + + +--echo # +--echo # End of 10.4 test +--echo # diff --git a/sql/field.cc b/sql/field.cc index e899a1ec4d6..1056a99a498 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1382,6 +1382,14 @@ error: } +bool Field::make_empty_rec_store_default_value(THD *thd, Item *item) +{ + DBUG_ASSERT(!(flags & BLOB_FLAG)); + int res= item->save_in_field(this, true); + return res != 0 && res != 3; +} + + /** Numeric fields base class constructor. */ @@ -8773,6 +8781,18 @@ void Field_blob::make_send_field(Send_field *field) } +bool Field_blob::make_empty_rec_store_default_value(THD *thd, Item *item) +{ + DBUG_ASSERT(flags & BLOB_FLAG); + int res= item->save_in_field(this, true); + DBUG_ASSERT(res != 3); // Field_blob never returns 3 + if (res) + return true; // E.g. truncation happened + reset(); // Clear the pointer to a String, it should not be written to frm + return false; +} + + int Field_blob_compressed::store(const char *from, size_t length, CHARSET_INFO *cs) { diff --git a/sql/field.h b/sql/field.h index c5195fe4a60..a1fae26dfcc 100644 --- a/sql/field.h +++ b/sql/field.h @@ -782,6 +782,11 @@ public: @retval false - conversion is needed */ virtual bool memcpy_field_possible(const Field *from) const= 0; + virtual bool make_empty_rec_store_default_value(THD *thd, Item *item); + virtual void make_empty_rec_reset(THD *thd) + { + reset(); + } virtual int store(const char *to, size_t length,CHARSET_INFO *cs)=0; virtual int store_hex_hybrid(const char *str, size_t length); virtual int store(double nr)=0; @@ -3895,6 +3900,7 @@ public: !compression_method() == !from->compression_method() && !table->copy_blobs; } + bool make_empty_rec_store_default_value(THD *thd, Item *item); int store(const char *to, size_t length, CHARSET_INFO *charset); using Field_str::store; double val_real(void); @@ -4212,6 +4218,16 @@ public: return save_in_field_str(to); } bool memcpy_field_possible(const Field *from) const { return false; } + void make_empty_rec_reset(THD *thd) + { + if (flags & NOT_NULL_FLAG) + { + set_notnull(); + store((longlong) 1, true); + } + else + reset(); + } int store(const char *to,size_t length,CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); @@ -4278,6 +4294,11 @@ public: { flags=(flags & ~ENUM_FLAG) | SET_FLAG; } + void make_empty_rec_reset(THD *thd) + { + Field::make_empty_rec_reset(thd); + } + int store_field(Field *from) { return from->save_in_field(this); } int store(const char *to,size_t length,CHARSET_INFO *charset); int store(double nr) { return Field_set::store((longlong) nr, FALSE); } diff --git a/sql/unireg.cc b/sql/unireg.cc index 1d6bb0cdfce..02d876e1455 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -974,13 +974,36 @@ static bool pack_fields(uchar **buff_arg, List &create_fields, DBUG_RETURN(0); } + +static bool make_empty_rec_store_default(THD *thd, Field *regfield, + Virtual_column_info *default_value) +{ + if (default_value && !default_value->flags) + { + Item *expr= default_value->expr; + // may be already fixed if ALTER TABLE + if (expr->fix_fields_if_needed(thd, &expr)) + return true; + DBUG_ASSERT(expr == default_value->expr); // Should not change + if (regfield->make_empty_rec_store_default_value(thd, expr)) + { + my_error(ER_INVALID_DEFAULT, MYF(0), regfield->field_name.str); + return true; + } + return false; + } + regfield->make_empty_rec_reset(thd); + return false; +} + + /* save an empty record on start of formfile */ static bool make_empty_rec(THD *thd, uchar *buff, uint table_options, List &create_fields, uint reclength, ulong data_offset) { - int error= 0; + int error= false; uint null_count; uchar *null_pos; TABLE table; @@ -1020,7 +1043,7 @@ static bool make_empty_rec(THD *thd, uchar *buff, uint table_options, field->flags); if (!regfield) { - error= 1; + error= true; goto err; // End of memory } @@ -1037,36 +1060,10 @@ static bool make_empty_rec(THD *thd, uchar *buff, uint table_options, !f_bit_as_char(field->pack_flag)) null_count+= field->length & 7; - if (field->default_value && !field->default_value->flags && - (!(field->flags & BLOB_FLAG) || - field->real_field_type() == MYSQL_TYPE_GEOMETRY)) - { - Item *expr= field->default_value->expr; - // may be already fixed if ALTER TABLE - int res= expr->fix_fields_if_needed(thd, &expr); - if (!res) - res= expr->save_in_field(regfield, 1); - if (!res && (field->flags & BLOB_FLAG)) - regfield->reset(); - - /* If not ok or warning of level 'note' */ - if (res != 0 && res != 3) - { - my_error(ER_INVALID_DEFAULT, MYF(0), regfield->field_name.str); - error= 1; - delete regfield; //To avoid memory leak - goto err; - } - delete regfield; //To avoid memory leak - } - else if (regfield->real_type() == MYSQL_TYPE_ENUM && - (field->flags & NOT_NULL_FLAG)) - { - regfield->set_notnull(); - regfield->store((longlong) 1, TRUE); - } - else - regfield->reset(); + error= make_empty_rec_store_default(thd, regfield, field->default_value); + delete regfield; // Avoid memory leaks + if (error) + goto err; } DBUG_ASSERT(data_offset == ((null_count + 7) / 8));