Commit 465c81b3 authored by Dmitry Shulga's avatar Dmitry Shulga

MDEV-5816: Stored programs: validation of stored program statements

Added re-parsing of failed statements inside a stored routine.

General idea of the patch is to install an instance of the class
Reprepare_observer before executing a next SP instruction and
re-parse a statement of this SP instruction in case of
its execution failure.

To implement the described approach the class sp_lex_keeper
has been extended with the method validate_lex_and_exec_core()
that is just a wrapper around the method reset_lex_and_exec_core()
with additional setting/resetting an instance of the class
Reprepare_observer on each iteration of SP instruction
execution.

If reset_lex_and_exec_core() returns error and an instance
of the class Reprepare_observer is installed before running
a SP instruction then a number of attempts to re-run the SP
instruction is checked against a max. limit and in case it doesn't
reach the limit a statement for the failed SP instruction is re-parsed.

Re-parsing of a statement for the failed SP instruction is implemented
by the new method sp_le_inst::parse_expr() that prepends
a SP instruction's statement with the clause 'SELECT' and parse it.
Own SP instruction MEM_ROOT and a separate free_list is used for
parsing of a SP statement. On successful re-parsing of SP instruction's
statement the virtual methods adjust_sql_command() and
on_after_expr_parsing() of the class sp_lex_instr is called
to update the SP instruction state with a new data created
on parsing the statement.

