Commit c12d1ed4 authored by Varun Gupta's avatar Varun Gupta Committed by Vicențiu Ciorbaru

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: default avatarVicențiu Ciorbaru <vicentiu@mariadb.org>
parent b213f57d
......@@ -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
......
......@@ -4471,6 +4471,34 @@ class Item_func_or_sum: public Item_result_field,
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
{
......
......@@ -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);
}
......@@ -6305,75 +6276,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.
......@@ -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())
{
......
......@@ -2732,26 +2732,12 @@ class Item_func_row_count :public Item_longlong_func
*
*/
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 @@ class Item_func_sp :public Item_func
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);
......
......@@ -1675,18 +1675,16 @@ sp_head::execute_trigger(THD *thd,
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;
/*
......
......@@ -343,7 +343,8 @@ class sp_head :private Query_arena,
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment