← Back to team overview

maria-developers team mailing list archive

Re: d67c3f88883: MDEV-27744 InnoDB: Failing assertion: !cursor->index->is_committed() in row0ins.cc (from row_ins_sec_index_entry_by_modify) | Assertion `0' failed in row_upd_sec_index_entry (debug) | Corruption

 

Hi, Alexander,

A question: if the frm will *always* be created in the canonical,
non-ORACLE mode and always parsed in the same mode, will we even need
these schema-qualified native functions?

okay, the schema-qualified trick will help to avoid creating
REGEX_REPLACE_ORACLE and so on. But is it needed to fix MDEV-27744?

More comments below

On Apr 27, Alexander Barkov wrote:
> revision-id: d67c3f88883 (mariadb-10.3.33-61-gd67c3f88883)
> parent(s): 7355f7b1f5c
> author: Alexander Barkov
> committer: Alexander Barkov
> timestamp: 2022-04-22 15:35:16 +0400
> message:
> 
> MDEV-27744 InnoDB: Failing assertion: !cursor->index->is_committed() in row0ins.cc (from row_ins_sec_index_entry_by_modify) | Assertion `0' failed in row_upd_sec_index_entry (debug) | Corruption
> 
> 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:
...
good comment

> diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
> index 59d2ea60715..8e83ee02782 100644
> --- a/sql/sql_partition.cc
> +++ b/sql/sql_partition.cc
> @@ -2591,11 +2591,9 @@ char *generate_partition_syntax_for_frm(THD *thd, partition_info *part_info,
>                                          HA_CREATE_INFO *create_info,
>                                          Alter_info *alter_info)
>  {
> -  sql_mode_t old_mode= thd->variables.sql_mode;
> -  thd->variables.sql_mode &= ~MODE_ANSI_QUOTES;
> +  Sql_mode_save_for_frm_handling sql_mode_save(thd);

right. thanks

>    char *res= generate_partition_syntax(thd, part_info, buf_length,
>                                               true, create_info, alter_info);
> -  thd->variables.sql_mode= old_mode;
>    return res;
>  }
>  
> diff --git a/sql/unireg.cc b/sql/unireg.cc
> index efbb5e773b4..3b37944afa5 100644
> --- a/sql/unireg.cc
> +++ b/sql/unireg.cc
> @@ -652,6 +649,7 @@ static bool pack_expression(String *buf, Virtual_column_info *vcol,
>  static bool pack_vcols(String *buf, List<Create_field> &create_fields,
>                               List<Virtual_column_info> *check_constraint_list)
>  {
> +  Sql_mode_save_for_frm_handling sql_mode_save(current_thd);

well, it was done above pack_vcols(), because the caller had access to
the thd. Why not to keep it that way?

>    List_iterator<Create_field> it(create_fields);
>    Create_field *field;
>  
> diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
> index f41414f8ae9..0f5c7519d07 100644
> --- a/sql/item_cmpfunc.cc
> +++ b/sql/item_cmpfunc.cc
> @@ -3292,9 +3292,24 @@ void Item_func_case_simple::print(String *str, enum_query_type query_type)
>  }
>  
>  
> +Item_func_decode_oracle *
> +Item_func_decode_oracle::create(THD *thd, const LEX_CSTRING &name,
> +                                List<Item> *item_list)
> +{
> +  if (unlikely(!item_list || item_list->elements < 3))
> +  {
> +    wrong_param_count_error(oracle_schema_ref.name(), name);
> +    return NULL;
> +  }
> +  return new (thd->mem_root) Item_func_decode_oracle(thd, *item_list);
> +}

why? items are normally created in the Create_xxx::create* methods,
defined in item_create.cc. Why did you delegate that to static
methods in Item_func_xxx ?

> +
> +
>  void Item_func_decode_oracle::print(String *str, enum_query_type query_type)
>  {
> -  str->append(func_name());
> +  print_sql_mode_dependent_name(str, query_type,
> +                                oracle_schema_ref,
> +                                Item_func_decode_oracle::func_name());
>    str->append('(');
>    args[0]->print(str, query_type);
>    for (uint i= 1, count= when_count() ; i <= count; i++)
> diff --git a/sql/mysqld.h b/sql/mysqld.h
> index d40e1d170d0..54069c3cd5a 100644
> --- a/sql/mysqld.h
> +++ b/sql/mysqld.h
> @@ -741,12 +741,25 @@ enum enum_query_type
>                          QT_ITEM_SUBSELECT_ID_ONLY,
>  
>    QT_SHOW_SELECT_NUMBER= (1<<10),
> +
> +  //QT_ITEM_IDENT_DISABLE_DB_TABLE_NAMES= (1 <<11), -- this is taken in 10.5
> +
> +  /// Print sql_mode-dependent functions with the schema qualifier
> +  /// even if the currently implied (by sql_mode) schema is equal to
> +  /// to the function schema, e.g. mariadb_schema.concat('a').
> +  QT_ITEM_FUNC_FORCE_SCHEMA_NAME= (1 << 12),
> +
> +  /// Print for FRM file. Focus on parse-back.
> +  /// e.g. VIEW expressions and virtual column expressions
> +  QT_FOR_FRM= (1 << 13),
> +
>    /// This is used for EXPLAIN EXTENDED extra warnings / Be more detailed
>    /// Be more detailed than QT_EXPLAIN.
>    /// Perhaps we should eventually include QT_ITEM_IDENT_SKIP_CURRENT_DATABASE
>    /// here, as it would give better readable results
>    QT_EXPLAIN_EXTENDED=  QT_TO_SYSTEM_CHARSET|
> -                        QT_SHOW_SELECT_NUMBER,
> +                        QT_SHOW_SELECT_NUMBER|
> +                        QT_ITEM_FUNC_FORCE_SCHEMA_NAME,

don't do this. let's try to keep the behavior *exactly as before*
for people who don't switch between sql_modes all the time

on the second thought. why do you need QT_ITEM_FUNC_FORCE_SCHEMA_NAME at all?
Besides EXPLAIN EXTENDED (where we'd better not use it), it's only
used in generating view's sql for I_S. It's not supposed to be used for
generating views, so why would it need QT_ITEM_FUNC_FORCE_SCHEMA_NAME?
May be it can be deleted altogether?

>  
>    // If an expression is constant, print the expression, not the value
>    // it evaluates to. Should be used for error messages, so that they
> diff --git a/sql/sql_class.h b/sql/sql_class.h
> index 8268ebe0bcd..6b988938f0b 100644
> --- a/sql/sql_class.h
> +++ b/sql/sql_class.h
> @@ -6589,6 +6589,50 @@ class Sql_mode_save
>    sql_mode_t old_mode; // SQL mode saved at construction time.
>  };
>  
> +
> +/*
> +  Save the current sql_mode. Switch off sql_mode flags which can prevent
> +  normal parsing of VIEWs, expressions in generated columns.
> +  Restore the old sql_mode on destructor.
> +*/
> +class Sql_mode_save_for_frm_handling: public Sql_mode_save
> +{
> +public:
> +  Sql_mode_save_for_frm_handling(THD *thd)
> +   :Sql_mode_save(thd)
> +  {
> +    /*
> +      - MODE_REAL_AS_FLOAT            affect only CREATE TABLE parsing
> +      + MODE_PIPES_AS_CONCAT          affect expression parsing
> +      + MODE_ANSI_QUOTES              affect expression parsing
> +      + MODE_IGNORE_SPACE             affect expression parsing
> +      - MODE_IGNORE_BAD_TABLE_OPTIONS affect only CREATE/ALTER TABLE parsing
> +      * MODE_ONLY_FULL_GROUP_BY       affect execution
> +      * MODE_NO_UNSIGNED_SUBTRACTION  affect execution
> +      - MODE_NO_DIR_IN_CREATE         affect table creation only
> +      - MODE_POSTGRESQL               compounded from other modes
> +      - MODE_ORACLE                   affects Item creation (e.g for CONCAT)

should be '+' for MODE_ORACLE, not '-', right?

> +      - MODE_MSSQL                    compounded from other modes
> +      - MODE_DB2                      compounded from other modes
> +      - MODE_MAXDB                    affect only CREATE TABLE parsing
> +      - MODE_NO_KEY_OPTIONS           affect only SHOW
> +      - MODE_NO_TABLE_OPTIONS         affect only SHOW
> +      - MODE_NO_FIELD_OPTIONS         affect only SHOW
> +      - MODE_MYSQL323                 affect only SHOW
> +      - MODE_MYSQL40                  affect only SHOW
> +      - MODE_ANSI                     compounded from other modes
> +                                      (+ transaction mode)
> +      ? MODE_NO_AUTO_VALUE_ON_ZERO    affect UPDATEs
> +      + MODE_NO_BACKSLASH_ESCAPES     affect expression parsing
> +      + MODE_EMPTY_STRING_IS_NULL     affect expression parsing
> +    */
> +    thd->variables.sql_mode&= ~(MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
> +                                MODE_IGNORE_SPACE | MODE_NO_BACKSLASH_ESCAPES |
> +                                MODE_ORACLE | MODE_EMPTY_STRING_IS_NULL);
> +  };
> +};
> +
> +
>  class Abort_on_warning_instant_set
>  {
>    THD *m_thd;
> diff --git a/sql/sql_schema.h b/sql/sql_schema.h
> index 7c8f284d526..36914e36990 100644
> --- a/sql/sql_schema.h
> +++ b/sql/sql_schema.h
> @@ -33,6 +36,34 @@ class Schema
>    {
>      return src;
>    }
> +
> +
> +  /**
> +    Find the native function builder associated with a given function name.
> +    @param thd The current thread
> +    @param name The native function name
> +    @return The native function builder associated with the name, or NULL
> +  */
> +  virtual Create_func *find_native_function_builder(THD *thd,
> +                                                    const LEX_CSTRING &name)
> +                                                    const;
> +  /**
> +    Find a native function builder, return an error if not found,
> +    build an Item otherwise.
> +  */
> +  Item *make_item_func_call_native(THD *thd,
> +                                   const Lex_ident_sys &name,
> +                                   List<Item> *args) const;
> +
> +  // Builders for native SQL function with a special syntax in sql_yacc.yy
> +  virtual Item *make_item_func_substr(THD *thd,
> +                                      const Lex_substring_spec_st &spec) const;
> +
> +  virtual Item *make_item_func_trim(THD *thd, const Lex_trim_st &spec) const;
> +  virtual Item *make_item_func_replace(THD *thd,
> +                                       Item *subj,
> +                                       Item *find,
> +                                       Item *replace) const;

Why no Lex_replace_spec_st ?

>    /*
>      For now we have *hard-coded* compatibility schemas:
>        schema_mariadb, schema_oracle, schema_maxdb.
> @@ -66,5 +97,6 @@ class Schema
>  
>  
>  extern Schema mariadb_schema;
> +extern const Schema &oracle_schema_ref;

??? why did you declare mariadb_schema extern, but oracle_schema_ref
only via a const ref?

>  
>  #endif // SQL_SCHEMA_H_INCLUDED
> diff --git a/sql/item_create.h b/sql/item_create.h
> index 894e9777b8d..3fadaecb090 100644
> --- a/sql/item_create.h
> +++ b/sql/item_create.h
> @@ -215,9 +205,48 @@ struct Native_func_registry
>    Create_func *builder;
>  };
>  
> +
> +class Native_functions_hash: public HASH
> +{
> +public:
> +  Native_functions_hash()
> +  {
> +    bzero(this, sizeof(*this));
> +  }
> +  ~Native_functions_hash()
> +  {
> +    /*
> +      No automatic free.
> +      The the upper level code should call cleanup() explicitly.

why is that?

> +    */
> +    DBUG_ASSERT(!records);
> +  }
> +  bool init(size_t count);
> +  bool init(const Native_func_registry array[], size_t count)
> +  {
> +    return init(count) || append(array);
> +  }
> +  bool append(const Native_func_registry array[]);
> +  bool remove(const Native_func_registry array[]);
> +  void cleanup();
> +  /**
> +    Find the native function builder associated with a given function name.
> +    @param thd The current thread
> +    @param name The native function name
> +    @return The native function builder associated with the name, or NULL
> +  */
> +  Create_func *find(THD *thd, const LEX_CSTRING &name) const;
> +};
> +
> +extern Native_functions_hash native_functions_hash;
> +extern Native_functions_hash native_functions_hash_oracle_overrides;
> +
> +extern const Native_func_registry func_array[];
> +extern const size_t func_array_length;
> +
>  int item_create_init();
> -int item_create_append(Native_func_registry array[]);
> -int item_create_remove(Native_func_registry array[]);
> +int item_create_append(const Native_func_registry array[]);
> +int item_create_remove(const Native_func_registry array[]);
>  void item_create_cleanup();
>  
>  Item *create_func_dyncol_create(THD *thd, List<DYNCALL_CREATE_DEF> &list);
> diff --git a/sql/sql_schema.cc b/sql/sql_schema.cc
> index 0bf4a63c2f8..e2993f51c6d 100644
> --- a/sql/sql_schema.cc
> +++ b/sql/sql_schema.cc
> @@ -78,3 +89,86 @@ Schema *Schema::find_implied(THD *thd)
>      return &maxdb_schema;
>    return &mariadb_schema;
>  }
> +
> +
> +Create_func *
> +Schema::find_native_function_builder(THD *thd, const LEX_CSTRING &name) const
> +{
> +  return native_functions_hash.find(thd, name);
> +}
> +
> +
> +Create_func *
> +Schema_oracle::find_native_function_builder(THD *thd,
> +                                            const LEX_CSTRING &name) const
> +{
> +  Create_func *create= native_functions_hash_oracle_overrides.find(thd, name);
> +  if (create)
> +    return create;
> +  return native_functions_hash.find(thd, name);

may be populate native_functions_hash_oracle will all names to 
do only one lookup here? this will be used often in oracle mode

> +}
> +
> +
> +Item *Schema::make_item_func_call_native(THD *thd,
> +                                         const Lex_ident_sys &name,
> +                                         List<Item> *args) const
> +{
> +  Create_func *builder= find_native_function_builder(thd, name);
> +  if (builder)
> +    return builder->create_func(thd, &name, args);
> +  my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), name.str);
> +  return NULL;
> +}
> +
> +
> +Item *Schema::make_item_func_trim(THD *thd, const Lex_trim_st &spec) const
> +{
> +  return spec.make_item_func_trim_std(thd);

why does trim is constructed via helper in spec,
but substr and replace are not?

> +}
> +
> +
> +Item *Schema::make_item_func_substr(THD *thd,
> +                                    const Lex_substring_spec_st &spec) const
> +{
> +  return spec.m_for ?
> +    new (thd->mem_root) Item_func_substr(thd, spec.m_subject, spec.m_from,
> +                                              spec.m_for) :
> +    new (thd->mem_root) Item_func_substr(thd, spec.m_subject, spec.m_from);
> +}
> +
> +
> +Item *Schema_oracle::make_item_func_substr(THD *thd,
> +                                    const Lex_substring_spec_st &spec) const
> +{
> +  return spec.m_for ?
> +    new (thd->mem_root) Item_func_substr_oracle(thd, spec.m_subject,
> +                                                     spec.m_from,
> +                                                     spec.m_for) :
> +    new (thd->mem_root) Item_func_substr_oracle(thd, spec.m_subject,
> +                                                     spec.m_from);
> +}
> +
> +
> +Item *Schema_oracle::make_item_func_trim(THD *thd,
> +                                         const Lex_trim_st &spec) const
> +{
> +  return spec.make_item_func_trim_oracle(thd);
> +}
> +
> +
> +Item *Schema::make_item_func_replace(THD *thd,
> +                                     Item *subj,
> +                                     Item *find,
> +                                     Item *replace) const
> +{
> +  return new (thd->mem_root) Item_func_replace(thd, subj, find, replace);
> +}
> +
> +
> +Item *Schema_oracle::make_item_func_replace(THD *thd,
> +                                            Item *subj,
> +                                            Item *find,
> +                                            Item *replace) const
> +{
> +  return new (thd->mem_root) Item_func_replace_oracle(thd, subj, find, replace);
> +}
> diff --git a/sql/item_func.h b/sql/item_func.h
> index 754b1cd1eb2..52bf7f661d1 100644
> --- a/sql/item_func.h
> +++ b/sql/item_func.h
> @@ -56,8 +56,117 @@ class Item_func :public Item_func_or_sum
>    bool check_argument_types_can_return_text(uint start, uint end) const;
>    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_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_name_with_optional_suffix(String *to,
> +                                       const char *function_name,
> +                                       const LEX_CSTRING &suffix) const
> +  {
> +    // e.g. func_oracle()
> +    to->append(function_name);
> +    if (suffix.length)
> +    {
> +      to->append("_");
> +      to->append(suffix);
> +    }
> +  }
> +
> +  void print_sql_mode_dependent_name(String *to, enum_query_type query_type,
> +                                     const Schema &schema,
> +                                     const char *function_name,
> +                                     const LEX_CSTRING &suffix) const
> +  {
> +    if (query_type & QT_ITEM_FUNC_FORCE_SCHEMA_NAME)
> +    {
> +      DBUG_ASSERT((query_type & QT_FOR_FRM) == 0);
> +      print_schema_qualified_name(to, schema.name(), function_name);
> +    }
> +    else if (query_type & QT_FOR_FRM)
> +    {
> +      // e.g. substr_oracle()
> +      DBUG_ASSERT((query_type & QT_ITEM_FUNC_FORCE_SCHEMA_NAME) == 0);
> +      print_name_with_optional_suffix(to, function_name, suffix);
> +    }
> +    else if (&schema != Schema::find_implied(current_thd))
> +      print_schema_qualified_name(to, schema.name(), function_name);
> +    else
> +      to->append(function_name);
> +  }
> +
> +  void print_sql_mode_dependent_name(String *to, enum_query_type query_type,
> +                                     const Schema &schema,
> +                                     const char *function_name) const
> +  {
> +    static const LEX_CSTRING oracle= {STRING_WITH_LEN("oracle")};

can it be oracle_schema.name() instead?

> +    static const LEX_CSTRING empty= {NULL, 0};
> +    const LEX_CSTRING suffix= &schema == &oracle_schema_ref ? oracle : empty;
> +    print_sql_mode_dependent_name(to, query_type,
> +                                  schema, function_name, suffix);
> +  }
> +  void print_sql_mode_dependent_name(String *to, enum_query_type query_type,
> +                                     const char *function_name) const
> +  {
> +    print_sql_mode_dependent_name(to, query_type, *schema(), function_name);
> +  }
> +  void print_sql_mode_dependent(String *to, enum_query_type query_type)
> +  {
> +    print_sql_mode_dependent_name(to, query_type, func_name());
> +    print_args_parenthesized(to, query_type);
> +  }
>  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);
> +
> +  // Check that the number of arguments is greater or equal to "expected"
> +  static bool create_check_args_ge(const LEX_CSTRING &name,
> +                                   const List<Item> *item_list,
> +                                   uint expected)
> +  {
> +    DBUG_ASSERT(expected > 0);
> +    if (!item_list || item_list->elements < expected)
> +    {
> +      my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
> +      return true;
> +    }
> +    return false;
> +  }
> +
> +  // Check that the number of arguments is less or equal to "expected"
> +  static bool create_check_args_le(const LEX_CSTRING &name,
> +                                   const List<Item> *item_list,
> +                                   uint expected)
> +  {
> +    DBUG_ASSERT(expected > 0);
> +    if (!item_list || item_list->elements > expected)
> +    {
> +      my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str);
> +      return true;
> +    }
> +    return false;
> +  }
> +
> +  // Check that the number of arguments is in the allowed range
> +  static bool create_check_args_between(const LEX_CSTRING &name,
> +                                        const List<Item> *item_list,
> +                                        uint min_arg_count,
> +                                        uint max_arg_count)
> +  {
> +    DBUG_ASSERT(min_arg_count < max_arg_count);
> +    return create_check_args_ge(name, item_list, min_arg_count) ||
> +           create_check_args_le(name, item_list, max_arg_count);
> +  }
> +
>    table_map not_null_tables_cache;
>  
>    enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC,
> diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
> index 3770488f3f4..e0bafc75a36 100644
> --- a/sql/sql_yacc.yy
> +++ b/sql/sql_yacc.yy
> @@ -11235,6 +11236,34 @@ function_call_generic:
>              if (unlikely(!($$= Lex->make_item_func_call_generic(thd, &$1, &$3, &$5, $7))))
>                MYSQL_YYABORT;
>            }
> +        | ident_cli '.' REPLACE '(' expr ',' expr ',' expr ')'
> +          {
> +            if (unlikely(!($$= Lex->make_item_func_replace(thd, $1, $3,
> +                                                           $5, $7, $9))))
> +              MYSQL_YYABORT;
> +          }
> +        | ident_cli '.' SUBSTRING '(' substring_operands ')'
> +          {
> +            if (unlikely(!($$= Lex->make_item_func_substr(thd, $1, $3, $5))))
> +              MYSQL_YYABORT;
> +          }
> +        | ident_cli '.' TRIM '(' trim_operands ')'
> +          {
> +            if (unlikely(!($$= $5.make_item_func_trim(thd, $1, $3))))
> +              MYSQL_YYABORT;
> +          }
> +          /*
> +            We don't add a qualified syntax for TRIM_ORACLE here,
> +            as this syntax is not absolutely required:
> +              SELECT mariadb_schema.TRIM_ORACLE(..);
> +            What absolutely required is only:
> +              SELECT mariadb_schema.TRIM(..);
> +            Adding a qualified syntax for TRIM_ORACLE would be tricky because
> +            it is a non-reserved keyword. To avoid new shift/reduce conflicts
> +            it would require grammar changes, like introducing a new rule
> +            ident_step2_cli (which would include everything that ident_cli
> +            includes but TRIM_ORACLE).

Did you add a qualified syntax for any other xxx_ORACLE functions?

> +          */
>          ;
>  
>  fulltext_options:
> diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
> index 7f1b362d91d..6b0a351f76e 100644
> --- a/sql/sql_lex.cc
> +++ b/sql/sql_lex.cc
> @@ -2008,7 +2023,59 @@ bool Lex_input_stream::get_7bit_or_8bit_ident(THD *thd, uchar *last_char)
>  }
>  
>  
> +/*
> +  Resolve special SQL functions that have a qualified syntax in sql_yacc.yy.
> +  These functions are not listed in the native function registry
> +  because of a special syntax, or a reserved keyword:
> +
> +    mariadb_schema.SUBSTRING('a' FROM 1 FOR 2)   -- Special syntax
> +    mariadb_schema.TRIM(BOTH ' ' FROM 'a')       -- Special syntax
> +    mariadb_schema.REPLACE('a','b','c')          -- Verb keyword
> +*/
> +
> +int Lex_input_stream::find_keyword_qualified_special_func(Lex_ident_cli_st *str,
> +                                                          uint length) const
> +{
> +  /*
> +    There are many other special functions, see the following grammar rules:
> +      function_call_keyword
> +      function_call_nonkeyword
> +    Here we resolve only those that have a qualified syntax to handle
> +    different behavior in different @@sql_mode settings.
> +
> +    Other special functions do not work in qualified context:
> +      SELECT mariadb_schema.year(now()); -- Function year is not defined
> +      SELECT mariadb_schema.now();       -- Function now is not defined
> +
> +    We don't resolve TRIM_ORACLE here, because it does not have
> +    a qualified syntax yet. Search for "trim_operands" in sql_yacc.yy
> +    to find more comments.
> +  */
> +  static LEX_CSTRING funcs[]=
> +  {
> +    {STRING_WITH_LEN("SUBSTRING")},
> +    {STRING_WITH_LEN("SUBSTR")},
> +    {STRING_WITH_LEN("TRIM")},
> +    {STRING_WITH_LEN("REPLACE")}
> +  };
> +
> +  int tokval= find_keyword(str, length, true);
> +  if (!tokval)
> +    return 0;
> +  for (size_t i= 0; i < array_elements(funcs); i++)
> +  {
> +    CHARSET_INFO *cs= system_charset_info;
> +    if (!cs->coll->strnncollsp(cs,

may be strcasecmp? or with cs=latin1?
you know the repertoire is ascii, so you can cut corners here.

> +                               (const uchar *) m_tok_start, length,
> +                               (const uchar *) funcs[i].str, funcs[i].length))
> +      return tokval;
> +  }
> +  return 0;
> +}
> +
> +
> -int Lex_input_stream::scan_ident_sysvar(THD *thd, Lex_ident_cli_st *str)
> +int Lex_input_stream::scan_ident_common(THD *thd, Lex_ident_cli_st *str,
> +                                        Ident_mode mode)
>  {
>    uchar last_char;
>    uint length;
> @@ -8035,30 +8133,49 @@ bool LEX::add_grant_command(THD *thd, enum_sql_command sql_command_arg,
>  }
>  
>  
> +const Schema *
> +LEX::find_func_schema_by_name_or_error(const Lex_ident_sys &schema,
> +                                       const Lex_ident_sys &func)
> +{
> +  Schema *res= Schema::find_by_name(schema);
> +  if (res)
> +    return res;
> +  Database_qualified_name qname(schema, func);
> +  my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), ErrConvDQName(&qname).ptr());

