← Back to team overview

maria-developers team mailing list archive

Re: Review for MDEV-6112 multiple triggers per table

 

Hello Monty,

This is a more detailed review for MDEV-6112.



> --- a/mysql-test/t/trigger-compat.test
> +++ b/mysql-test/t/trigger-compat.test

<cut>

> +--replace_column 17 #
> SELECT trigger_name, definer FROM INFORMATION_SCHEMA.TRIGGERS ORDER BY trigger_name;

The above replace_column is not needed.


> --- a/sql/sql_show.cc
> +++ b/sql/sql_show.cc
> <cut>
> @@ -6385,43 +6399,59 @@ static int get_schema_constraints_record(THD *thd, TABLE_LIST *tables,
>  }
>
>
> -static bool store_trigger(THD *thd, TABLE *table, LEX_STRING *db_name,
> - LEX_STRING *table_name, LEX_STRING *trigger_name,
> +static bool store_trigger(THD *thd, Trigger *trigger,
> +                          TABLE *table, LEX_STRING *db_name,
> +                          LEX_STRING *table_name,
>                            enum trg_event_type event,
> -                          enum trg_action_time_type timing,
> -                          LEX_STRING *trigger_stmt,
> -                          ulong sql_mode,
> -                          LEX_STRING *definer_buffer,
> -                          LEX_STRING *client_cs_name,
> -                          LEX_STRING *connection_cl_name,
> -                          LEX_STRING *db_cl_name)
> +                          enum trg_action_time_type timing)
>  {
>    CHARSET_INFO *cs= system_charset_info;
>    LEX_STRING sql_mode_rep;
> +  MYSQL_TIME timestamp;
> +  char definer_holder[USER_HOST_BUFF_SIZE];
> +  LEX_STRING definer_buffer, trigger_stmt, trigger_body;
> +  definer_buffer.str= definer_holder;
> +
> + trigger->get_trigger_info(&trigger_stmt, &trigger_body, &definer_buffer);
>
>    restore_record(table, s->default_values);
>    table->field[0]->store(STRING_WITH_LEN("def"), cs);
>    table->field[1]->store(db_name->str, db_name->length, cs);
> -  table->field[2]->store(trigger_name->str, trigger_name->length, cs);
> +  table->field[2]->store(trigger->name.str, trigger->name.length, cs);
>    table->field[3]->store(trg_event_type_names[event].str,
>                           trg_event_type_names[event].length, cs);
>    table->field[4]->store(STRING_WITH_LEN("def"), cs);
>    table->field[5]->store(db_name->str, db_name->length, cs);
>    table->field[6]->store(table_name->str, table_name->length, cs);
> -  table->field[9]->store(trigger_stmt->str, trigger_stmt->length, cs);
> +  table->field[7]->set_notnull();

Why is set_notnull() needed?
I_S.TRIGGERS.ACTION_ORDER is declared as NOT NULL.


> +  table->field[7]->store(trigger->action_order);
> +  table->field[9]->store(trigger_body.str, trigger_body.length, cs);
>    table->field[10]->store(STRING_WITH_LEN("ROW"), cs);
>    table->field[11]->store(trg_action_time_type_names[timing].str,
> trg_action_time_type_names[timing].length, cs);
>    table->field[14]->store(STRING_WITH_LEN("OLD"), cs);
>    table->field[15]->store(STRING_WITH_LEN("NEW"), cs);
>
> -  sql_mode_string_representation(thd, sql_mode, &sql_mode_rep);
> +  if (trigger->create_time)
> +  {
> +    table->field[16]->set_notnull();

You use set_notnull() here, but don't use it
in show_create_trigger_impl. See below.


<cut>

> @@ -9307,35 +9322,45 @@ int finalize_schema_table(st_plugin_int *plugin)
>    DBUG_RETURN(0);
>  }
>
> +/*
> +  This is used to create a timestamp field
> +*/
> +
> +MYSQL_TIME zero_time={ 0,0,0,0,0,0,0,0, MYSQL_TIMESTAMP_TIME };
> +
> +class Item_timestamp_literal: public Item_datetime_literal
> +{
> +public:
> +  Item_timestamp_literal(THD *thd, uint decimals):
> +    Item_datetime_literal(thd, &zero_time, decimals)
> +  {}
> +  enum_field_types field_type() const { return MYSQL_TYPE_TIMESTAMP; }
> +};
> +

Why you do need MYSQL_TYPE_TIMESTAMP here?

"CREATED" in triggers_fields_info is defined as MYSQL_TYPE_DATETIME.
"mysql --column-type-info" reports MYSQL_TYPE_DATETIME for "CREATED".

I tried to replace Item_timestamp_literal to Item_datetime_literal
and it seems to work fine. Please use Item_datetime_literal.


>
>  /**
>    Output trigger information (SHOW CREATE TRIGGER) to the client.
>
>    @param thd          Thread context.
> -  @param triggers     List of triggers for the table.
> -  @param trigger_idx  Index of the trigger to dump.
> +  @param trigger      Trigger to dump
>
>    @return Operation status
>      @retval TRUE Error.
>      @retval FALSE Success.
>  */
>
> -static bool show_create_trigger_impl(THD *thd,
> -                                     Table_triggers_list *triggers,
> -                                     int trigger_idx)
> +static bool show_create_trigger_impl(THD *thd, Trigger *trigger)
>  {
>    int ret_code;
>    Protocol *p= thd->protocol;
>    List<Item> fields;
> -  LEX_STRING trg_name;
> -  ulonglong trg_sql_mode;
> -  LEX_STRING trg_sql_mode_str;
> +  LEX_STRING trg_sql_mode_str, trg_body;
>    LEX_STRING trg_sql_original_stmt;
> -  LEX_STRING trg_client_cs_name;
> -  LEX_STRING trg_connection_cl_name;
> -  LEX_STRING trg_db_cl_name;
> +  LEX_STRING trg_definer;
>    CHARSET_INFO *trg_client_cs;
>    MEM_ROOT *mem_root= thd->mem_root;
> +  char definer_holder[USER_HOST_BUFF_SIZE];
> +  trg_definer.str= definer_holder;
>
>    /*
>      TODO: Check privileges here. This functionality will be added by
> @@ -9349,20 +9374,12 @@ static bool show_create_trigger_impl(THD *thd,
>
>    /* Prepare trigger "object". */
>
> -  triggers->get_trigger_info(thd,
> -                             trigger_idx,
> -                             &trg_name,
> -                             &trg_sql_mode,
> -                             &trg_sql_original_stmt,
> -                             &trg_client_cs_name,
> -                             &trg_connection_cl_name,
> -                             &trg_db_cl_name);
> -
> -  sql_mode_string_representation(thd, trg_sql_mode, &trg_sql_mode_str);
> + trigger->get_trigger_info(&trg_sql_original_stmt, &trg_body, &trg_definer); > + sql_mode_string_representation(thd, trigger->sql_mode, &trg_sql_mode_str);
>
>    /* Resolve trigger client character set. */
>
> -  if (resolve_charset(trg_client_cs_name.str, NULL, &trg_client_cs))
> + if (resolve_charset(trigger->client_cs_name.str, NULL, &trg_client_cs))
>      return TRUE;
>
>    /* Send header. */
> @@ -9425,18 +9446,30 @@ static bool show_create_trigger_impl(THD *thd,
>
<cut>

> +  if (trigger->create_time)
> +  {
> +    MYSQL_TIME timestamp;
> +    thd->variables.time_zone->gmt_sec_to_TIME(&timestamp,
> +                                              trigger->create_time/100);
> +    timestamp.second_part= (trigger->create_time % 100) * 10000;
> +    p->store(&timestamp, 2);

Not set_notnull() here, but there is set_notnull() in the same
place in store_trigger().
It seems both places should be identical.


> +  }
> +  else
> +    p->store_null();
> +
> +
>    ret_code= p->write();
>
>    if (!ret_code)
<cut>


> --- a/sql/sql_trigger.cc
> +++ b/sql/sql_trigger.cc
> @@ -34,30 +34,17 @@
>  #include "sp_cache.h"                     // sp_invalidate_cache
>  #include <mysys_err.h>
>
> -/*************************************************************************/
> -
> -template <class T>
> -inline T *alloc_type(MEM_ROOT *m)
> -{
> -  return (T *) alloc_root(m, sizeof (T));
> -}
> -
> -/*
> - NOTE: Since alloc_type() is declared as inline, alloc_root() calls should
> -  be inlined by the compiler. So, implementation of alloc_root() is not
> - needed. However, let's put the implementation in object file just in case
> -  of stupid MS or other old compilers.
> -*/
> -
> -template LEX_STRING *alloc_type<LEX_STRING>(MEM_ROOT *m);
> -template ulonglong *alloc_type<ulonglong>(MEM_ROOT *m);
> -
> -inline LEX_STRING *alloc_lex_string(MEM_ROOT *m)
> +LEX_STRING *make_lex_string(LEX_STRING *lex_str, const char* str, uint length,
> +                            MEM_ROOT *mem_root)
>  {
> -  return alloc_type<LEX_STRING>(m);
> +  if (!(lex_str->str= strmake_root(mem_root, str, length)))
> +    return 0;
> +  lex_str->length= length;
> +  return lex_str;

This make_lex_string() should probably go into some  *.h file,
and a very similar THD::make_lex_string() defined in sql_class.h
should reuse it. Exactly the same duplicate code.


<cut>

> @@ -220,6 +205,11 @@ static File_option triggers_file_parameters[]=
>      my_offsetof(class Table_triggers_list, db_cl_names),
>      FILE_OPTIONS_STRLIST
>    },
> +  {
> +    { C_STRING_WITH_LEN("created") },
> +    my_offsetof(class Table_triggers_list, create_times),
> +    FILE_OPTIONS_ULLLIST
> +  },
>    { { 0, 0 }, 0, FILE_OPTIONS_STRING }
>  };

"created" is stored in t1.TRG as (seconds*100+frac).
This is not future-proof. If we ever want to change
it to store more digits, we'll be in troubles.

Can we instead of this format:

created=147504759488

use a better one, with a dot followed by the fractional part:

created=1475047594.88


<cut>

> @@ -587,7 +612,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
>  end:
>    if (!result)
>    {
> - result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); > + result= write_bin_log(thd, TRUE, stmt_query.c_ptr(), stmt_query.length());

Why this change? Is it related to the MDEV itself?


<cut>

> @@ -609,8 +634,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
>  }
>
>  /**
> -  Build stmt_query to write it in the bin-log
> -  and get the trigger definer.
> +  Build stmt_query to write it in the bin-log, the statement to write in
> +  the trigger file and the trigger definer.
>
> @param thd current thread context (including trigger definition in
>                         LEX)
> @@ -618,7 +643,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
>                         trigger is created.
> @param[out] stmt_query after successful return, this string contains > well-formed statement for creation this trigger.
> -
> + @param[out] trigger_def query to be stored in trigger file. As stmt_query,
> +		           but without "OR REPLACE" and no FOLLOWS/PRECEDES.
>    @param[out] trg_definer         The triggger definer.
>    @param[out] trg_definer_holder  Used as a buffer for definer.
>
> @@ -630,12 +656,16 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
>      simultaneously NULL-strings (non-SUID/old trigger) or valid strings
>      (SUID/new trigger).
>  */
> +
>  static void build_trig_stmt_query(THD *thd, TABLE_LIST *tables,
> -                                  String *stmt_query,
> + String *stmt_query, String *trigger_def,
>                                    LEX_STRING *trg_definer,
>                                    char trg_definer_holder[])
>  {
> +  LEX_STRING stmt_definition;
>    LEX *lex= thd->lex;
> +  uint prefix_trimmed, suffix_trimmed;
> +  size_t original_length;
>
>    /*
>      Create a query with the full trigger definition.
> @@ -651,18 +683,42 @@ static void build_trig_stmt_query(THD *thd, TABLE_LIST *tables,
>      /* SUID trigger */
>      lex->definer->set_lex_string(trg_definer, trg_definer_holder);
> append_definer(thd, stmt_query, &lex->definer->user, &lex->definer->host); > + append_definer(thd, trigger_def, &lex->definer->user, &lex->definer->host);
>    }
>    else
>    {
>      *trg_definer= empty_lex_str;
>    }
>
> -  LEX_STRING stmt_definition;
> -  stmt_definition.str= (char*) thd->lex->stmt_definition_begin;
> -  stmt_definition.length= thd->lex->stmt_definition_end -
> -                          thd->lex->stmt_definition_begin;
> -  trim_whitespace(thd->charset(), &stmt_definition);
> +
> +  /* Create statement for binary logging */
> +  stmt_definition.str= (char*) lex->stmt_definition_begin;
> +  stmt_definition.length= (lex->stmt_definition_end -
> +                           lex->stmt_definition_begin);
> +  original_length= stmt_definition.length;
> +  trim_whitespace(thd->charset(), &stmt_definition, &prefix_trimmed);
> + suffix_trimmed= original_length - stmt_definition.length - prefix_trimmed;
> +
>    stmt_query->append(stmt_definition.str, stmt_definition.length);
> +
> +  /* Create statement for storing trigger (without trigger order) */
> +  if (lex->trg_chistics.ordering_clause == TRG_ORDER_NONE)
> +    trigger_def->append(stmt_definition.str, stmt_definition.length);
> +  else
> +  {
> +    /* Copy data before FOLLOWS/PRECEDES trigger_name */
> +    trigger_def->append(stmt_definition.str,
> +                        (lex->trg_chistics.ordering_clause_begin -
> +                         lex->stmt_definition_begin) - prefix_trimmed);
> +    /* Copy data after FOLLOWS/PRECEDES trigger_name */
> +    trigger_def->append(stmt_definition.str +
> +                        (lex->trg_chistics.ordering_clause_end -
> +                         lex->stmt_definition_begin)
> +                        - prefix_trimmed,
> +                        (lex->stmt_definition_end -
> +                         lex->trg_chistics.ordering_clause_end) -
> +                        suffix_trimmed);
> +  }
>  }

