diff options
author | Alexander Barkov <bar@mariadb.com> | 2019-11-12 12:20:30 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.com> | 2019-11-12 16:52:44 +0400 |
commit | e26d049197daef399a457d36e545d7ab5700c29a (patch) | |
tree | 3f9b5be14191b2ca472f0eb6fb34dedb636f2911 /sql/sql_lex.cc | |
parent | 68ed3a81f2113ecdcd5fce0f0cab636d57ff77da (diff) | |
download | mariadb-git-e26d049197daef399a457d36e545d7ab5700c29a.tar.gz |
MDEV-21023 Move LEX methods and related functions from sql_yacc.yy to sql_lex.cc
Diffstat (limited to 'sql/sql_lex.cc')
-rw-r--r-- | sql/sql_lex.cc | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 257a36e94c5..0a3385edb3b 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -52,6 +52,524 @@ const LEX_CSTRING empty_clex_str= {"", 0}; const LEX_CSTRING star_clex_str= {"*", 1}; const LEX_CSTRING param_clex_str= {"?", 1}; + +/** + Helper action for a case expression statement (the expr in 'CASE expr'). + This helper is used for 'searched' cases only. + @param lex the parser lex context + @param expr the parsed expression + @return 0 on success +*/ + +int LEX::case_stmt_action_expr(Item* expr) +{ + int case_expr_id= spcont->register_case_expr(); + sp_instr_set_case_expr *i; + + if (spcont->push_case_expr_id(case_expr_id)) + return 1; + + i= new (thd->mem_root) + sp_instr_set_case_expr(sphead->instructions(), spcont, case_expr_id, expr, + this); + + sphead->add_cont_backpatch(i); + return sphead->add_instr(i); +} + +/** + Helper action for a case when condition. + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context + @param when the parsed expression for the WHEN clause + @param simple true for simple cases, false for searched cases +*/ + +int LEX::case_stmt_action_when(Item *when, bool simple) +{ + uint ip= sphead->instructions(); + sp_instr_jump_if_not *i; + Item_case_expr *var; + Item *expr; + + if (simple) + { + var= new (thd->mem_root) + Item_case_expr(thd, spcont->get_current_case_expr_id()); + +#ifdef DBUG_ASSERT_EXISTS + if (var) + { + var->m_sp= sphead; + } +#endif + + expr= new (thd->mem_root) Item_func_eq(thd, var, when); + i= new (thd->mem_root) sp_instr_jump_if_not(ip, spcont, expr, this); + } + else + i= new (thd->mem_root) sp_instr_jump_if_not(ip, spcont, when, this); + + /* + BACKPATCH: Registering forward jump from + "case_stmt_action_when" to "case_stmt_action_then" + (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example) + */ + + return + !MY_TEST(i) || + sphead->push_backpatch(thd, i, spcont->push_label(thd, &empty_clex_str, 0)) || + sphead->add_cont_backpatch(i) || + sphead->add_instr(i); +} + +/** + Helper action for a case then statements. + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context +*/ + +int LEX::case_stmt_action_then() +{ + uint ip= sphead->instructions(); + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(ip, spcont); + if (!MY_TEST(i) || sphead->add_instr(i)) + return 1; + + /* + BACKPATCH: Resolving forward jump from + "case_stmt_action_when" to "case_stmt_action_then" + (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example) + */ + + sphead->backpatch(spcont->pop_label()); + + /* + BACKPATCH: Registering forward jump from + "case_stmt_action_then" to after END CASE + (jump from instruction 4 to 12, 7 to 12 ... in the example) + */ + + return sphead->push_backpatch(thd, i, spcont->last_label()); +} + + +/** + Helper action for a SET statement. + Used to push a system variable into the assignment list. + + @param tmp the system variable with base name + @param var_type the scope of the variable + @param val the value being assigned to the variable + + @return TRUE if error, FALSE otherwise. +*/ + +bool +LEX::set_system_variable(enum enum_var_type var_type, + sys_var *sysvar, const Lex_ident_sys_st *base_name, + Item *val) +{ + set_var *setvar; + + /* No AUTOCOMMIT from a stored function or trigger. */ + if (spcont && sysvar == Sys_autocommit_ptr) + sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; + + if (val && val->type() == Item::FIELD_ITEM && + ((Item_field*)val)->table_name.str) + { + my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), sysvar->name.str); + return TRUE; + } + + if (!(setvar= new (thd->mem_root) set_var(thd, var_type, sysvar, + base_name, val))) + return TRUE; + + return var_list.push_back(setvar, thd->mem_root); +} + + +/** + Helper action for a SET statement. + Used to SET a field of NEW row. + + @param name the field name + @param val the value being assigned to the row + + @return TRUE if error, FALSE otherwise. +*/ + +bool LEX::set_trigger_new_row(const LEX_CSTRING *name, Item *val) +{ + Item_trigger_field *trg_fld; + sp_instr_set_trigger_field *sp_fld; + + /* QQ: Shouldn't this be field's default value ? */ + if (! val) + val= new (thd->mem_root) Item_null(thd); + + DBUG_ASSERT(trg_chistics.action_time == TRG_ACTION_BEFORE && + (trg_chistics.event == TRG_EVENT_INSERT || + trg_chistics.event == TRG_EVENT_UPDATE)); + + trg_fld= new (thd->mem_root) + Item_trigger_field(thd, current_context(), + Item_trigger_field::NEW_ROW, + *name, UPDATE_ACL, FALSE); + + if (unlikely(trg_fld == NULL)) + return TRUE; + + sp_fld= new (thd->mem_root) + sp_instr_set_trigger_field(sphead->instructions(), + spcont, trg_fld, val, this); + + if (unlikely(sp_fld == NULL)) + return TRUE; + + /* + Let us add this item to list of all Item_trigger_field + objects in trigger. + */ + trg_table_fields.link_in_list(trg_fld, &trg_fld->next_trg_field); + + return sphead->add_instr(sp_fld); +} + + +/** + Create an object to represent a SP variable in the Item-hierarchy. + + @param name The SP variable name. + @param spvar The SP variable (optional). + @param start_in_q Start position of the SP variable name in the query. + @param end_in_q End position of the SP variable name in the query. + + @remark If spvar is not specified, the name is used to search for the + variable in the parse-time context. If the variable does not + exist, a error is set and NULL is returned to the caller. + + @return An Item_splocal object representing the SP variable, or NULL on error. +*/ +Item_splocal* +LEX::create_item_for_sp_var(const Lex_ident_cli_st *cname, sp_variable *spvar) +{ + const Sp_rcontext_handler *rh; + Item_splocal *item; + const char *start_in_q= cname->pos(); + const char *end_in_q= cname->end(); + uint pos_in_q, len_in_q; + Lex_ident_sys name(thd, cname); + + if (name.is_null()) + return NULL; // EOM + + /* If necessary, look for the variable. */ + if (spcont && !spvar) + spvar= find_variable(&name, &rh); + + if (!spvar) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str); + return NULL; + } + + DBUG_ASSERT(spcont && spvar); + + /* Position and length of the SP variable name in the query. */ + pos_in_q= (uint)(start_in_q - sphead->m_tmp_query); + len_in_q= (uint)(end_in_q - start_in_q); + + item= new (thd->mem_root) + Item_splocal(thd, rh, &name, spvar->offset, spvar->type_handler(), + pos_in_q, len_in_q); + +#ifdef DBUG_ASSERT_EXISTS + if (item) + item->m_sp= sphead; +#endif + + return item; +} + + +/** + Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>. + See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383. + This function returns the proper item for the SQL expression + <code>left [NOT] IN ( expr )</code> + @param thd the current thread + @param left the in predicand + @param equal true for IN predicates, false for NOT IN predicates + @param expr first and only expression of the in value list + @return an expression representing the IN predicate. +*/ +Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal, + Item *expr) +{ + /* + Relevant references for this issue: + - SQL:2003, Part 2, section 8.4 <in predicate>, page 383, + - SQL:2003, Part 2, section 7.2 <row value expression>, page 296, + - SQL:2003, Part 2, section 6.3 <value expression primary>, page 174, + - SQL:2003, Part 2, section 7.15 <subquery>, page 370, + - SQL:2003 Feature F561, "Full value expressions". + + The exception in SQL:2003 Note 184 means: + Item_singlerow_subselect, which corresponds to a <scalar subquery>, + should be re-interpreted as an Item_in_subselect, which corresponds + to a <table subquery> when used inside an <in predicate>. + + Our reading of Note 184 is reccursive, so that all: + - IN (( <subquery> )) + - IN ((( <subquery> ))) + - IN '('^N <subquery> ')'^N + - etc + should be interpreted as a <table subquery>, no matter how deep in the + expression the <subquery> is. + */ + + Item *result; + + DBUG_ENTER("handle_sql2003_note184_exception"); + + if (expr->type() == Item::SUBSELECT_ITEM) + { + Item_subselect *expr2 = (Item_subselect*) expr; + + if (expr2->substype() == Item_subselect::SINGLEROW_SUBS) + { + Item_singlerow_subselect *expr3 = (Item_singlerow_subselect*) expr2; + st_select_lex *subselect; + + /* + Implement the mandated change, by altering the semantic tree: + left IN Item_singlerow_subselect(subselect) + is modified to + left IN (subselect) + which is represented as + Item_in_subselect(left, subselect) + */ + subselect= expr3->invalidate_and_restore_select_lex(); + result= new (thd->mem_root) Item_in_subselect(thd, left, subselect); + + if (! equal) + result = negate_expression(thd, result); + + DBUG_RETURN(result); + } + } + + if (equal) + result= new (thd->mem_root) Item_func_eq(thd, left, expr); + else + result= new (thd->mem_root) Item_func_ne(thd, left, expr); + + DBUG_RETURN(result); +} + +/** + Create a separate LEX for each assignment if in SP. + + If we are in SP we want have own LEX for each assignment. + This is mostly because it is hard for several sp_instr_set + and sp_instr_set_trigger instructions share one LEX. + (Well, it is theoretically possible but adds some extra + overhead on preparation for execution stage and IMO less + robust). + + QQ: May be we should simply prohibit group assignments in SP? + + @see sp_create_assignment_instr + + @param thd Thread context + @param pos The position in the raw SQL buffer +*/ + + +bool sp_create_assignment_lex(THD *thd, const char *pos) +{ + if (thd->lex->sphead) + { + sp_lex_local *new_lex; + if (!(new_lex= new (thd->mem_root) sp_lex_set_var(thd, thd->lex)) || + new_lex->main_select_push()) + return true; + new_lex->sphead->m_tmp_query= pos; + return thd->lex->sphead->reset_lex(thd, new_lex); + } + return false; +} + + +/** + Create a SP instruction for a SET assignment. + + @see sp_create_assignment_lex + + @param thd - Thread context + @param no_lookahead - True if the parser has no lookahead + @param need_set_keyword - if a SET statement "SET a=10", + or a direct assignment overwise "a:=10" + @return false if success, true otherwise. +*/ + +bool sp_create_assignment_instr(THD *thd, bool no_lookahead, + bool need_set_keyword) +{ + LEX *lex= thd->lex; + + if (lex->sphead) + { + if (!lex->var_list.is_empty()) + { + /* + - Every variable assignment from the same SET command, e.g.: + SET @var1=expr1, @var2=expr2; + produce each own sp_create_assignment_instr() call + lex->var_list.elements is 1 in this case. + - This query: + SET TRANSACTION READ ONLY, ISOLATION LEVEL SERIALIZABLE; + in translated to: + SET tx_read_only=1, tx_isolation=ISO_SERIALIZABLE; + but produces a single sp_create_assignment_instr() call + which includes the query fragment covering both options. + */ + DBUG_ASSERT(lex->var_list.elements >= 1 && lex->var_list.elements <= 2); + /* + sql_mode=ORACLE's direct assignment of a global variable + is not possible by the grammar. + */ + DBUG_ASSERT(lex->option_type != OPT_GLOBAL || need_set_keyword); + /* + We have assignment to user or system variable or + option setting, so we should construct sp_instr_stmt + for it. + */ + Lex_input_stream *lip= &thd->m_parser_state->m_lip; + + /* + Extract the query statement from the tokenizer. The + end is either lip->ptr, if there was no lookahead, + lip->tok_end otherwise. + */ + static const LEX_CSTRING setlc= { STRING_WITH_LEN("SET ") }; + static const LEX_CSTRING setgl= { STRING_WITH_LEN("SET GLOBAL ") }; + const char *qend= no_lookahead ? lip->get_ptr() : lip->get_tok_end(); + Lex_cstring qbuf(lex->sphead->m_tmp_query, qend); + if (lex->new_sp_instr_stmt(thd, + lex->option_type == OPT_GLOBAL ? setgl : + need_set_keyword ? setlc : + null_clex_str, + qbuf)) + return true; + } + lex->pop_select(); + if (lex->check_main_unit_semantics()) + { + /* + "lex" can be referrenced by: + - sp_instr_set SET a= expr; + - sp_instr_set_row_field SET r.a= expr; + - sp_instr_stmt (just generated above) SET @a= expr; + In this case, "lex" is fully owned by sp_instr_xxx and it will + be deleted by the destructor ~sp_instr_xxx(). + So we should remove "lex" from the stack sp_head::m_lex, + to avoid double free. + Note, in case "lex" is not owned by any sp_instr_xxx, + it's also safe to remove it from the stack right now. + So we can remove it unconditionally, without testing lex->sp_lex_in_use. + */ + lex->sphead->restore_lex(thd); + return true; + } + enum_var_type inner_option_type= lex->option_type; + if (lex->sphead->restore_lex(thd)) + return true; + /* Copy option_type to outer lex in case it has changed. */ + thd->lex->option_type= inner_option_type; + } + return false; +} + + +void LEX::add_key_to_list(LEX_CSTRING *field_name, + enum Key::Keytype type, bool check_exists) +{ + Key *key; + MEM_ROOT *mem_root= thd->mem_root; + key= new (mem_root) + Key(type, &null_clex_str, HA_KEY_ALG_UNDEF, false, + DDL_options(check_exists ? + DDL_options::OPT_IF_NOT_EXISTS : + DDL_options::OPT_NONE)); + key->columns.push_back(new (mem_root) Key_part_spec(field_name, 0), + mem_root); + alter_info.key_list.push_back(key, mem_root); +} + + +bool LEX::add_alter_list(const char *name, Virtual_column_info *expr, + bool exists) +{ + MEM_ROOT *mem_root= thd->mem_root; + Alter_column *ac= new (mem_root) Alter_column(name, expr, exists); + if (unlikely(ac == NULL)) + return true; + alter_info.alter_list.push_back(ac, mem_root); + alter_info.flags|= ALTER_CHANGE_COLUMN_DEFAULT; + return false; +} + + +void LEX::init_last_field(Column_definition *field, + const LEX_CSTRING *field_name, + const CHARSET_INFO *cs) +{ + last_field= field; + + field->field_name= *field_name; + + /* reset LEX fields that are used in Create_field::set_and_check() */ + charset= cs; +} + + +bool LEX::set_bincmp(CHARSET_INFO *cs, bool bin) +{ + /* + if charset is NULL - we're parsing a field declaration. + we cannot call find_bin_collation for a field here, because actual + field charset is determined in get_sql_field_charset() much later. + so we only set a flag. + */ + if (!charset) + { + charset= cs; + last_field->flags|= bin ? BINCMP_FLAG : 0; + return false; + } + + charset= bin ? find_bin_collation(cs ? cs : charset) + : cs ? cs : charset; + return charset == NULL; +} + + +Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) +{ + Virtual_column_info *v= new (thd->mem_root) Virtual_column_info(); + if (unlikely(!v)) + return 0; + v->expr= expr; + v->utf8= 0; /* connection charset */ + return v; +} + + + /** @note The order of the elements of this array must correspond to the order of elements in enum_binlog_stmt_unsafe. |