diff options
author | Varun Gupta <varunraiko1803@gmail.com> | 2017-11-23 10:38:04 +0200 |
---|---|---|
committer | Vicențiu Ciorbaru <vicentiu@mariadb.org> | 2017-12-04 13:22:29 +0200 |
commit | c12d1ed48ee0708b85fbdbe149d15f6fea2c1e31 (patch) | |
tree | edc7283e665d576945f85267e70c45ae22fd27cb | |
parent | b213f57dc3f9da93ce444805f7581d982bde9f75 (diff) | |
download | mariadb-git-c12d1ed48ee0708b85fbdbe149d15f6fea2c1e31.tar.gz |
Refactor parts of Item_func_sp into Item_sp
In preparation for implementing custom aggregate functions, refactor
the common code between regular stored functions and aggregate stored
functions. This includes:
* initialising SP result field
* executing a SP
* access checks
In addition, refactor sp_head::execute_function to take two extra
parameters, a function rcontext and a Query_arena. These two paremeters
were initially initialised and destroyed within
sp_head::execute_function, but for aggregate stored functions we will
require control over their lifetime. The owner of these objects now
becomes Item_sp.
Signed-off-by: Vicențiu Ciorbaru <vicentiu@mariadb.org>
-rw-r--r-- | sql/item.cc | 231 | ||||
-rw-r--r-- | sql/item.h | 28 | ||||
-rw-r--r-- | sql/item_func.cc | 221 | ||||
-rw-r--r-- | sql/item_func.h | 19 | ||||
-rw-r--r-- | sql/sp_head.cc | 53 | ||||
-rw-r--r-- | sql/sp_head.h | 3 |
6 files changed, 296 insertions, 259 deletions
diff --git a/sql/item.cc b/sql/item.cc index 3ed43de5739..250f6707d4e 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2730,6 +2730,237 @@ Item* Item_func_or_sum::build_clone(THD *thd) return copy; } +Item_sp::Item_sp(THD *thd, Name_resolution_context *context_arg, + sp_name *name_arg) : + context(context_arg), m_name(name_arg), m_sp(NULL), func_ctx(NULL), + sp_result_field(NULL) +{ + dummy_table= (TABLE*) thd->calloc(sizeof(TABLE) + sizeof(TABLE_SHARE)); + dummy_table->s= (TABLE_SHARE*) (dummy_table + 1); + memset(&sp_mem_root, 0, sizeof(sp_mem_root)); +} + +const char * +Item_sp::func_name(THD *thd) const +{ + /* Calculate length to avoid reallocation of string for sure */ + uint len= (((m_name->m_explicit_name ? m_name->m_db.length : 0) + + m_name->m_name.length)*2 + //characters*quoting + 2 + // ` and ` + (m_name->m_explicit_name ? + 3 : 0) + // '`', '`' and '.' for the db + 1 + // end of string + ALIGN_SIZE(1)); // to avoid String reallocation + String qname((char *)alloc_root(thd->mem_root, len), len, + system_charset_info); + + qname.length(0); + if (m_name->m_explicit_name) + { + append_identifier(thd, &qname, m_name->m_db.str, m_name->m_db.length); + qname.append('.'); + } + append_identifier(thd, &qname, m_name->m_name.str, m_name->m_name.length); + return qname.c_ptr_safe(); +} + +void +Item_sp::cleanup() +{ + delete sp_result_field; + sp_result_field= NULL; + m_sp= NULL; + delete func_ctx; + func_ctx= NULL; + free_root(&sp_mem_root, MYF(0)); + dummy_table->alias.free(); +} + +/** + @brief Checks if requested access to function can be granted to user. + If function isn't found yet, it searches function first. + If function can't be found or user don't have requested access + error is raised. + + @param thd thread handler + + @return Indication if the access was granted or not. + @retval FALSE Access is granted. + @retval TRUE Requested access can't be granted or function doesn't exists. + +*/ +bool +Item_sp::sp_check_access(THD *thd) +{ + DBUG_ENTER("Item_sp::sp_check_access"); + DBUG_ASSERT(m_sp); + DBUG_RETURN(m_sp->check_execute_access(thd)); +} + +/** + @brief Execute function & store value in field. + + @return Function returns error status. + @retval FALSE on success. + @retval TRUE if an error occurred. +*/ +bool Item_sp::execute(THD *thd, bool *null_value, Item **args, uint arg_count) +{ + if (execute_impl(thd, args, arg_count)) + { + *null_value= 1; + context->process_error(thd); + if (thd->killed) + thd->send_kill_message(); + return true; + } + + /* Check that the field (the value) is not NULL. */ + + *null_value= sp_result_field->is_null(); + return (*null_value); +} + +/** + @brief Execute function and store the return value in the field. + + @note This function was intended to be the concrete implementation of + the interface function execute. This was never realized. + + @return The error state. + @retval FALSE on success + @retval TRUE if an error occurred. +*/ +bool +Item_sp::execute_impl(THD *thd, Item **args, uint arg_count) +{ + Sub_statement_state statement_state; + Security_context *save_security_ctx= thd->security_ctx; + enum enum_sp_data_access access= + (m_sp->daccess() == SP_DEFAULT_ACCESS) ? + SP_DEFAULT_ACCESS_MAPPING : m_sp->daccess(); + + DBUG_ENTER("Item_sp::execute_impl"); + + if (context->security_ctx) + { + /* Set view definer security context */ + thd->security_ctx= context->security_ctx; + } + + if (sp_check_access(thd)) + { + thd->security_ctx= save_security_ctx; + DBUG_RETURN(TRUE); + } + + /* + Throw an error if a non-deterministic function is called while + statement-based replication (SBR) is active. + */ + + if (!m_sp->detistic() && !trust_function_creators && + (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) && + (mysql_bin_log.is_open() && + thd->variables.binlog_format == BINLOG_FORMAT_STMT)) + { + my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0)); + thd->security_ctx= save_security_ctx; + DBUG_RETURN(TRUE); + } + + /* + Disable the binlogging if this is not a SELECT statement. If this is a + SELECT, leave binlogging on, so execute_function() code writes the + function call into binlog. + */ + thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION); + + init_sql_alloc(&sp_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); + Query_arena call_arena(&sp_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP); + + bool err_status= m_sp->execute_function(thd, args, arg_count, + sp_result_field, &func_ctx, + &call_arena); + /* Free Items allocated during function execution. */ + delete func_ctx; + func_ctx= NULL; + call_arena.free_items(); + free_root(&sp_mem_root, MYF(0)); + memset(&sp_mem_root, 0, sizeof(sp_mem_root)); + + thd->restore_sub_statement_state(&statement_state); + + thd->security_ctx= save_security_ctx; + DBUG_RETURN(err_status); +} + + +/** + @brief Initialize the result field by creating a temporary dummy table + and assign it to a newly created field object. Meta data used to + create the field is fetched from the sp_head belonging to the stored + proceedure found in the stored procedure functon cache. + + @note This function should be called from fix_fields to init the result + field. It is some what related to Item_field. + + @see Item_field + + @param thd A pointer to the session and thread context. + + @return Function return error status. + @retval TRUE is returned on an error + @retval FALSE is returned on success. +*/ + +bool +Item_sp::init_result_field(THD *thd, sp_head *sp, uint max_length, + uint maybe_null, bool *null_value, LEX_CSTRING *name) +{ + DBUG_ENTER("Item_sp::init_result_field"); + + DBUG_ASSERT(m_sp == NULL); + DBUG_ASSERT(sp_result_field == NULL); + + if (!(m_sp= sp)) + { + my_missing_function_error (m_name->m_name, ErrConvDQName(m_name).ptr()); + context->process_error(thd); + DBUG_RETURN(TRUE); + } + + /* + A Field needs to be attached to a Table. + Below we "create" a dummy table by initializing + the needed pointers. + */ + dummy_table->alias.set("", 0, table_alias_charset); + dummy_table->in_use= thd; + dummy_table->copy_blobs= TRUE; + dummy_table->s->table_cache_key= empty_clex_str; + dummy_table->s->table_name= empty_clex_str; + dummy_table->maybe_null= maybe_null; + + if (!(sp_result_field= m_sp->create_result_field(max_length, name, + dummy_table))) + DBUG_RETURN(TRUE); + + if (sp_result_field->pack_length() > sizeof(result_buf)) + { + void *tmp; + if (!(tmp= thd->alloc(sp_result_field->pack_length()))) + DBUG_RETURN(TRUE); + sp_result_field->move_field((uchar*) tmp); + } + else + sp_result_field->move_field(result_buf); + + sp_result_field->null_ptr= (uchar *) null_value; + sp_result_field->null_bit= 1; + + DBUG_RETURN(FALSE); +} /** @brief diff --git a/sql/item.h b/sql/item.h index d21fc86ea48..574680bc86d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -4471,6 +4471,34 @@ public: Item* build_clone(THD *thd); }; +class sp_head; +class sp_name; +struct st_sp_security_context; + +class Item_sp +{ +public: + Name_resolution_context *context; + sp_name *m_name; + sp_head *m_sp; + TABLE *dummy_table; + uchar result_buf[64]; + sp_rcontext *func_ctx; + MEM_ROOT sp_mem_root; + + /* + The result field of the stored function. + */ + Field *sp_result_field; + Item_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name_arg); + const char *func_name(THD *thd) const; + void cleanup(); + bool sp_check_access(THD *thd); + bool execute(THD *thd, bool *null_value, Item **args, uint arg_count); + bool execute_impl(THD *thd, Item **args, uint arg_count); + bool init_result_field(THD *thd, sp_head *sp, uint max_length, + uint maybe_null, bool *null_value, LEX_CSTRING *name); +}; class Item_ref :public Item_ident { diff --git a/sql/item_func.cc b/sql/item_func.cc index 67269e4dd0e..d851b508daf 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -6238,35 +6238,24 @@ longlong Item_func_row_count::val_int() Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name): - Item_func(thd), context(context_arg), m_name(name), m_sp(NULL), sp_result_field(NULL) + Item_func(thd), Item_sp(thd, context_arg, name) { maybe_null= 1; - dummy_table= (TABLE*) thd->calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); - dummy_table->s= (TABLE_SHARE*) (dummy_table+1); } Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name_arg, List<Item> &list): - Item_func(thd, list), context(context_arg), m_name(name_arg), m_sp(NULL), - sp_result_field(NULL) + Item_func(thd, list), Item_sp(thd, context_arg, name_arg) { maybe_null= 1; - dummy_table= (TABLE*) thd->calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); - dummy_table->s= (TABLE_SHARE*) (dummy_table+1); } void Item_func_sp::cleanup() { - if (sp_result_field) - { - delete sp_result_field; - sp_result_field= NULL; - } - m_sp= NULL; - dummy_table->alias.free(); + Item_sp::cleanup(); Item_func::cleanup(); } @@ -6274,25 +6263,7 @@ const char * Item_func_sp::func_name() const { THD *thd= current_thd; - /* Calculate length to avoid reallocation of string for sure */ - uint len= (((m_name->m_explicit_name ? m_name->m_db.length : 0) + - m_name->m_name.length)*2 + //characters*quoting - 2 + // ` and ` - (m_name->m_explicit_name ? - 3 : 0) + // '`', '`' and '.' for the db - 1 + // end of string - ALIGN_SIZE(1)); // to avoid String reallocation - String qname((char *)alloc_root(thd->mem_root, len), len, - system_charset_info); - - qname.length(0); - if (m_name->m_explicit_name) - { - append_identifier(thd, &qname, m_name->m_db.str, m_name->m_db.length); - qname.append('.'); - } - append_identifier(thd, &qname, m_name->m_name.str, m_name->m_name.length); - return qname.c_ptr_safe(); + return Item_sp::func_name(thd); } @@ -6306,75 +6277,6 @@ void my_missing_function_error(const LEX_CSTRING &token, const char *func_name) /** - @brief Initialize the result field by creating a temporary dummy table - and assign it to a newly created field object. Meta data used to - create the field is fetched from the sp_head belonging to the stored - proceedure found in the stored procedure functon cache. - - @note This function should be called from fix_fields to init the result - field. It is some what related to Item_field. - - @see Item_field - - @param thd A pointer to the session and thread context. - - @return Function return error status. - @retval TRUE is returned on an error - @retval FALSE is returned on success. -*/ - -bool -Item_func_sp::init_result_field(THD *thd, sp_head *sp) -{ - TABLE_SHARE *share; - DBUG_ENTER("Item_func_sp::init_result_field"); - - DBUG_ASSERT(m_sp == NULL); - DBUG_ASSERT(sp_result_field == NULL); - - if (!(m_sp= sp)) - { - my_missing_function_error (m_name->m_name, ErrConvDQName(m_name).ptr()); - context->process_error(thd); - DBUG_RETURN(TRUE); - } - - /* - A Field need to be attached to a Table. - Below we "create" a dummy table by initializing - the needed pointers. - */ - - share= dummy_table->s; - dummy_table->alias.set("", 0, table_alias_charset); - dummy_table->maybe_null = maybe_null; - dummy_table->in_use= thd; - dummy_table->copy_blobs= TRUE; - share->table_cache_key= empty_clex_str; - share->table_name= empty_clex_str; - - if (!(sp_result_field= m_sp->create_result_field(max_length, &name, dummy_table))) - { - DBUG_RETURN(TRUE); - } - - if (sp_result_field->pack_length() > sizeof(result_buf)) - { - void *tmp; - if (!(tmp= thd->alloc(sp_result_field->pack_length()))) - DBUG_RETURN(TRUE); - sp_result_field->move_field((uchar*) tmp); - } - else - sp_result_field->move_field(result_buf); - - sp_result_field->null_ptr= (uchar *) &null_value; - sp_result_field->null_bit= 1; - DBUG_RETURN(FALSE); -} - - -/** @note Deterministic stored procedures are considered inexpensive. Consequently such procedures may be evaluated during optimization, @@ -6408,95 +6310,11 @@ void Item_func_sp::fix_length_and_dec() } -/** - @brief Execute function & store value in field. - - @return Function returns error status. - @retval FALSE on success. - @retval TRUE if an error occurred. -*/ - bool Item_func_sp::execute() { - THD *thd= current_thd; - /* Execute function and store the return value in the field. */ - - if (execute_impl(thd)) - { - null_value= 1; - context->process_error(thd); - if (thd->killed) - thd->send_kill_message(); - return TRUE; - } - - /* Check that the field (the value) is not NULL. */ - - null_value= sp_result_field->is_null(); - - return null_value; -} - - -/** - @brief Execute function and store the return value in the field. - - @note This function was intended to be the concrete implementation of - the interface function execute. This was never realized. - - @return The error state. - @retval FALSE on success - @retval TRUE if an error occurred. -*/ -bool -Item_func_sp::execute_impl(THD *thd) -{ - bool err_status= TRUE; - Sub_statement_state statement_state; - Security_context *save_security_ctx= thd->security_ctx; - enum enum_sp_data_access access= - (m_sp->daccess() == SP_DEFAULT_ACCESS) ? - SP_DEFAULT_ACCESS_MAPPING : m_sp->daccess(); - - DBUG_ENTER("Item_func_sp::execute_impl"); - - if (context->security_ctx) - { - /* Set view definer security context */ - thd->security_ctx= context->security_ctx; - } - if (sp_check_access(thd)) - goto error; - - /* - Throw an error if a non-deterministic function is called while - statement-based replication (SBR) is active. - */ - - if (!m_sp->detistic() && !trust_function_creators && - (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) && - (mysql_bin_log.is_open() && - thd->variables.binlog_format == BINLOG_FORMAT_STMT)) - { - my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0)); - goto error; - } - - /* - Disable the binlogging if this is not a SELECT statement. If this is a - SELECT, leave binlogging on, so execute_function() code writes the - function call into binlog. - */ - thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION); - err_status= m_sp->execute_function(thd, args, arg_count, sp_result_field); - thd->restore_sub_statement_state(&statement_state); - -error: - thd->security_ctx= save_security_ctx; - - DBUG_RETURN(err_status); + return Item_sp::execute(current_thd, &null_value, args, arg_count); } @@ -6561,29 +6379,6 @@ longlong Item_func_sqlcode::val_int() } -/** - @brief Checks if requested access to function can be granted to user. - If function isn't found yet, it searches function first. - If function can't be found or user don't have requested access - error is raised. - - @param thd thread handler - - @return Indication if the access was granted or not. - @retval FALSE Access is granted. - @retval TRUE Requested access can't be granted or function doesn't exists. - -*/ - -bool -Item_func_sp::sp_check_access(THD *thd) -{ - DBUG_ENTER("Item_func_sp::sp_check_access"); - DBUG_ASSERT(m_sp); - DBUG_RETURN(m_sp->check_execute_access(thd)); -} - - bool Item_func_sp::fix_fields(THD *thd, Item **ref) { @@ -6625,15 +6420,15 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) to make m_sp and result_field members available to fix_length_and_dec(), which is called from Item_func::fix_fields(). */ - res= init_result_field(thd, sp); + res= init_result_field(thd, sp, max_length, maybe_null, &null_value, &name); if (res) - DBUG_RETURN(res); + DBUG_RETURN(TRUE); res= Item_func::fix_fields(thd, ref); if (res) - DBUG_RETURN(res); + DBUG_RETURN(TRUE); if (thd->lex->is_view_context_analysis()) { diff --git a/sql/item_func.h b/sql/item_func.h index ed6f800fd17..07c6b5bb8d3 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -2732,26 +2732,12 @@ public: * */ -class sp_head; -class sp_name; -struct st_sp_security_context; - -class Item_func_sp :public Item_func +class Item_func_sp :public Item_func, + public Item_sp { private: - Name_resolution_context *context; - sp_name *m_name; - mutable sp_head *m_sp; - TABLE *dummy_table; - uchar result_buf[64]; - /* - The result field of the concrete stored function. - */ - Field *sp_result_field; bool execute(); - bool execute_impl(THD *thd); - bool init_result_field(THD *thd, sp_head *sp); protected: bool is_expensive_processor(void *arg) @@ -2843,7 +2829,6 @@ public: virtual bool change_context_processor(void *cntx) { context= (Name_resolution_context *)cntx; return FALSE; } - bool sp_check_access(THD * thd); virtual enum Functype functype() const { return FUNC_SP; } bool fix_fields(THD *thd, Item **ref); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 829e4cc6414..62e198efb9e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1675,18 +1675,16 @@ err_with_cleanup: bool sp_head::execute_function(THD *thd, Item **argp, uint argcount, - Field *return_value_fld) + Field *return_value_fld, sp_rcontext **func_ctx, + Query_arena *call_arena) { ulonglong UNINIT_VAR(binlog_save_options); bool need_binlog_call= FALSE; uint arg_no; sp_rcontext *octx = thd->spcont; - sp_rcontext *nctx = NULL; char buf[STRING_BUFFER_USUAL_SIZE]; String binlog_buf(buf, sizeof(buf), &my_charset_bin); bool err_status= FALSE; - MEM_ROOT call_mem_root; - Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP); Query_arena backup_arena; DBUG_ENTER("sp_head::execute_function"); DBUG_PRINT("info", ("function %s", m_name.str)); @@ -1719,23 +1717,25 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, TODO: we should create sp_rcontext once per command and reuse it on subsequent executions of a function/trigger. */ - init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); - thd->set_n_backup_active_arena(&call_arena, &backup_arena); - - if (!(nctx= rcontext_create(thd, return_value_fld, argp, argcount))) + if (!(*func_ctx)) { - thd->restore_active_arena(&call_arena, &backup_arena); - err_status= TRUE; - goto err_with_cleanup; - } + thd->set_n_backup_active_arena(call_arena, &backup_arena); - /* - We have to switch temporarily back to callers arena/memroot. - Function arguments belong to the caller and so the may reference - memory which they will allocate during calculation long after - this function call will be finished (e.g. in Item::cleanup()). - */ - thd->restore_active_arena(&call_arena, &backup_arena); + if (!(*func_ctx= rcontext_create(thd, return_value_fld, argp, argcount))) + { + thd->restore_active_arena(call_arena, &backup_arena); + err_status= TRUE; + goto err_with_cleanup; + } + + /* + We have to switch temporarily back to callers arena/memroot. + Function arguments belong to the caller and so the may reference + memory which they will allocate during calculation long after + this function call will be finished (e.g. in Item::cleanup()). + */ + thd->restore_active_arena(call_arena, &backup_arena); + } /* Pass arguments. */ for (arg_no= 0; arg_no < argcount; arg_no++) @@ -1743,7 +1743,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, /* Arguments must be fixed in Item_func_sp::fix_fields */ DBUG_ASSERT(argp[arg_no]->fixed); - if ((err_status= nctx->set_variable(thd, arg_no, &(argp[arg_no])))) + if ((err_status= (*func_ctx)->set_variable(thd, arg_no, &(argp[arg_no])))) goto err_with_cleanup; } @@ -1775,7 +1775,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, if (arg_no) binlog_buf.append(','); - Item *item= nctx->get_item(arg_no); + Item *item= (*func_ctx)->get_item(arg_no); str_value= item->type_handler()->print_item_value(thd, item, &str_value_holder); if (str_value) @@ -1785,7 +1785,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } binlog_buf.append(')'); } - thd->spcont= nctx; + thd->spcont= *func_ctx; #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *save_security_ctx; @@ -1826,11 +1826,11 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, sp_rcontext and allocate all these objects (and sp_rcontext itself) on it directly rather than juggle with arenas. */ - thd->set_n_backup_active_arena(&call_arena, &backup_arena); + thd->set_n_backup_active_arena(call_arena, &backup_arena); err_status= execute(thd, TRUE); - thd->restore_active_arena(&call_arena, &backup_arena); + thd->restore_active_arena(call_arena, &backup_arena); if (need_binlog_call) { @@ -1860,7 +1860,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, { /* We need result only in function but not in trigger */ - if (!nctx->is_return_value_set()) + if (!(*func_ctx)->is_return_value_set()) { my_error(ER_SP_NORETURNEND, MYF(0), m_name.str); err_status= TRUE; @@ -1872,9 +1872,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, #endif err_with_cleanup: - delete nctx; - call_arena.free_items(); - free_root(&call_mem_root, MYF(0)); thd->spcont= octx; /* diff --git a/sql/sp_head.h b/sql/sp_head.h index 8d836732a10..1a994bdf70d 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -343,7 +343,8 @@ public: GRANT_INFO *grant_info); bool - execute_function(THD *thd, Item **args, uint argcount, Field *return_fld); + execute_function(THD *thd, Item **args, uint argcount, Field *return_fld, + sp_rcontext **nctx, Query_arena *call_arena); bool execute_procedure(THD *thd, List<Item> *args); |