diff --git a/sql/sp.cc b/sql/sp.cc index ce62b5849c5..9d993d4dba2 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -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(); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 002f7883c0e..2919c2c7dfa 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -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. */ diff --git a/sql/sp_head.h b/sql/sp_head.h index 69e7838b25a..b621cfd4ad2 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -330,7 +330,8 @@ protected: 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 @@ public: 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 @@ public: 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 @@ protected: */ 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 @@ public: being opened is probably enough). */ SQL_I_List 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 diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 208ed5ccd7d..24e9fd5f1ec 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -361,6 +361,115 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, } +void sp_lex_keeper::free_lex(THD *thd) +{ + if (!m_lex_resp || !m_lex) return; + + /* Prevent endless recursion. */ + m_lex->sphead= nullptr; + lex_end(m_lex); + + delete (st_lex_local *)m_lex; + + /* + Set thd->lex to the null value in case it points to a LEX object + we just deleted in order to avoid dangling pointer problem + */ + if (thd->lex == m_lex) + thd->lex= nullptr; + + m_lex= nullptr; + m_lex_resp= false; + lex_query_tables_own_last= nullptr; +} + + +void sp_lex_keeper::set_lex(LEX *lex, bool is_lex_owner) +{ + m_lex= lex; + m_lex_resp= is_lex_owner; + + if (m_lex) + m_lex->sp_lex_in_use= true; +} + + +int sp_lex_keeper::validate_lex_and_exec_core(THD *thd, uint *nextp, + bool open_tables, + sp_lex_instr* instr) +{ + Reprepare_observer reprepare_observer; + + while (true) + { + if (instr->is_invalid()) + { + thd->clear_error(); + free_lex(thd); + LEX *lex= instr->parse_expr(thd, thd->spcont->m_sp); + + if (!lex) return true; + + set_lex(lex, true); + + m_first_execution= true; + } + + Reprepare_observer *stmt_reprepare_observer= nullptr; + + if (!m_first_execution && + ((sql_command_flags[m_lex->sql_command] & CF_REEXECUTION_FRAGILE) || + m_lex->sql_command == SQLCOM_END)) + { + reprepare_observer.reset_reprepare_observer(); + stmt_reprepare_observer= &reprepare_observer; + } + + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + thd->m_reprepare_observer= stmt_reprepare_observer; + + bool rc= reset_lex_and_exec_core(thd, nextp, open_tables, instr); + + thd->m_reprepare_observer= save_reprepare_observer; + + m_first_execution= false; + + if (!rc) + break; + + /* + Raise the error upper level in case: + - we got an error and Reprepare_observer is not set + - a fatal error has been got + - the current execution thread has been killed + - an error different from ER_NEED_REPREPARE has been got. + */ + if (stmt_reprepare_observer == nullptr || + thd->is_fatal_error || + thd->killed || + thd->get_stmt_da()->get_sql_errno() != ER_NEED_REPREPARE) + return 1; + + if (!stmt_reprepare_observer->can_retry()) + { + /* + Reprepare_observer sets error status in DA but Sql_condition is not + added. Please check Reprepare_observer::report_error(). Pushing + Sql_condition for ER_NEED_REPREPARE here. + */ + Diagnostics_area *da= thd->get_stmt_da(); + da->push_warning(thd, da->get_sql_errno(), da->get_sqlstate(), + Sql_state_errno_level::WARN_LEVEL_ERROR, da->message()); + return 1; + } + + instr->invalidate(); + } + + return 0; +} + + int sp_lex_keeper::cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables, sp_instr *instr) @@ -379,7 +488,6 @@ int sp_lex_keeper::cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, return res; } - /* sp_instr class functions */ @@ -438,6 +546,148 @@ void sp_lex_instr::get_query(String *sql_query) const } +void sp_lex_instr::cleanup_before_parsing() +{ + Item *current= free_list; + + while (current) + { + Item *next= current->next; + current->delete_self(); + current= next; + } + + free_list= nullptr; +} + + +/** + Set up field object for every NEW/OLD item of the trigger. + + @param thd current thread + @param sp sp_head object of the trigger +*/ + +static void setup_table_fields_for_trigger(THD *thd, sp_head *sp) +{ + DBUG_ASSERT(sp->m_trg); + + for (Item_trigger_field *trg_field= sp->m_trg_table_fields.first; + trg_field; + trg_field= trg_field->next_trg_field) + { + trg_field->setup_field(thd, sp->m_trg->base->get_subject_table(), + &sp->m_trg->subject_table_grants); + } +} + +LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp) +{ + String sql_query; + + get_query(&sql_query); + + if (sql_query.length() == 0) + { + /** + The instruction has returned zero-length query string. That means, the + re-preparation of the instruction is not possible. We should not come + here in the normal case. + */ + assert(false); + my_error(ER_UNKNOWN_ERROR, MYF(0)); + return nullptr; + } + + cleanup_before_parsing(); + + Parser_state parser_state; + + if (parser_state.init(thd, sql_query.c_ptr(), sql_query.length())) + return nullptr; + + // Create a new LEX and initialize it. + + LEX *lex_saved= thd->lex; + + DBUG_ASSERT(mem_root != thd->mem_root); + /* + Back up the current free_list pointer and reset it to nullptr. + Set thd->mem_root pointing to a mem_root of SP instruction being re-parsed. + In that way any items created on parsing a statement of the current + instruction is allocated on SP instruction's mem_root and placed on its own + free_list that later assigned to the current sp_instr. We use the separate + free list for every instruction since at least at one place in the source + code (the function subst_spvars() to be accurate) we iterate along the + list sp_instr->free_list on executing of every SP instruction. + */ + Query_arena backup; + /* + A statement of SP instruction is going to be re-parsed, so reset + SP arena's state to STMT_INITIALIZED_FOR_SP as its initial state. + */ + state= STMT_INITIALIZED_FOR_SP; + thd->set_n_backup_active_arena(this, &backup); + thd->free_list= nullptr; + + thd->lex= new (thd->mem_root) st_lex_local; + + lex_start(thd); + + thd->lex->sphead= sp; + thd->lex->spcont= m_ctx; + + sql_digest_state *parent_digest= thd->m_digest; + PSI_statement_locker *parent_locker= thd->m_statement_psi; + + thd->m_digest= nullptr; + thd->m_statement_psi= nullptr; + + /* + sp_head::m_tmp_query is set by parser on parsing every statement of + a stored routine. Since here we re-parse failed statement outside stored + routine context, this data member isn't set. In result, the assert + DBUG_ASSERT(sphead->m_tmp_query <= start) + is fired in the constructor of the class Query_fragment. + To fix the assert failure, reset this data member to point to beginning of + the current statement being parsed. + */ + const char *m_tmp_query_bak= sp->m_tmp_query; + sp->m_tmp_query= sql_query.c_ptr(); + + bool parsing_failed= parse_sql(thd, &parser_state, nullptr); + + sp->m_tmp_query= m_tmp_query_bak; + thd->m_digest= parent_digest; + thd->m_statement_psi= parent_locker; + + if (!parsing_failed) + { + thd->lex->set_trg_event_type_for_tables(); + adjust_sql_command(thd->lex); + parsing_failed= on_after_expr_parsing(thd); + + if (sp->m_handler->type() == SP_TYPE_TRIGGER) + setup_table_fields_for_trigger(thd, sp); + + /* + Assign the list of items created on parsing to the current + stored routine instruction. + */ + free_list= thd->free_list; + thd->free_list= nullptr; + } + + Query_arena old; + thd->restore_active_arena(&old, &backup); + + LEX *expr_lex= thd->lex; + thd->lex= lex_saved; + + return parsing_failed ? nullptr : expr_lex; +} + + /* sp_instr_stmt class functions */ @@ -478,7 +728,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) thd->query_length()) <= 0) { thd->reset_slow_query_state(); - res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, false, this); + res= m_lex_keeper.validate_lex_and_exec_core(thd, nextp, false, this); bool log_slow= !res && thd->enable_slow_log; /* Finalize server status flags after executing a statement. */ @@ -592,7 +842,7 @@ sp_instr_set::execute(THD *thd, uint *nextp) DBUG_ENTER("sp_instr_set::execute"); DBUG_PRINT("info", ("offset: %u", m_offset)); - DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, true, this)); + DBUG_RETURN(m_lex_keeper.validate_lex_and_exec_core(thd, nextp, true, this)); } @@ -744,7 +994,7 @@ sp_instr_set_trigger_field::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_set_trigger_field::execute"); thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; - DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, true, this)); + DBUG_RETURN(m_lex_keeper.validate_lex_and_exec_core(thd, nextp, true, this)); } @@ -836,6 +1086,27 @@ sp_instr_jump::opt_move(uint dst, List *bp) m_ip= dst; } +bool sp_instr_set_trigger_field::on_after_expr_parsing(THD *thd) +{ + DBUG_ASSERT(thd->lex->current_select->item_list.elements == 1); + + value= thd->lex->current_select->item_list.head(); + DBUG_ASSERT(value != nullptr); + + trigger_field = new (thd->mem_root) + Item_trigger_field(thd, thd->lex->current_context(), + Item_trigger_field::NEW_ROW, + m_trigger_field_name, UPDATE_ACL, false); + + if (!value || !trigger_field) + return true; + + thd->spcont->m_sp->m_trg_table_fields.link_in_list( + trigger_field, &trigger_field->next_trg_field); + + return false; +} + /* sp_instr_jump_if_not class functions @@ -849,7 +1120,7 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_jump_if_not::execute"); DBUG_PRINT("info", ("destination: %u", m_dest)); - DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, true, this)); + DBUG_RETURN(m_lex_keeper.validate_lex_and_exec_core(thd, nextp, true, this)); } @@ -953,7 +1224,7 @@ int sp_instr_freturn::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_freturn::execute"); - DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, true, this)); + DBUG_RETURN(m_lex_keeper.validate_lex_and_exec_core(thd, nextp, true, this)); } @@ -1610,7 +1881,7 @@ sp_instr_set_case_expr::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_set_case_expr::execute"); - DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, true, this)); + DBUG_RETURN(m_lex_keeper.validate_lex_and_exec_core(thd, nextp, true, this)); } diff --git a/sql/sp_instr.h b/sql/sp_instr.h index b1d9b95be5b..662dc4bce8b 100644 --- a/sql/sp_instr.h +++ b/sql/sp_instr.h @@ -201,6 +201,9 @@ public: }; // 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 @@ public: : 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 @@ public: 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 @@ public: 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 @@ private: statement enters/leaves prelocked mode on its own. */ TABLE_LIST **lex_query_tables_own_last; + + bool m_first_execution; }; @@ -319,6 +375,18 @@ public: */ 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 @@ protected: */ 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 @@ protected: 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 @@ protected: 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 @@ public: 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 @@ protected: return m_expr_str; } + bool on_after_expr_parsing(THD *thd) override; + private: Item_trigger_field *trigger_field; Item *value; @@ -572,6 +699,8 @@ private: */ 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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; diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index de355093c88..81aaf9b3bbf 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -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) diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index ea669b2d1d8..a891ab04865 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -71,7 +71,7 @@ public: /// /// @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 @@ public: ~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 @@ public: /// 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. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 34d08736fd3..167d2ea7791 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -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; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 7e8a8bb2be7..6330bc77956 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3529,6 +3529,7 @@ public: 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; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 132dcc2465e..10e09dec18c 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -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); diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index a1b8a42f28d..d1edd94eb4f 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -343,6 +343,12 @@ private: } return false; } + +public: + TABLE *get_subject_table() + { + return trigger_table; + } }; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a015a6ed48f..9354e7188dc 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -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); }