MDEV-27744 LPAD in vcol created in ORACLE mode makes table corrupted in non-ORACLE

The crash happened with an indexed virtual column whose
value is evaluated using a function that has a different meaning
in sql_mode='' vs sql_mode=ORACLE:

- DECODE()
- LTRIM()
- RTRIM()
- LPAD()
- RPAD()
- REPLACE()
- SUBSTR()

For example:

CREATE TABLE t1 (
  b VARCHAR(1),
  g CHAR(1) GENERATED ALWAYS AS (SUBSTR(b,0,0)) VIRTUAL,
  KEY g(g)
);

So far we had replacement XXX_ORACLE() functions for all mentioned function,
e.g. SUBSTR_ORACLE() for SUBSTR(). So it was possible to correctly re-parse
SUBSTR_ORACLE() even in sql_mode=''.

But it was not possible to re-parse the MariaDB version of SUBSTR()
after switching to sql_mode=ORACLE. It was erroneously mis-interpreted
as SUBSTR_ORACLE().

As a result, this combination worked fine:

SET sql_mode=ORACLE;
CREATE TABLE t1 ... g CHAR(1) GENERATED ALWAYS AS (SUBSTR(b,0,0)) VIRTUAL, ...;
INSERT ...
FLUSH TABLES;
SET sql_mode='';
INSERT ...

But the other way around it crashed:

SET sql_mode='';
CREATE TABLE t1 ... g CHAR(1) GENERATED ALWAYS AS (SUBSTR(b,0,0)) VIRTUAL, ...;
INSERT ...
FLUSH TABLES;
SET sql_mode=ORACLE;
INSERT ...

At CREATE time, SUBSTR was instantiated as Item_func_substr and printed
in the FRM file as substr(). At re-open time with sql_mode=ORACLE, "substr()"
was erroneously instantiated as Item_func_substr_oracle.

Fix:

The fix proposes a symmetric solution. It provides a way to re-parse reliably
all sql_mode dependent functions to their original CREATE TABLE time meaning,
no matter what the open-time sql_mode is.

We take advantage of the same idea we previously used to resolve sql_mode
dependent data types.

Now all sql_mode dependent functions are printed by SHOW using a schema
qualifier when the current sql_mode differs from the function sql_mode:

SET sql_mode='';
CREATE TABLE t1 ... SUBSTR(a,b,c) ..;
SET sql_mode=ORACLE;
SHOW CREATE TABLE t1;   ->   mariadb_schema.substr(a,b,c)

SET sql_mode=ORACLE;
CREATE TABLE t2 ... SUBSTR(a,b,c) ..;
SET sql_mode='';
SHOW CREATE TABLE t1;   ->   oracle_schema.substr(a,b,c)

Old replacement names like substr_oracle() are still understood for
backward compatibility and used in FRM files (for downgrade compatibility),
but they are not printed by SHOW any more.
This commit is contained in:
Alexander Barkov 2022-04-04 14:50:21 +04:00
commit 2b6d241ee4
34 changed files with 3754 additions and 126 deletions

View file

@ -56,8 +56,40 @@ protected:
bool check_argument_types_can_return_date(uint start, uint end) const;
bool check_argument_types_can_return_time(uint start, uint end) const;
void print_cast_temporal(String *str, enum_query_type query_type);
void print_schema_qualified_name(String *to,
const LEX_CSTRING &schema_name,
const char *function_name) const
{
// e.g. oracle_schema.func()
to->append(schema_name);
to->append('.');
to->append(function_name);
}
void print_sql_mode_qualified_name(String *to,
enum_query_type query_type,
const char *function_name) const
{
const Schema *func_schema= schema();
if (!func_schema || func_schema == Schema::find_implied(current_thd))
to->append(function_name);
else
print_schema_qualified_name(to, func_schema->name(), function_name);
}
void print_sql_mode_qualified_name(String *to, enum_query_type query_type)
const
{
return print_sql_mode_qualified_name(to, query_type, func_name());
}
public:
// Print an error message for a builtin-schema qualified function call
static void wrong_param_count_error(const LEX_CSTRING &schema_name,
const LEX_CSTRING &func_name);
table_map not_null_tables_cache;
enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC,
@ -179,7 +211,13 @@ public:
List<Item> &fields, uint flags);
virtual void print(String *str, enum_query_type query_type);
void print_op(String *str, enum_query_type query_type);
void print_args(String *str, uint from, enum_query_type query_type);
void print_args(String *str, uint from, enum_query_type query_type) const;
void print_args_parenthesized(String *str, enum_query_type query_type) const
{
str->append('(');
print_args(str, 0, query_type);
str->append(')');
}
bool is_null() {
update_null_value();
return null_value;