Few words about reason for prepending a SP instruction's statement
with the clause 'SELECT' - this is required step to produce a valid
SQL statement, since for some SP instructions the instructions statement
is not a valid SQL statement. Wrapping such text into 'SELECT ( )'
produces a correct operator from SQL syntax point of view.
parent 5a8b9a16
......@@ -831,6 +831,45 @@ Silence_deprecated_warning::handle_condition(
}
/**
Make a copy of a SQL statement used for creation of a stored routine.
@param defstr Original SQL statement that is used for creation
a stored routine
@param sp_mem_root Memory root where a copy of original SQL statement should
be placed.
@return a copy of an original CREATE PROCEDURE/FUNCTION/EVENT/TRIGGER
SQL statement wrapped into an instance of LEX_STRING.
The data member LEX_STRING.str of returning object is set nullptr
in case of error.
*/
static LEX_STRING copy_definition_string(String *defstr,
MEM_ROOT *sp_mem_root)
{
LEX_STRING definition_string;
/*
Make a \0-terminated copy of the original SQL statement
*/
definition_string.str= (char*)strmake_root(sp_mem_root, defstr->c_ptr_safe(),
defstr->length());
if (!definition_string.str)
{
my_error(ER_OUTOFMEMORY, MYF(ME_FATAL), defstr->length());
return LEX_STRING{nullptr, 0};
}
/*
Set the length as an original string has
*/
definition_string.length= defstr->length();
return definition_string;
}
/**
@brief The function parses input strings and returns SP stucture.
......@@ -860,14 +899,33 @@ static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode,
thd->variables.sql_mode= sql_mode;
thd->variables.select_limit= HA_POS_ERROR;
if (parser_state.init(thd, defstr->c_ptr_safe(), defstr->length()))
LEX_STRING definition_string;
lex_start(thd);
init_sql_alloc(key_memory_sp_head_main_root, &thd->lex->sp_mem_root,
MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC, MYF(0));
thd->lex->sp_mem_root_ptr= &thd->lex->sp_mem_root;
/*
Copy a stored routine definition string to a memory buffer allocated on
the stored routine's memory root.
*/
definition_string= copy_definition_string(defstr, thd->lex->sp_mem_root_ptr);
/*
Check for OOM condition
*/
if (!definition_string.str)
return nullptr;
if (parser_state.init(thd, definition_string.str, definition_string.length))
{
thd->variables.sql_mode= old_sql_mode;
thd->variables.select_limit= old_select_limit;
return NULL;
}
lex_start(thd);
thd->lex->sphead= parent;
thd->push_internal_handler(&warning_handler);
thd->spcont= 0;
......@@ -881,6 +939,7 @@ static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode,
else
{
sp= thd->lex->sphead;
sp->set_definition_string(definition_string);
}
thd->pop_internal_handler();
......
......@@ -511,16 +511,16 @@ check_routine_name(const LEX_CSTRING *ident)
*/
sp_head *sp_head::create(sp_package *parent, const Sp_handler *handler,
enum_sp_aggregate_type agg_type)
enum_sp_aggregate_type agg_type, MEM_ROOT *sp_mem_root)
{
MEM_ROOT own_root;
init_sql_alloc(key_memory_sp_head_main_root, &own_root, MEM_ROOT_BLOCK_SIZE,
MEM_ROOT_PREALLOC, MYF(0));
sp_head *sp;
if (!(sp= new (&own_root) sp_head(&own_root, parent, handler, agg_type)))
free_root(&own_root, MYF(0));
return sp;
if (!sp_mem_root)
{
init_sql_alloc(key_memory_sp_head_main_root, &own_root, MEM_ROOT_BLOCK_SIZE,
MEM_ROOT_PREALLOC, MYF(0));
sp_mem_root= &own_root;
}
return new (sp_mem_root) sp_head(sp_mem_root, parent, handler, agg_type);
}
......@@ -534,7 +534,6 @@ void sp_head::destroy(sp_head *sp)
&sp->mem_root, &own_root));
delete sp;
free_root(&own_root, MYF(0));
}
}
......@@ -881,7 +880,6 @@ sp_head::set_stmt_end(THD *thd)
sp_head::~sp_head()
{
LEX *lex;
sp_instr *i;
DBUG_ENTER("sp_head::~sp_head");
......@@ -900,14 +898,7 @@ sp_head::~sp_head()
THD::lex. It is safe to not update LEX::ptr because further query
string parsing and execution will be stopped anyway.
*/
while ((lex= (LEX *)m_lex.pop()))
{
THD *thd= lex->thd;
thd->lex->sphead= NULL;
lex_end(thd->lex);
delete thd->lex;
thd->lex= lex;
}
unwind_aux_lexes_and_restore_original_lex();
my_hash_free(&m_sptabs);
my_hash_free(&m_sroutines);
......@@ -2513,6 +2504,22 @@ sp_head::merge_lex(THD *thd, LEX *oldlex, LEX *sublex)
DBUG_RETURN(FALSE);
}
void sp_head::unwind_aux_lexes_and_restore_original_lex()
{
LEX *lex;
while ((lex= (LEX *)m_lex.pop()))
{
THD *thd= lex->thd;
thd->lex->sphead= NULL;
lex_end(thd->lex);
delete thd->lex;
thd->lex= lex;
}
}
/**
Put the instruction on the backpatch list, associated with the label.
*/
......
......@@ -330,7 +330,8 @@ class sp_head :private Query_arena,
public:
static void destroy(sp_head *sp);
static sp_head *create(sp_package *parent, const Sp_handler *handler,
enum_sp_aggregate_type agg_type);
enum_sp_aggregate_type agg_type,
MEM_ROOT *sp_mem_root);
/// Initialize after we have reset mem_root
void
......@@ -632,6 +633,16 @@ class sp_head :private Query_arena,
DBUG_RETURN(false);
}
/**
Delete all auxiliary LEX objects created on parsing a statement and
restore a value of the data member THD::lex to point on the LEX object
that was actual before parsing started.
*/
void unwind_aux_lexes_and_restore_original_lex();
/**
Iterate through the LEX stack from the top (the newest) to the bottom
(the oldest) and find the one that contains a non-zero spname.
......@@ -792,6 +803,10 @@ class sp_head :private Query_arena,
m_definer.copy(mem_root, user_name, host_name);
}
void set_definition_string(LEX_STRING &defstr)
{
m_definition_string= defstr;
}
void reset_thd_mem_root(THD *thd);
void restore_thd_mem_root(THD *thd);
......@@ -937,6 +952,12 @@ class sp_head :private Query_arena,
*/
HASH m_sptabs;
/**
Text of the query CREATE PROCEDURE/FUNCTION/TRIGGER/EVENT ...
used for DDL parsing.
*/
LEX_STRING m_definition_string;
bool
execute(THD *thd, bool merge_da_on_success);
......@@ -966,6 +987,15 @@ class sp_head :private Query_arena,
being opened is probably enough).
*/
SQL_I_List<Item_trigger_field> m_trg_table_fields;
/**
The object of the Trigger class corresponding to this sp_head object.
This data member is set on table's triggers loading at the function
check_n_load and is used at the method sp_lex_instr::parse_expr
for accessing to the trigger's table after re-parsing of failed
trigger's instruction.
*/
Trigger *m_trg= nullptr;
}; // class sp_head : public Sql_alloc
......
This diff is collapsed.
......@@ -201,6 +201,9 @@ class sp_instr :public Query_arena, public Sql_alloc
}; // class sp_instr : public Sql_alloc
class sp_instr;
class sp_lex_instr;
/**
Auxilary class to which instructions delegate responsibility
for handling LEX and preparations before executing statement
......@@ -225,7 +228,8 @@ class sp_lex_keeper final
: m_lex(lex),
m_lex_resp(lex_resp),
prelocking_tables(nullptr),
lex_query_tables_own_last(nullptr)
lex_query_tables_own_last(nullptr),
m_first_execution(true)
{
lex->sp_lex_in_use= true;
}
......@@ -251,9 +255,43 @@ class sp_lex_keeper final
int reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables,
sp_instr* instr);
/**
Do several attempts to execute an instruction.
This method installs Reprepare_observer to catch possible metadata changes
on depending database objects, then calls reset_lex_and_exec_core()
to execute the instruction. If execution of the instruction fails, does
re-parsing of the instruction and re-execute it.
@param thd Thread context.
@param[out] nextp Pointer for storing a next instruction to execute
@param open_tables Flag to specify if the function should check read
access to tables in LEX's table list and open and
lock them (used in instructions which need to
calculate some expression and don't execute
complete statement).
@param instr instruction which we prepare context and run.
@return 0 on success, 1 on error
*/
int validate_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables,
sp_lex_instr* instr);
int cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables,
sp_instr *instr);
/**
(Re-)parse the query corresponding to this instruction and return a new
LEX-object.
@param thd Thread context.
@param sp The stored program.
@return new LEX-object or NULL in case of failure.
*/
LEX *parse_expr(THD *thd, const sp_head *sp);
inline uint sql_command() const
{
return (uint)m_lex->sql_command;
......@@ -264,6 +302,22 @@ class sp_lex_keeper final
m_lex->safe_to_cache_query= 0;
}
private:
/**
Clean up and destroy owned LEX object.
*/
void free_lex(THD *thd);
/**
Set LEX object.
@param lex LEX-object
@param is_lex_owner this flag specifies whether this LEX object is owned
by the sp_lex_keeper and so should deleted when
needed.
*/
void set_lex(LEX *lex, bool is_lex_owner);
private:
LEX *m_lex;
......@@ -291,6 +345,8 @@ class sp_lex_keeper final
statement enters/leaves prelocked mode on its own.
*/
TABLE_LIST **lex_query_tables_own_last;
bool m_first_execution;
};
......@@ -319,6 +375,18 @@ class sp_lex_instr : public sp_instr
*/
virtual void get_query(String *sql_query) const;
/**
(Re-)parse the query corresponding to this instruction and return a new
LEX-object.
@param thd Thread context.
@param sp The stored program.
@return new LEX-object or NULL in case of failure.
*/
LEX *parse_expr(THD *thd, sp_head *sp);
protected:
/**
@return the expression query string. This string can't be passed directly
......@@ -326,7 +394,36 @@ class sp_lex_instr : public sp_instr
*/
virtual LEX_CSTRING get_expr_query() const = 0;
/**
Some expressions may be re-parsed as SELECT statements.
This method is overridden in derived classes for instructions
those SQL command should be adjusted.
*/
virtual void adjust_sql_command(LEX *)
{}
/**
Callback method which is called after an expression string successfully
parsed and the thread context has not been switched to the outer context.
The thread context contains new LEX-object corresponding to the parsed
expression string.
@param thd Thread context.
@return Error flag.
*/
virtual bool on_after_expr_parsing(THD *)
{
return false;
}
sp_lex_keeper m_lex_keeper;
private:
/**
Clean up items previously created on behalf of the current instruction.
*/
void cleanup_before_parsing();
};
......@@ -383,6 +480,13 @@ class sp_instr_stmt : public sp_lex_instr
return LEX_CSTRING{m_query.str, m_query.length};
}
protected:
bool on_after_expr_parsing(THD *) override
{
m_valid= true;
return false;
}
public:
PSI_statement_info* get_psi_info() override { return & psi_info; }
static PSI_statement_info psi_info;
......@@ -431,6 +535,23 @@ class sp_instr_set : public sp_lex_instr
return m_expr_str;
}
void adjust_sql_command(LEX *lex) override
{
DBUG_ASSERT(lex->sql_command == SQLCOM_SELECT);
lex->sql_command= SQLCOM_SET_OPTION;
}
bool on_after_expr_parsing(THD *thd) override
{
DBUG_ASSERT(thd->lex->current_select->item_list.elements == 1);
m_value= thd->lex->current_select->item_list.head();
DBUG_ASSERT(m_value != nullptr);
// Return error in release version if m_value == nullptr
return m_value == nullptr;
}
sp_rcontext *get_rcontext(THD *thd) const;
const Sp_rcontext_handler *m_rcontext_handler;
uint m_offset; ///< Frame offset
......@@ -538,7 +659,11 @@ class sp_instr_set_trigger_field : public sp_lex_instr
trigger_field(trg_fld),
value(val),
m_expr_str(value_query)
{}
{
m_trigger_field_name=
LEX_CSTRING{strdup_root(current_thd->mem_root, trg_fld->field_name.str),
trg_fld->field_name.length};
}
virtual ~sp_instr_set_trigger_field() = default;
......@@ -564,6 +689,8 @@ class sp_instr_set_trigger_field : public sp_lex_instr
return m_expr_str;
}
bool on_after_expr_parsing(THD *thd) override;
private:
Item_trigger_field *trigger_field;
Item *value;
......@@ -572,6 +699,8 @@ class sp_instr_set_trigger_field : public sp_lex_instr
*/
LEX_CSTRING m_expr_str;
LEX_CSTRING m_trigger_field_name;
public:
PSI_statement_info* get_psi_info() override { return & psi_info; }
static PSI_statement_info psi_info;
......@@ -741,6 +870,23 @@ class sp_instr_jump_if_not : public sp_lex_instr, public sp_instr_opt_meta
return m_expr_str;
}
void adjust_sql_command(LEX *lex) override
{
assert(lex->sql_command == SQLCOM_SELECT);
lex->sql_command= SQLCOM_END;
}
bool on_after_expr_parsing(THD *thd) override
{
DBUG_ASSERT(thd->lex->current_select->item_list.elements == 1);
m_expr= thd->lex->current_select->item_list.head();
DBUG_ASSERT(m_expr != nullptr);
// Return error in release version if m_expr == nullptr
return m_expr == nullptr;
}
private:
Item *m_expr; ///< The condition
LEX_CSTRING m_expr_str;
......@@ -823,6 +969,16 @@ class sp_instr_freturn : public sp_lex_instr
return m_expr_str;
}
bool on_after_expr_parsing(THD *thd) override
{
DBUG_ASSERT(thd->lex->current_select->item_list.elements == 1);
m_value= thd->lex->current_select->item_list.head();
DBUG_ASSERT(m_value != nullptr);
// Return error in release version if m_value == nullptr
return m_value == nullptr;
}
Item *m_value;
const Type_handler *m_type_handler;
......@@ -1161,6 +1317,12 @@ class sp_instr_cursor_copy_struct: public sp_lex_instr
return m_cursor_stmt;
}
bool on_after_expr_parsing(THD *) override
{
m_valid= true;
return false;
}
public:
PSI_statement_info* get_psi_info() override { return & psi_info; }
static PSI_statement_info psi_info;
......@@ -1341,6 +1503,23 @@ class sp_instr_set_case_expr : public sp_lex_instr, public sp_instr_opt_meta
return m_expr_str;
}
void adjust_sql_command(LEX *lex) override
{
assert(lex->sql_command == SQLCOM_SELECT);
lex->sql_command= SQLCOM_END;
}
bool on_after_expr_parsing(THD *thd) override
{
DBUG_ASSERT(thd->lex->current_select->item_list.elements == 1);
m_case_expr= thd->lex->current_select->item_list.head();
DBUG_ASSERT(m_case_expr != nullptr);
// Return error in release version if m_case_expr == nullptr
return m_case_expr == nullptr;
}
private:
uint m_case_expr_id;
Item *m_case_expr;
......
......@@ -63,11 +63,11 @@ const LEX_CSTRING *Sp_rcontext_handler_package_body::get_name_prefix() const
///////////////////////////////////////////////////////////////////////////
sp_rcontext::sp_rcontext(const sp_head *owner,
sp_rcontext::sp_rcontext(sp_head *owner,
const sp_pcontext *root_parsing_ctx,
Field *return_value_fld,
bool in_sub_stmt)
:end_partial_result_set(false),
:callers_arena(nullptr), end_partial_result_set(false),
pause_state(false), quit_func(false), instr_ptr(0),
m_sp(owner),
m_root_parsing_ctx(root_parsing_ctx),
......@@ -91,7 +91,7 @@ sp_rcontext::~sp_rcontext()
sp_rcontext *sp_rcontext::create(THD *thd,
const sp_head *owner,
sp_head *owner,
const sp_pcontext *root_parsing_ctx,
Field *return_value_fld,
Row_definition_list &field_def_lst)
......
......@@ -71,7 +71,7 @@ class sp_rcontext : public Sql_alloc
///
/// @return valid sp_rcontext object or NULL in case of OOM-error.
static sp_rcontext *create(THD *thd,
const sp_head *owner,
sp_head *owner,
const sp_pcontext *root_parsing_ctx,
Field *return_value_fld,
Row_definition_list &defs);
......@@ -79,7 +79,7 @@ class sp_rcontext : public Sql_alloc
~sp_rcontext();
private:
sp_rcontext(const sp_head *owner,
sp_rcontext(sp_head *owner,
const sp_pcontext *root_parsing_ctx,
Field *return_value_fld,
bool in_sub_stmt);
......@@ -169,7 +169,7 @@ class sp_rcontext : public Sql_alloc
/// checking if correct runtime context is used for variable handling,
/// and to access the package run-time context.
/// Also used by slow log.
const sp_head *m_sp;
sp_head *m_sp;
/////////////////////////////////////////////////////////////////////////
// SP-variables.
......
......@@ -402,6 +402,15 @@ bool sp_create_assignment_lex(THD *thd, const char *pos)
{
if (thd->lex->sphead)
{
if (thd->lex->sphead->is_invoked())
/*
sphead->is_invoked() is true in case the assignment statement
is re-parsed. In this case, a new lex for re-parsing the statement
has been already created by sp_lex_instr::parse_expr and it should
be used for parsing the assignment SP instruction.
*/
return false;
sp_lex_local *new_lex;
if (!(new_lex= new (thd->mem_root) sp_lex_set_var(thd, thd->lex)) ||
new_lex->main_select_push())
......@@ -436,6 +445,16 @@ bool sp_create_assignment_instr(THD *thd, bool no_lookahead,
if (lex->sphead)
{
if (lex->sphead->is_invoked())
/*
Don't create a new SP assignment instruction in case the current
one is re-parsed by reasoning of metadata changes. Since in that case
a new lex is also not instantiated (@sa sp_create_assignment_lex)
it is safe to just return without restoring old lex that was active
before calling SP instruction.
*/
return false;
if (!lex->var_list.is_empty())
{
/*
......@@ -1312,6 +1331,7 @@ void LEX::start(THD *thd_arg)
table_count_update= 0;
memset(&trg_chistics, 0, sizeof(trg_chistics));
DBUG_VOID_RETURN;
}
......@@ -3792,14 +3812,20 @@ void st_select_lex::print_limit(THD *thd,
void LEX::cleanup_lex_after_parse_error(THD *thd)
{
/*
Delete sphead for the side effect of restoring of the original
LEX state, thd->lex, thd->mem_root and thd->free_list if they
were replaced when parsing stored procedure statements. We
will never use sphead object after a parse error, so it's okay
to delete it only for the sake of the side effect.
TODO: make this functionality explicit in sp_head class.
Sic: we must nullify the member of the main lex, not the
current one that will be thrown away
Don't delete an instance of the class sp_head pointed by the data member
thd->lex->sphead since sp_head's destructor deletes every instruction
created during parsing the stored routine. One of deleted instruction
is used later in the method sp_head::execute by the following
construction
ctx->handle_sql_condition(thd, &ip, i)
Here the variable 'i' references to the instruction that could be deleted
by sp_head's destructor and it would result in server abnormal termination.
This use case can theoretically happen in case the current stored routine's
instruction causes re-compilation of a SP intruction's statement and
internal parse error happens during this process.
Rather, just restore the original LEX object used before parser has been
run.
*/
if (thd->lex->sphead)
{
......@@ -3821,12 +3847,20 @@ void LEX::cleanup_lex_after_parse_error(THD *thd)
thd->lex->sphead= NULL;
}
else
{
sp_head::destroy(thd->lex->sphead);
thd->lex->sphead= NULL;
}
thd->lex->sphead->unwind_aux_lexes_and_restore_original_lex();
}
else if (thd->lex->sp_mem_root_ptr)
{
/*
A memory root pointed by the data member thd->lex->sp_mem_root_ptr
is allocated on compilation of a stored routine. In case the stored
routine name is incorrect an instance of the class sp_head hasn't been
assigned yet at the moment the error is reported. So, we free here
a memory root that allocated for the stored routine having incorrect name.
*/
free_root(thd->lex->sp_mem_root_ptr, MYF(0));
thd->lex->sp_mem_root_ptr= nullptr;
}
/*
json_table must be NULL before the query.
Didn't want to overload LEX::start, it's enough to put it here.
......@@ -3921,7 +3955,8 @@ LEX::LEX()
: explain(NULL), result(0), part_info(NULL), arena_for_set_stmt(0),
mem_root_for_set_stmt(0), json_table(NULL), default_used(0),
with_rownum(0), is_lex_started(0), option_type(OPT_DEFAULT),
context_analysis_only(0), sphead(0), limit_rows_examined_cnt(ULONGLONG_MAX)
context_analysis_only(0), sphead(0), sp_mem_root_ptr(nullptr),
limit_rows_examined_cnt(ULONGLONG_MAX)
{
init_dynamic_array2(PSI_INSTRUMENT_ME, &plugins, sizeof(plugin_ref),
......@@ -7371,7 +7406,7 @@ sp_head *LEX::make_sp_head(THD *thd, const sp_name *name,
sp_head *sp;
/* Order is important here: new - reset - init */
if (likely((sp= sp_head::create(package, sph, agg_type))))
if (likely((sp= sp_head::create(package, sph, agg_type, sp_mem_root_ptr))))
{
sp->reset_thd_mem_root(thd);
sp->init(this);
......@@ -10548,6 +10583,24 @@ bool LEX::new_sp_instr_stmt(THD *thd,
memcpy(qbuff.str, prefix.str, prefix.length);
strmake(qbuff.str + prefix.length, suffix.str, suffix.length);
/*
Force null-termination for every SQL statement inside multi-statements
block in order to make the assert
DBUG_ASSERT(ls->length < UINT_MAX32 &&
((ls->length == 0 && !ls->str) ||
ls->length == strlen(ls->str)));
inside the method
bool String::append(const LEX_CSTRING *ls)
be happy.
This method is invoked by implementations of the virtual method
sp_lex_instr::get_query
and overridden implementations of this method in derived classes.
*/
qbuff.str[prefix.length + suffix.length]= 0;
if (!(i= new (thd->mem_root) sp_instr_stmt(sphead->instructions(),
spcont, this, qbuff)))
return true;
......
......@@ -3529,6 +3529,7 @@ struct LEX: public Query_tables_list
TABLE_LIST *create_last_non_select_table;
sp_head *sphead;
sp_name *spname;
MEM_ROOT sp_mem_root, *sp_mem_root_ptr;
sp_pcontext *spcont;
......
......@@ -1651,7 +1651,12 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
bool parse_error= parse_sql(thd, & parser_state, creation_ctx);
thd->pop_internal_handler();
DBUG_ASSERT(!parse_error || lex.sphead == 0);
if (parse_error)
{
sp_head::destroy(lex.sphead);
lex.sphead= nullptr;
}
/*
Not strictly necessary to invoke this method here, since we know
......@@ -1815,6 +1820,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
&trigger->subject_table_grants);
}
sp->m_trg= trigger;
lex_end(&lex);
}
thd->reset_db(&save_db);
......
......@@ -343,6 +343,12 @@ class Table_triggers_list: public Sql_alloc
}
return false;
}
public:
TABLE *get_subject_table()
{
return trigger_table;
}
};
......
......@@ -153,17 +153,17 @@ static Item* escape(THD *thd)
static void yyerror(THD *thd, const char *s)
{
/* "parse error" changed into "syntax error" between bison 1.75 and 1.875 */
if (strcmp(s,"parse error") == 0 || strcmp(s,"syntax error") == 0)
s= ER_THD(thd, ER_SYNTAX_ERROR);
thd->parse_error(s, 0);
/*
Restore the original LEX if it was replaced when parsing
a stored procedure. We must ensure that a parsing error
does not leave any side effects in the THD.
*/
LEX::cleanup_lex_after_parse_error(thd);
/* "parse error" changed into "syntax error" between bison 1.75 and 1.875 */
if (strcmp(s,"parse error") == 0 || strcmp(s,"syntax error") == 0)
s= ER_THD(thd, ER_SYNTAX_ERROR);
thd->parse_error(s, 0);
}
......
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