The above does not seem to be covered in the tests.

There should be two tests:
- in /suite/binlog/, to make sure that the query contains the FOLLOWS/PRECEDES
  part.
- in /suite/rpl/, to make sure that ACTION_ORDER is correctly reproduced
  on the slave side.


<cut>


> @@ -860,34 +887,91 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,

> +  lex_string_set(&trigger->connection_cl_name,
>                   thd->variables.collation_connection->name);
> -
> -  lex_string_set(trg_db_cl_name,
> +  lex_string_set(&trigger->db_cl_name,
>                   get_default_db_collation(thd, tables->db)->name);
>
> -  build_trig_stmt_query(thd, tables, stmt_query,
> -                        trg_definer, trg_definer_holder);
> -
> -  trg_def->str= stmt_query->c_ptr_safe();
> -  trg_def->length= stmt_query->length();
> +  /* Add trigger in it's correct place */
> +  if (add_trigger(lex->trg_chistics.event,
> +                  lex->trg_chistics.action_time,
> +                  lex->trg_chistics.ordering_clause,
> +                  &lex->trg_chistics.anchor_trigger_name,
> +                  trigger))
> +    goto err_with_cleanup;
>
> -  /* Create trigger definition file. */
> +  /* Create trigger definition file .TRG */
> +  if (create_lists_needed_for_files(thd->mem_root))
> +    goto err_with_cleanup;
>
>    if (!sql_create_definition_file(NULL, &file, &triggers_file_type,
> (uchar*)this, triggers_file_parameters))
> -    return false;
> +    DBUG_RETURN(false);

Looks like a memory leak. Isn't a "delete trigger" needed?


<cut>

> +Trigger *Table_triggers_list::find_trigger(const LEX_STRING *name,
> +                                           bool remove_from_list)
> +{
> +  for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
> +  {
> +    for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
> +    {
> +      Trigger **parent, *trigger;
> +
> +      parent= &triggers[i][j];
> +      for (parent= &triggers[i][j];

Looks like "parent= &triggers[i][j]" is done two times.

> @@ -979,85 +1104,70 @@ static bool save_trigger_file(Table_triggers_list *triggers, const char *db,
>    @retval
>      True    error
>  */
> +
>  bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables,
>                                         String *stmt_query)
>  {
> -  const char *sp_name= thd->lex->spname->m_name.str; // alias
> -
> -  LEX_STRING *name;
> +  const LEX_STRING *sp_name= &thd->lex->spname->m_name; // alias
>    char path[FN_REFLEN];
> +  Trigger *trigger;
>
> -  List_iterator_fast<LEX_STRING> it_name(names_list);
> -
> -  List_iterator<ulonglong> it_mod(definition_modes_list);
> -  List_iterator<LEX_STRING> it_def(definitions_list);
> -  List_iterator<LEX_STRING> it_definer(definers_list);
> -  List_iterator<LEX_STRING> it_client_cs_name(client_cs_names);
> -  List_iterator<LEX_STRING> it_connection_cl_name(connection_cl_names);
> -  List_iterator<LEX_STRING> it_db_cl_name(db_cl_names);
> + stmt_query->set(thd->query(), thd->query_length(), stmt_query->charset());
>
> -  stmt_query->append(thd->query(), thd->query_length());
> -
> -  while ((name= it_name++))
> +  /* Find and delete trigger from list */
> +  if (!(trigger= find_trigger(sp_name, true)))
>    {
> -    it_def++;
> -    it_mod++;
> -    it_definer++;
> -    it_client_cs_name++;
> -    it_connection_cl_name++;
> -    it_db_cl_name++;
> -
> -    if (my_strcasecmp(table_alias_charset, sp_name, name->str) == 0)
> -    {
> -      /*
> -        Again we don't care much about other things required for
> -        clean trigger removing since table will be reopened anyway.
> -      */
> -      it_def.remove();
> -      it_mod.remove();
> -      it_definer.remove();
> -      it_client_cs_name.remove();
> -      it_connection_cl_name.remove();
> -      it_db_cl_name.remove();
> -
> -      if (definitions_list.is_empty())
> -      {
> -        /*
> -          TODO: Probably instead of removing .TRG file we should move
> -          to archive directory but this should be done as part of
> -          parse_file.cc functionality (because we will need it
> -          elsewhere).
> -        */
> -        if (rm_trigger_file(path, tables->db, tables->table_name))
> -          return 1;
> -      }
> -      else
> -      {
> -        if (save_trigger_file(this, tables->db, tables->table_name))
> -          return 1;
> -      }
> + my_message(ER_TRG_DOES_NOT_EXIST, ER_THD(thd, ER_TRG_DOES_NOT_EXIST),
> +               MYF(0));
> +    return 1;
> +  }
>
> -      if (rm_trigname_file(path, tables->db, sp_name))
> -        return 1;
> -      return 0;
> -    }
> +  if (!count)                                   // If no more triggers
> +  {
> +    /*
> +      TODO: Probably instead of removing .TRG file we should move
> +      to archive directory but this should be done as part of
> +      parse_file.cc functionality (because we will need it
> +      elsewhere).
> +    */
> +    if (rm_trigger_file(path, tables->db, tables->table_name))
> +      return 1;
>    }
> +  else
> +  {
> +    if (save_trigger_file(thd, tables->db, tables->table_name))
> +      return 1;
> +  }
> +
> +  if (rm_trigname_file(path, tables->db, sp_name->str))
> +    return 1;

Can you please clarify why these  two files are deleted:
tables->table_name and sp_name->table_name ?


<cut>

>  /**
> +   Add trigger in the correct position according to ordering clause
> +   Also update action order
> +
> +   If anchor_trigger doesn't exist, add it last
> +*/
> +
> +bool Table_triggers_list::add_trigger(trg_event_type event,
> +                                      trg_action_time_type action_time,
> + trigger_order_type ordering_clause,
> +                                      LEX_STRING *anchor_trigger_name,
> +                                      Trigger *trigger)

st_trg_execution_order can be passed here,
instead of separate ordering_clause and anchor_trigger_name.

<cut>

> --- a/sql/sql_yacc.yy
> +++ b/sql/sql_yacc.yy
<cut>
> @@ -16494,14 +16531,16 @@ trigger_tail:
>              lex->ident.str= $9;
>              lex->ident.length= $13 - $9;
>              lex->spname= $5;
> + (*static_cast<st_trg_execution_order*>(&lex->trg_chistics))= ($18);
> +            lex->trg_chistics.ordering_clause_end= lip->get_cpp_ptr();
>
>              if (!make_sp_head(thd, $5, TYPE_ENUM_TRIGGER))
>                MYSQL_YYABORT;
>
> -            lex->sphead->set_body_start(thd, lip->get_cpp_ptr());
> +            lex->sphead->set_body_start(thd, lip->get_cpp_tok_start());

Can you clarify please why this change ^^^^ ?




On 09/27/2016 04:33 PM, Alexander Barkov wrote:
Hello Monty.


I checked your patch for MDEV-6112 multiple triggers per table.

Here are some quick notes. I'll send a detailed review separately.


1. It would be nice to push refactoring changes in separate patch:

   a. do sql_mode_t refactoring
   b. "trg_idx -> Trigger *" refactoring
   c. opt_debugging related things


2. Why not to reuse the FOLLOWING_SYM and PRECEDING_SYM keywords
    instead of adding new ones FOLLOWS_SYM, PRECEDES_SYM ?
    (this is a non-standard syntax anyway)

3. I'd prefer PRECEDES/FOLLOWS to go before FOR EACH ROW.
"FOR EACH ROW stmt" is a kind of single clause responsible for the action.
It's very confusing to have PRECEDES/FOLLOWS characteristics in between
"FOR EACH ROW" and stmt.


4. You have a lot of changes related to trim_whitespace() where you declare
an unused "prefix_removed" variable. It would be nice to avoid declaring
it.

Possible ways:

a. Perhaps the new function version can be defined like this:

size_t trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str);

and make it return "prefix length removed", instead of adding a new
parameter.

b. Another option is just to overload it:

extern void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str,
                             uint *prefix_removed);
inline void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str)
{
   uint not_used;
   trim_whitespace(cs, str, &not_used);
}


5. The "CREATED" field is not populated well. I'm getting strange values
like 1970-04-24 09:35:00.30.

Btw, his query returns a correct value:
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(CREATED)*100)
FROM INFORMATION_SCHEMA.triggers;

You must have forgotten to multiply or delete to 100 somewhere.


6. In replication tests it's better to make sure that CREATED is safely
replicated (instead of changing it to #).

By the way, in other tests it's also a good idea not to use # for CREATED.
(see the previous problem)

You can use this:
SET TIMESTAMP=UNIX_TIMESTAMP('2001-01-01 10:20:30');
...
SET TIMESTAM=DEFAULT;


7. I suggest to move this code into a method, say find_trigger():


+  if (table->triggers)
+  {
+    Trigger *t;
+    for (t= table->triggers->get_trigger(TRG_EVENT_INSERT,
+                                         TRG_ACTION_BEFORE);
+         t;
+         t= t->next)
+      if (t->is_fields_updated_in_trigger(&full_part_field_set))
+        DBUG_RETURN(false);
+  }

It's repeated at least two times.


8. The following structure fields are defined two times:

   enum trigger_order_type ordering_clause;
   LEX_STRING anchor_trigger_name;

The first time for st_trg_chistics.
The second time for %union in sql_yacc.yy
Note, there will be the third time in sql_yacc_ora.yy.
Please define a structure in sql_lex.h instead:


struct st_trg_execution_order_chistics
{
   /**
     FOLLOWS or PRECEDES as specified in the CREATE TRIGGER statement.
   */
   enum trigger_order_type ordering_clause;

   /**
     Trigger name referenced in the FOLLOWS/PRECEDES clause of the
     CREATE TRIGGER statement.
   */
   LEX_STRING anchor_trigger_name;
};


and reuse it in here:

struct st_trg_chirstics: public st_trg_execution_order_chistics
{
  ...
};

and in here:

%union {
   ...
   st_trg_execition_order_chistics trg_execution_order_chirstics;
   ...
};


Please also rename trg_characteristics to trg_execution_order_chistics,
because "trg_characteristics" is more suitable to something having type
st_trg_chirstics rather than st_trg_execution_order_chistics.


Greetings.


References