eh, not quite. I can define a function with the same name as a native
function in any db. E.g.

  create test.replace(a int) returns int

and it'll be callable as test.replace(1)
So ER_FUNCTION_NOT_DEFINED is wrong here.

But note that even though test.substr() is possible,
it should not allow substr() special syntax.

> +  return NULL;
> +}
> -Item *LEX::make_item_func_substr(THD *thd, Item *a, Item *b, Item *c)
> -{
> -  return (thd->variables.sql_mode & MODE_ORACLE) ?
> -    new (thd->mem_root) Item_func_substr_oracle(thd, a, b, c) :
> -    new (thd->mem_root) Item_func_substr(thd, a, b, c);
> -}
>  
>  
> -Item *LEX::make_item_func_substr(THD *thd, Item *a, Item *b)
> -{
> -  return (thd->variables.sql_mode & MODE_ORACLE) ?
> -    new (thd->mem_root) Item_func_substr_oracle(thd, a, b) :
> -    new (thd->mem_root) Item_func_substr(thd, a, b);
> -}
> +Item *LEX::make_item_func_substr(THD *thd,
> +                                 const Lex_ident_cli_st &schema_name_cli,
> +                                 const Lex_ident_cli_st &func_name_cli,
> +                                 const Lex_substring_spec_st &spec)
> +{
> +  Lex_ident_sys schema_name(thd, &schema_name_cli);
> +  Lex_ident_sys func_name(thd, &func_name_cli);
> +  if (schema_name.is_null() || func_name.is_null())
> +    return NULL; // EOM
> +  const Schema *schema= find_func_schema_by_name_or_error(schema_name,
> +                                                          func_name);
> +  return schema ? schema->make_item_func_substr(thd, spec) : NULL;
> +}
>  
>  
>  Item *LEX::make_item_func_replace(THD *thd,
> +                                  const Lex_ident_cli_st &schema_name_cli,
> +                                  const Lex_ident_cli_st &func_name_cli,
>                                    Item *org,
>                                    Item *find,
>                                    Item *replace)
>  {
> -  return (thd->variables.sql_mode & MODE_ORACLE) ?
> -    new (thd->mem_root) Item_func_replace_oracle(thd, org, find, replace) :
> -    new (thd->mem_root) Item_func_replace(thd, org, find, replace);
> +  Lex_ident_sys schema_name(thd, &schema_name_cli);
> +  Lex_ident_sys func_name(thd, &func_name_cli);
> +  if (schema_name.is_null() || func_name.is_null())
> +    return NULL; // EOM
> +  const Schema *schema= find_func_schema_by_name_or_error(schema_name,
> +                                                          func_name);
> +  return schema ? schema->make_item_func_replace(thd, org, find, replace) :
> +                  NULL;
>  }
>  
>  

Regards,
Sergei
VP of MariaDB Server Engineering
and security@xxxxxxxxxxx


Follow ups