summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.com>2019-08-26 15:28:32 +0400
committerAlexander Barkov <bar@mariadb.com>2019-09-03 05:34:53 +0400
commitdc719597ee0b11da722e9813639e8b48018a8c10 (patch)
tree548b53ee953557ddf6f88191b9e6cf70f74c96a2 /sql
parent0d6635822094a424f19e753aa24a8424d449dd6a (diff)
downloadmariadb-git-dc719597ee0b11da722e9813639e8b48018a8c10.tar.gz
MDEV-18156 Assertion `0' failed or `btr_validate_index(index, 0, false)' in row_upd_sec_index_entry or error code 126: Index is corrupted upon DELETE with PAD_CHAR_TO_FULL_LENGTH
This change takes into account a column's GENERATED ALWAYS AS expression dependcy on sql_mode's PAD_CHAR_TO_FULL_LENGTH and NO_UNSIGNED_SUBTRACTION flags. Indexed virtual columns as well as persistent generated columns are now not allowed to have such dependencies to avoid inconsistent data or index files on sql_mode changes. So an error is now returned in cases like this: CREATE OR REPLACE TABLE t1 ( a CHAR(5), v VARCHAR(5) AS (a) PERSISTENT -- CHAR->VARCHAR or CHAR->TEXT = ERROR ); Functions RPAD() and RTRIM() can now remove dependency on PAD_CHAR_TO_FULL_LENGTH. So this can be used instead: CREATE OR REPLACE TABLE t1 ( a CHAR(5), v VARCHAR(5) AS (RTRIM(a)) PERSISTENT ); Note, unlike CHAR->VARCHAR and CHAR->TEXT this still works, not RPAD(a) is needed: CREATE OR REPLACE TABLE t1 ( a CHAR(5), v CHAR(5) AS (a) PERSISTENT -- CHAR->CHAR is OK ); More sql_mode flags may affect values of generated columns. They will be addressed separately. See comments in sql_mode.h for implementation details.
Diffstat (limited to 'sql')
-rw-r--r--sql/CMakeLists.txt2
-rw-r--r--sql/field.cc67
-rw-r--r--sql/field.h16
-rw-r--r--sql/item.h35
-rw-r--r--sql/item_cmpfunc.cc40
-rw-r--r--sql/item_cmpfunc.h2
-rw-r--r--sql/item_func.cc19
-rw-r--r--sql/item_func.h5
-rw-r--r--sql/item_strfunc.cc67
-rw-r--r--sql/item_strfunc.h6
-rw-r--r--sql/mysqld.h1
-rw-r--r--sql/set_var.h1
-rw-r--r--sql/sql_mode.cc34
-rw-r--r--sql/sql_mode.h162
-rw-r--r--sql/sql_string.h5
-rw-r--r--sql/sys_vars.cc8
-rw-r--r--sql/table.cc6
17 files changed, 472 insertions, 4 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 769a1b53331..963243c9767 100644
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -135,7 +135,7 @@ SET (SQL_SOURCE
my_apc.cc mf_iocache_encr.cc item_jsonfunc.cc
my_json_writer.cc
rpl_gtid.cc rpl_parallel.cc
- sql_type.cc
+ sql_type.cc sql_mode.cc
item_windowfunc.cc sql_window.cc
sql_cte.cc
${WSREP_SOURCES}
diff --git a/sql/field.cc b/sql/field.cc
index d7ac03328a0..e5359a7520e 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -1428,6 +1428,43 @@ void Field::load_data_set_value(const char *pos, uint length,
}
+void Field::error_generated_column_function_is_not_allowed(THD *thd) const
+{
+ StringBuffer<64> tmp;
+ vcol_info->expr->print(&tmp, (enum_query_type)
+ (QT_TO_SYSTEM_CHARSET |
+ QT_ITEM_IDENT_SKIP_DB_NAMES |
+ QT_ITEM_IDENT_SKIP_TABLE_NAMES));
+ my_error(ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED, MYF(0),
+ tmp.c_ptr(), vcol_info->get_vcol_type_name(),
+ const_cast<const char*>(field_name));
+}
+
+
+/*
+ Check if an indexed or a persistent virtual column depends on sql_mode flags
+ that it cannot handle.
+ See sql_mode.h for details.
+*/
+bool Field::check_vcol_sql_mode_dependency(THD *thd) const
+{
+ DBUG_ASSERT(vcol_info);
+ if ((flags & PART_KEY_FLAG) != 0 || stored_in_db())
+ {
+ Sql_mode_dependency dep=
+ vcol_info->expr->value_depends_on_sql_mode() &
+ Sql_mode_dependency(~0, ~can_handle_sql_mode_dependency_on_store());
+ if (dep)
+ {
+ error_generated_column_function_is_not_allowed(thd);
+ dep.push_dependency_warnings(thd);
+ return true;
+ }
+ }
+ return false;
+}
+
+
/**
Numeric fields base class constructor.
*/
@@ -1464,6 +1501,12 @@ void Field_num::prepend_zeros(String *value) const
}
+sql_mode_t Field_num::can_handle_sql_mode_dependency_on_store() const
+{
+ return MODE_PAD_CHAR_TO_FULL_LENGTH;
+}
+
+
Item *Field_num::get_equal_zerofill_const_item(THD *thd, const Context &ctx,
Item *const_item)
{
@@ -5654,6 +5697,12 @@ my_time_t Field_timestampf::get_timestamp(const uchar *pos,
/*************************************************************/
+sql_mode_t Field_temporal::can_handle_sql_mode_dependency_on_store() const
+{
+ return MODE_PAD_CHAR_TO_FULL_LENGTH;
+}
+
+
uint Field_temporal::is_equal(Create_field *new_field)
{
return new_field->sql_type == real_type() &&
@@ -7271,6 +7320,18 @@ longlong Field_string::val_int(void)
}
+sql_mode_t Field_string::value_depends_on_sql_mode() const
+{
+ return has_charset() ? MODE_PAD_CHAR_TO_FULL_LENGTH : sql_mode_t(0);
+};
+
+
+sql_mode_t Field_string::can_handle_sql_mode_dependency_on_store() const
+{
+ return has_charset() ? MODE_PAD_CHAR_TO_FULL_LENGTH : sql_mode_t(0);
+}
+
+
String *Field_string::val_str(String *val_buffer __attribute__((unused)),
String *val_ptr)
{
@@ -8881,6 +8942,12 @@ bool Field_geom::load_data_set_null(THD *thd)
** If one uses this string in a number context one gets the type number.
****************************************************************************/
+sql_mode_t Field_enum::can_handle_sql_mode_dependency_on_store() const
+{
+ return MODE_PAD_CHAR_TO_FULL_LENGTH;
+}
+
+
enum ha_base_keytype Field_enum::key_type() const
{
switch (packlength) {
diff --git a/sql/field.h b/sql/field.h
index ac7794081bc..9333d1492fd 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -697,6 +697,7 @@ protected:
val_str(&result);
return to->store(result.ptr(), result.length(), charset());
}
+ void error_generated_column_function_is_not_allowed(THD *thd) const;
static void do_field_int(Copy_field *copy);
static void do_field_real(Copy_field *copy);
static void do_field_string(Copy_field *copy);
@@ -1174,6 +1175,16 @@ public:
}
bool stored_in_db() const { return !vcol_info || vcol_info->stored_in_db; }
+ bool check_vcol_sql_mode_dependency(THD *) const;
+
+ virtual sql_mode_t value_depends_on_sql_mode() const
+ {
+ return 0;
+ }
+ virtual sql_mode_t can_handle_sql_mode_dependency_on_store() const
+ {
+ return 0;
+ }
inline THD *get_thd() const
{ return likely(table) ? table->in_use : current_thd; }
@@ -1653,6 +1664,7 @@ public:
enum Derivation derivation(void) const { return DERIVATION_NUMERIC; }
uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; }
CHARSET_INFO *charset(void) const { return &my_charset_numeric; }
+ sql_mode_t can_handle_sql_mode_dependency_on_store() const;
Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item)
{
return (flags & ZEROFILL_FLAG) ?
@@ -2389,6 +2401,7 @@ public:
field_name_arg)
{ flags|= BINARY_FLAG; }
Item_result result_type () const { return STRING_RESULT; }
+ sql_mode_t can_handle_sql_mode_dependency_on_store() const;
int store_hex_hybrid(const char *str, uint length)
{
return store(str, length, &my_charset_bin);
@@ -3190,6 +3203,8 @@ public:
{ return charset() == &my_charset_bin ? FALSE : TRUE; }
Field *make_new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type);
virtual uint get_key_image(uchar *buff,uint length, imagetype type);
+ sql_mode_t value_depends_on_sql_mode() const;
+ sql_mode_t can_handle_sql_mode_dependency_on_store() const;
private:
int do_save_field_metadata(uchar *first_byte);
};
@@ -3599,6 +3614,7 @@ public:
enum_field_types type() const { return MYSQL_TYPE_STRING; }
enum Item_result cmp_type () const { return INT_RESULT; }
enum ha_base_keytype key_type() const;
+ sql_mode_t can_handle_sql_mode_dependency_on_store() const;
Copy_func *get_copy_func(const Field *from) const
{
if (eq_def(from))
diff --git a/sql/item.h b/sql/item.h
index 2239544dc88..ff4cfd6c1b8 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -1216,6 +1216,32 @@ public:
double val_real_from_decimal();
double val_real_from_date();
+ /*
+ Returns true if this item can be calculated during
+ value_depends_on_sql_mode()
+ */
+ bool value_depends_on_sql_mode_const_item()
+ {
+ /*
+ Currently we use value_depends_on_sql_mode() only for virtual
+ column expressions. They should not contain any expensive items.
+ If we ever get a crash on the assert below, it means
+ check_vcol_func_processor() is badly implemented for this item.
+ */
+ DBUG_ASSERT(!is_expensive());
+ /*
+ It should return const_item() actually.
+ But for some reasons Item_field::const_item() returns true
+ at value_depends_on_sql_mode() call time.
+ This should be checked and fixed.
+ */
+ return basic_const_item();
+ }
+ virtual Sql_mode_dependency value_depends_on_sql_mode() const
+ {
+ return Sql_mode_dependency();
+ }
+
// Get TIME, DATE or DATETIME using proper sql_mode flags for the field type
bool get_temporal_with_sql_mode(MYSQL_TIME *ltime);
// Check NULL value for a TIME, DATE or DATETIME expression
@@ -2694,6 +2720,10 @@ public:
{
return MONOTONIC_STRICT_INCREASING;
}
+ Sql_mode_dependency value_depends_on_sql_mode() const
+ {
+ return Sql_mode_dependency(0, field->value_depends_on_sql_mode());
+ }
longlong val_int_endpoint(bool left_endp, bool *incl_endp);
bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
bool get_date_result(MYSQL_TIME *ltime,ulonglong fuzzydate);
@@ -4102,6 +4132,7 @@ public:
inline Item **arguments() const { return args; }
inline uint argument_count() const { return arg_count; }
inline void remove_arguments() { arg_count=0; }
+ Sql_mode_dependency value_depends_on_sql_mode_bit_or() const;
};
@@ -4334,6 +4365,10 @@ public:
bool const_item() const { return const_item_cache; }
table_map used_tables() const { return used_tables_cache; }
Item* build_clone(THD *thd, MEM_ROOT *mem_root);
+ Sql_mode_dependency value_depends_on_sql_mode() const
+ {
+ return Item_args::value_depends_on_sql_mode_bit_or().soft_to_hard();
+ }
};
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
index 8bf130b297f..2c3a96f3f47 100644
--- a/sql/item_cmpfunc.cc
+++ b/sql/item_cmpfunc.cc
@@ -531,6 +531,23 @@ bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp)
}
+/*
+ Comparison operators remove arguments' dependency on PAD_CHAR_TO_FULL_LENGTH
+ in case of PAD SPACE comparison collations: trailing spaces do not affect
+ the comparison result for such collations.
+*/
+Sql_mode_dependency
+Item_bool_rowready_func2::value_depends_on_sql_mode() const
+{
+ if (compare_collation()->state & MY_CS_NOPAD)
+ return Item_func::value_depends_on_sql_mode();
+ return ((args[0]->value_depends_on_sql_mode() |
+ args[1]->value_depends_on_sql_mode()) &
+ Sql_mode_dependency(~0, ~MODE_PAD_CHAR_TO_FULL_LENGTH)).
+ soft_to_hard();
+}
+
+
bool Item_bool_rowready_func2::fix_length_and_dec()
{
max_length= 1; // Function returns 0 or 1
@@ -5284,6 +5301,29 @@ bool Item_func_like::with_sargable_pattern() const
}
+/*
+ subject LIKE pattern
+ removes subject's dependency on PAD_CHAR_TO_FULL_LENGTH
+ if pattern ends with the '%' wildcard.
+*/
+Sql_mode_dependency Item_func_like::value_depends_on_sql_mode() const
+{
+ if (!args[1]->value_depends_on_sql_mode_const_item())
+ return Item_func::value_depends_on_sql_mode();
+ StringBuffer<64> patternbuf;
+ String *pattern= args[1]->val_str_ascii(&patternbuf);
+ if (!pattern || !pattern->length())
+ return Sql_mode_dependency(); // Will return NULL or 0
+ DBUG_ASSERT(pattern->charset()->mbminlen == 1);
+ if (pattern->ptr()[pattern->length() - 1] != '%')
+ return Item_func::value_depends_on_sql_mode();
+ return ((args[0]->value_depends_on_sql_mode() |
+ args[1]->value_depends_on_sql_mode()) &
+ Sql_mode_dependency(~0, ~MODE_PAD_CHAR_TO_FULL_LENGTH)).
+ soft_to_hard();
+}
+
+
SEL_TREE *Item_func_like::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr)
{
MEM_ROOT *tmp_root= param->mem_root;
diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h
index 5045e39fc5a..0a5abfe6273 100644
--- a/sql/item_cmpfunc.h
+++ b/sql/item_cmpfunc.h
@@ -489,6 +489,7 @@ public:
{
allowed_arg_cols= 0; // Fetch this value from first argument
}
+ Sql_mode_dependency value_depends_on_sql_mode() const;
void print(String *str, enum_query_type query_type)
{
Item_func::print_op(str, query_type);
@@ -1920,6 +1921,7 @@ public:
Item_bool_func2(thd, a, b), canDoTurboBM(FALSE), pattern(0), pattern_len(0),
bmGs(0), bmBc(0), escape_item(escape_arg),
escape_used_in_parsing(escape_used), use_sampling(0), negated(0) {}
+ Sql_mode_dependency value_depends_on_sql_mode() const;
longlong val_int();
enum Functype functype() const { return LIKE_FUNC; }
void print(String *str, enum_query_type query_type);
diff --git a/sql/item_func.cc b/sql/item_func.cc
index 5ff1b26d734..d11b56f2aad 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -422,6 +422,15 @@ void Item_args::propagate_equal_fields(THD *thd,
}
+Sql_mode_dependency Item_args::value_depends_on_sql_mode_bit_or() const
+{
+ Sql_mode_dependency res;
+ for (uint i= 0; i < arg_count; i++)
+ res|= args[i]->value_depends_on_sql_mode();
+ return res;
+}
+
+
/**
See comments in Item_cond::split_sum_func()
*/
@@ -1414,9 +1423,13 @@ bool Item_func_minus::fix_length_and_dec()
{
if (Item_num_op::fix_length_and_dec())
return TRUE;
- if (unsigned_flag &&
- (current_thd->variables.sql_mode & MODE_NO_UNSIGNED_SUBTRACTION))
- unsigned_flag=0;
+ m_sql_mode_dependency= Item_func::value_depends_on_sql_mode();
+ if (unsigned_flag)
+ {
+ m_sql_mode_dependency|= Sql_mode_dependency(0,MODE_NO_UNSIGNED_SUBTRACTION);
+ if (current_thd->variables.sql_mode & MODE_NO_UNSIGNED_SUBTRACTION)
+ unsigned_flag= false;
+ }
return FALSE;
}
diff --git a/sql/item_func.h b/sql/item_func.h
index 81a7e948085..5be62427852 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -773,11 +773,16 @@ public:
class Item_func_minus :public Item_func_additive_op
{
+ Sql_mode_dependency m_sql_mode_dependency;
public:
Item_func_minus(THD *thd, Item *a, Item *b):
Item_func_additive_op(thd, a, b) {}
const char *func_name() const { return "-"; }
enum precedence precedence() const { return ADD_PRECEDENCE; }
+ Sql_mode_dependency value_depends_on_sql_mode() const
+ {
+ return m_sql_mode_dependency;
+ }
longlong int_op();
double real_op();
my_decimal *decimal_op(my_decimal *);
diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
index 753b6134419..dcfbd272809 100644
--- a/sql/item_strfunc.cc
+++ b/sql/item_strfunc.cc
@@ -2103,6 +2103,41 @@ void Item_func_trim::print(String *str, enum_query_type query_type)
}
+/*
+ RTRIM(expr)
+ TRIM(TRAILING ' ' FROM expr)
+ remove argument's soft dependency on PAD_CHAR_TO_FULL_LENGTH:
+*/
+Sql_mode_dependency Item_func_trim::value_depends_on_sql_mode() const
+{
+ DBUG_ASSERT(fixed);
+ if (arg_count == 1) // RTRIM(expr)
+ return (args[0]->value_depends_on_sql_mode() &
+ Sql_mode_dependency(~0, ~MODE_PAD_CHAR_TO_FULL_LENGTH)).
+ soft_to_hard();
+ // TRIM(... FROM expr)
+ DBUG_ASSERT(arg_count == 2);
+ if (!args[1]->value_depends_on_sql_mode_const_item())
+ return Item_func::value_depends_on_sql_mode();
+ StringBuffer<64> trimstrbuf;
+ String *trimstr= args[1]->val_str(&trimstrbuf);
+ if (!trimstr)
+ return Sql_mode_dependency(); // will return NULL
+ if (trimstr->length() == 0)
+ return Item_func::value_depends_on_sql_mode(); // will trim nothing
+ if (trimstr->lengthsp() != 0)
+ return Item_func::value_depends_on_sql_mode(); // will trim not only spaces
+ if (trimstr->length() > trimstr->charset()->mbminlen ||
+ trimstr->numchars() > 1)
+ return Item_func::value_depends_on_sql_mode(); // more than one space
+ // TRIM(TRAILING ' ' FROM expr)
+ return ((args[0]->value_depends_on_sql_mode() |
+ args[1]->value_depends_on_sql_mode()) &
+ Sql_mode_dependency(~0, ~MODE_PAD_CHAR_TO_FULL_LENGTH)).
+ soft_to_hard();
+}
+
+
/* Item_func_password */
bool Item_func_password::fix_fields(THD *thd, Item **ref)
@@ -3116,6 +3151,38 @@ bool Item_func_pad::fix_length_and_dec()
}
+/*
+ PAD(expr,length,' ')
+ removes argument's soft dependency on PAD_CHAR_TO_FULL_LENGTH if the result
+ is longer than the argument's maximim possible length.
+*/
+Sql_mode_dependency Item_func_rpad::value_depends_on_sql_mode() const
+{
+ DBUG_ASSERT(fixed);
+ DBUG_ASSERT(arg_count == 3);
+ if (!args[1]->value_depends_on_sql_mode_const_item() ||
+ !args[2]->value_depends_on_sql_mode_const_item())
+ return Item_func::value_depends_on_sql_mode();
+ Longlong_hybrid len= args[1]->to_longlong_hybrid();
+ if (args[1]->null_value || len.neg())
+ return Sql_mode_dependency(); // will return NULL
+ if (len.abs() > 0 && len.abs() < args[0]->max_char_length())
+ return Item_func::value_depends_on_sql_mode();
+ StringBuffer<64> padstrbuf;
+ String *padstr= args[2]->val_str(&padstrbuf);
+ if (!padstr || !padstr->length())
+ return Sql_mode_dependency(); // will return NULL
+ if (padstr->lengthsp() != 0)
+ return Item_func::value_depends_on_sql_mode(); // will pad not only spaces
+ // RPAD(expr, length, ' ') -- with a long enough length
+ return ((args[0]->value_depends_on_sql_mode() |
+ args[1]->value_depends_on_sql_mode()) &
+ Sql_mode_dependency(~0, ~MODE_PAD_CHAR_TO_FULL_LENGTH)).
+ soft_to_hard();
+}
+
+
+
String *Item_func_rpad::val_str(String *str)
{
DBUG_ASSERT(fixed == 1);
diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
index a398b4972cb..df8761534a1 100644
--- a/sql/item_strfunc.h
+++ b/sql/item_strfunc.h
@@ -489,6 +489,7 @@ protected:
public:
Item_func_trim(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {}
Item_func_trim(THD *thd, Item *a): Item_str_func(thd, a) {}
+ Sql_mode_dependency value_depends_on_sql_mode() const;
String *val_str(String *);
bool fix_length_and_dec();
const char *func_name() const { return "trim"; }
@@ -504,6 +505,10 @@ class Item_func_ltrim :public Item_func_trim
public:
Item_func_ltrim(THD *thd, Item *a, Item *b): Item_func_trim(thd, a, b) {}
Item_func_ltrim(THD *thd, Item *a): Item_func_trim(thd, a) {}
+ Sql_mode_dependency value_depends_on_sql_mode() const
+ {
+ return Item_func::value_depends_on_sql_mode();
+ }
String *val_str(String *);
const char *func_name() const { return "ltrim"; }
const char *mode_name() const { return "leading"; }
@@ -950,6 +955,7 @@ public:
Item_func_pad(thd, arg1, arg2, arg3) {}
String *val_str(String *);
const char *func_name() const { return "rpad"; }
+ Sql_mode_dependency value_depends_on_sql_mode() const;
Item *get_copy(THD *thd, MEM_ROOT *mem_root)
{ return get_item_copy<Item_func_rpad>(thd, mem_root, this); }
};
diff --git a/sql/mysqld.h b/sql/mysqld.h
index 26229c8cc5d..2947734901c 100644
--- a/sql/mysqld.h
+++ b/sql/mysqld.h
@@ -19,6 +19,7 @@
#include <my_global.h> /* MYSQL_PLUGIN_IMPORT, FN_REFLEN, FN_EXTLEN */
#include "sql_basic_types.h" /* query_id_t */
+#include "sql_mode.h" /* Sql_mode_dependency */
#include "sql_bitmap.h" /* Bitmap */
#include "my_decimal.h" /* my_decimal */
#include "mysql_com.h" /* SERVER_VERSION_LENGTH */
diff --git a/sql/set_var.h b/sql/set_var.h
index 8a563f3c089..a5439a27137 100644
--- a/sql/set_var.h
+++ b/sql/set_var.h
@@ -427,6 +427,7 @@ inline bool IS_SYSVAR_AUTOSIZE(void *ptr)
bool fix_delay_key_write(sys_var *self, THD *thd, enum_var_type type);
sql_mode_t expand_sql_mode(sql_mode_t sql_mode);
+const char *sql_mode_string_representation(uint bit_number);
bool sql_mode_string_representation(THD *thd, sql_mode_t sql_mode, LEX_STRING *ls);
int default_regex_flags_pcre(const THD *thd);
diff --git a/sql/sql_mode.cc b/sql/sql_mode.cc
new file mode 100644
index 00000000000..9eea6ccdb52
--- /dev/null
+++ b/sql/sql_mode.cc
@@ -0,0 +1,34 @@
+/*
+ Copyright (c) 2019, MariaDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "mysqld.h"
+#include "set_var.h"
+
+void Sql_mode_dependency::push_dependency_warnings(THD *thd)
+{
+ sql_mode_t all= m_hard | m_soft;
+ for (uint i= 0; all ; i++, all >>= 1)
+ {
+ if (all & 1)
+ {
+ // TODO-10.5: add a new error code
+ push_warning_printf(thd,
+ Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ "Expression depends on the @@%s value %s",
+ "sql_mode", sql_mode_string_representation(i));
+ }
+ }
+}
diff --git a/sql/sql_mode.h b/sql/sql_mode.h
new file mode 100644
index 00000000000..e92848fb6d1
--- /dev/null
+++ b/sql/sql_mode.h
@@ -0,0 +1,162 @@
+#ifndef SQL_MODE_H_INCLUDED
+#define SQL_MODE_H_INCLUDED
+/*
+ Copyright (c) 2019, MariaDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_basic_types.h"
+
+/*
+ class Sql_mode_dependency
+
+ A combination of hard and soft dependency on sql_mode.
+ Used to watch if a GENERATED ALWAYS AS expression guarantees consitent
+ data written to its virtual column.
+
+ A virtual column can appear in an index if:
+ - the generation expression does not depend on any sql_mode flags, or
+ - the generation expression has a soft dependency on an sql_mode flag,
+ and the column knows how to handle this dependeny.
+
+ A virtual column cannot appear in an index if:
+ - its generation expression has a hard dependency
+ - its generation expression has a soft dependency, but the column
+ cannot handle it on store.
+ An error is reported in such cases.
+
+ How dependencies appear:
+ - When a column return value depends on some sql_mode flag,
+ its Item_field adds a corresponding bit to m_soft. For example,
+ Item_field for a CHAR(N) column adds the PAD_CHAR_TO_FULL_LENGTH flag.
+ - When an SQL function/operator return value depends on some sql_mode flag,
+ it adds a corresponding bit to m_soft. For example, Item_func_minus
+ adds the MODE_NO_UNSIGNED_SUBTRACTION in case of unsigned arguments.
+
+ How dependency are processed (see examples below):
+ - All SQL functions/operators bit-OR all hard dependencies from all arguments.
+ - Some soft dependencies can be handled by the underlying Field on store,
+ e.g. CHAR(N) can handle PAD_CHAR_TO_FULL_LENGTH.
+ - Some soft dependencies can be handled by SQL functions and operators,
+ e.g. RTRIM(expr) removes expr's soft dependency on PAD_CHAR_TO_FULL_LENGTH.
+ If a function or operator handles a soft dependency on a certain sql_mode
+ flag, it removes the corresponding bit from m_soft (see below).
+ Note, m_hard is not touched in such cases.
+ - When an expression with a soft dependency on a certain sql_mode flag
+ goes as an argument to an SQL function/operator which cannot handle
+ this flag, the dependency escalates from soft to hard
+ (by moving the corresponding bit from m_soft to m_hard) and cannot be
+ handled any more on the upper level, neither by a Field on store,
+ nor by another SQL function/operator.
+
+ There are four kinds of Items:
+ 1. Items that generate a soft or hard dependency, e.g.
+ - Item_field for CHAR(N) - generates soft/PAD_CHAR_TO_FULL_LENGTH
+ - Item_func_minus - generates soft/NO_UNSIGNED_SUBTRACTION
+ 2. Items that convert a soft dependency to a hard dependency.
+ This happens e.g. when an Item_func instance gets a soft dependency
+ from its arguments, and it does not know how to handle this dependency.
+ Most Item_func descendants do this.
+ 3. Items that remove soft dependencies, e.g.:
+ - Item_func_rtrim - removes soft/PAD_CHAR_TO_FULL_LENGTH
+ that came from args[0] (under certain conditions)
+ - Item_func_rpad - removes soft/PAD_CJAR_TO_FULL_LENGTH
+ that came from args[0] (under certain conditions)
+ 4. Items that repeat soft dependency from its arguments to the caller.
+ They are not implemented yet. But functions like Item_func_coalesce,
+ Item_func_case, Item_func_case_abbreviation2 could do this.
+
+ Examples:
+
+ 1. CREATE OR REPLACE TABLE t1 (a CHAR(5), v CHAR(20) AS(a), KEY(v));
+
+ Here `v` has a soft dependency on `a`.
+ The value of `a` depends on PAD_CHAR_TO_FULL_LENGTH, it can return:
+ - 'a' - if PAD_CHAR_TO_FULL_LENGTH is disabled
+ - 'a' followed by four spaces - if PAD_CHAR_TO_FULL_LENGTH is enabled
+ But `v` will pad trailing spaces to the full length on store anyway.
+ So Field_string handles this soft dependency on store.
+ This combination of the virtial column data type and its generation
+ expression is safe and provides consistent data in `v`, which is
+ 'a' followed by four spaces, no matter what PAD_CHAR_TO_FULL_LENGTH is.
+
+ 2. CREATE OR REPLACE TABLE t1 (a CHAR(5), v VARCHAR(20) AS(a), KEY(v));
+
+ Here `v` has a soft dependency on `a`. But Field_varstring does
+ not pad spaces on store, so it cannot handle this dependency.
+ This combination of the virtual column data type and its generation
+ expression is not safe. An error is returned.
+
+ 3. CREATE OR REPLACE TABLE t1 (a CHAR(5), v INT AS(LENGTH(a)), KEY(v));
+
+ Here `v` has a hard dependency on `a`, because the value of `a`
+ is wrapped to the function LENGTH().
+ The value of `LENGTH(a)` depends on PAD_CHAR_TO_FULL_LENGTH, it can return:
+ - 1 - if PAD_CHAR_TO_FULL_LENGTH is disabled
+ - 4 - if PAD_CHAR_TO_FULL_LENGTH is enabled
+ This combination cannot provide consistent data stored to `v`,
+ therefore it's disallowed.
+*/
+class Sql_mode_dependency
+{
+ sql_mode_t m_hard;
+ sql_mode_t m_soft;
+public:
+ Sql_mode_dependency()
+ :m_hard(0), m_soft(0)
+ { }
+ Sql_mode_dependency(sql_mode_t hard, sql_mode_t soft)
+ :m_hard(hard), m_soft(soft)
+ { }
+ sql_mode_t hard() const { return m_hard; }
+ sql_mode_t soft() const { return m_soft; }
+ operator bool () const
+ {
+ return m_hard > 0 || m_soft > 0;
+ }
+ Sql_mode_dependency operator|(const Sql_mode_dependency &other) const
+ {
+ return Sql_mode_dependency(m_hard | other.m_hard, m_soft | other.m_soft);
+ }
+ Sql_mode_dependency operator&(const Sql_mode_dependency &other) const
+ {
+ return Sql_mode_dependency(m_hard & other.m_hard, m_soft & other.m_soft);
+ }
+ Sql_mode_dependency &operator|=(const Sql_mode_dependency &other)
+ {
+ m_hard|= other.m_hard;
+ m_soft|= other.m_soft;
+ return *this;
+ }
+ Sql_mode_dependency &operator&=(const Sql_mode_dependency &other)
+ {
+ m_hard&= other.m_hard;
+ m_soft&= other.m_soft;
+ return *this;
+ }
+ Sql_mode_dependency &soft_to_hard()
+ {
+ m_hard|= m_soft;
+ m_soft= 0;
+ return *this;
+ }
+ void push_dependency_warnings(THD *thd);
+};
+
+
+#endif // SQL_MODE_H_INCLUDED
diff --git a/sql/sql_string.h b/sql/sql_string.h
index a7652376bbc..e1d64722898 100644
--- a/sql/sql_string.h
+++ b/sql/sql_string.h
@@ -235,6 +235,11 @@ public:
return skr;
}
+ size_t lengthsp() const
+ {
+ return str_charset->cset->lengthsp(str_charset, Ptr, str_length);
+ }
+
void set(String &str,uint32 offset,uint32 arg_length)
{
DBUG_ASSERT(&str != this);
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 04ecbb29b12..8eb75e49d8e 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -3160,6 +3160,14 @@ static const char *sql_mode_names[]=
0
};
+
+const char *sql_mode_string_representation(uint bit_number)
+{
+ DBUG_ASSERT(bit_number < array_elements(sql_mode_names));
+ return sql_mode_names[bit_number];
+}
+
+
export bool sql_mode_string_representation(THD *thd, sql_mode_t sql_mode,
LEX_STRING *ls)
{
diff --git a/sql/table.cc b/sql/table.cc
index d3b4cf3a3c0..62449ebcc06 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -1073,6 +1073,12 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table,
vcol= unpack_vcol_info_from_frm(thd, mem_root, table, &expr_str,
&((*field_ptr)->vcol_info), error_reported);
*(vfield_ptr++)= *field_ptr;
+ if (vcol && field_ptr[0]->check_vcol_sql_mode_dependency(thd))
+ {
+ DBUG_ASSERT(thd->is_error());
+ *error_reported= true;
+ goto end;
+ }
break;
case VCOL_DEFAULT:
vcol= unpack_vcol_info_from_frm(thd, mem_root, table, &expr_str,