forked from StoneAtom/StoneDB
440 lines
13 KiB
C++
440 lines
13 KiB
C++
/* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "my_config.h"
|
|
#include "parse_tree_helpers.h"
|
|
|
|
#include "sql_class.h"
|
|
#include "sp_head.h"
|
|
#include "sp_instr.h"
|
|
#include "auth/auth_common.h"
|
|
|
|
|
|
/**
|
|
Create an object to represent a SP variable in the Item-hierarchy.
|
|
|
|
@param thd The current thread.
|
|
@param name The SP variable name.
|
|
@param spv The SP variable (optional).
|
|
@param query_start_ptr Start of the SQL-statement query string (optional).
|
|
@param start Start position of the SP variable name in the query.
|
|
@param end End position of the SP variable name in the query.
|
|
|
|
@remark If spv 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* create_item_for_sp_var(THD *thd,
|
|
LEX_STRING name,
|
|
sp_variable *spv,
|
|
const char *query_start_ptr,
|
|
const char *start,
|
|
const char *end)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
size_t spv_pos_in_query= 0;
|
|
size_t spv_len_in_query= 0;
|
|
sp_pcontext *pctx= lex->get_sp_current_parsing_ctx();
|
|
|
|
/* If necessary, look for the variable. */
|
|
if (pctx && !spv)
|
|
spv= pctx->find_variable(name, false);
|
|
|
|
if (!spv)
|
|
{
|
|
my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str);
|
|
return NULL;
|
|
}
|
|
|
|
assert(pctx && spv);
|
|
|
|
if (query_start_ptr)
|
|
{
|
|
/* Position and length of the SP variable name in the query. */
|
|
spv_pos_in_query= start - query_start_ptr;
|
|
spv_len_in_query= end - start;
|
|
}
|
|
|
|
Item_splocal *item=
|
|
new (thd->mem_root) Item_splocal(
|
|
name, spv->offset, spv->type, spv_pos_in_query, spv_len_in_query);
|
|
|
|
#ifndef NDEBUG
|
|
if (item)
|
|
item->m_sp= lex->sphead;
|
|
#endif
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
/**
|
|
Report syntax error if the sel query block can't be parenthesized
|
|
|
|
@return false if successful, true if an error was reported. In the latter
|
|
case parsing should stop.
|
|
*/
|
|
bool setup_select_in_parentheses(SELECT_LEX *sel)
|
|
{
|
|
assert(sel->braces);
|
|
if (sel->linkage == UNION_TYPE &&
|
|
!sel->master_unit()->first_select()->braces &&
|
|
sel->master_unit()->first_select()->linkage ==
|
|
UNION_TYPE)
|
|
{
|
|
my_syntax_error(ER(ER_SYNTAX_ERROR));
|
|
return true;
|
|
}
|
|
if (sel->linkage == UNION_TYPE &&
|
|
sel->olap != UNSPECIFIED_OLAP_TYPE &&
|
|
sel->master_unit()->fake_select_lex)
|
|
{
|
|
my_error(ER_WRONG_USAGE, MYF(0), "CUBE/ROLLUP", "ORDER BY");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Push an error message into MySQL diagnostic area with line
|
|
and position information.
|
|
|
|
This function provides semantic action implementers with a way
|
|
to push the famous "You have a syntax error near..." error
|
|
message into the diagnostic area, which is normally produced only if
|
|
a parse error is discovered internally by the Bison generated
|
|
parser.
|
|
*/
|
|
|
|
void my_syntax_error(const char *s)
|
|
{
|
|
THD *thd= current_thd;
|
|
Lex_input_stream *lip= & thd->m_parser_state->m_lip;
|
|
|
|
const char *yytext= lip->get_tok_start();
|
|
if (!yytext)
|
|
yytext= "";
|
|
|
|
/* Push an error into the diagnostic area */
|
|
ErrConvString err(yytext, thd->variables.character_set_client);
|
|
my_printf_error(ER_PARSE_ERROR, ER(ER_PARSE_ERROR), MYF(0), s,
|
|
err.ptr(), lip->yylineno);
|
|
}
|
|
|
|
|
|
bool find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
|
|
{
|
|
tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
|
|
|
|
if (tmp->var == NULL)
|
|
my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), tmp->base_name.str);
|
|
else
|
|
tmp->base_name= null_lex_str;
|
|
|
|
return thd->is_error();
|
|
}
|
|
|
|
|
|
/**
|
|
Helper action for a SET statement.
|
|
Used to push a system variable into the assignment list.
|
|
|
|
@param thd the current thread
|
|
@param var_with_base 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
|
|
set_system_variable(THD *thd, struct sys_var_with_base *var_with_base,
|
|
enum enum_var_type var_type, Item *val)
|
|
{
|
|
set_var *var;
|
|
LEX *lex= thd->lex;
|
|
sp_head *sp= lex->sphead;
|
|
sp_pcontext *pctx= lex->get_sp_current_parsing_ctx();
|
|
|
|
/* No AUTOCOMMIT from a stored function or trigger. */
|
|
if (pctx && var_with_base->var == Sys_autocommit_ptr)
|
|
sp->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
|
|
|
|
#ifdef HAVE_REPLICATION
|
|
if (lex->uses_stored_routines() &&
|
|
((var_with_base->var == Sys_gtid_next_ptr
|
|
#ifdef HAVE_GTID_NEXT_LIST
|
|
|| var_with_base->var == Sys_gtid_next_list_ptr
|
|
#endif
|
|
) ||
|
|
Sys_gtid_purged_ptr == var_with_base->var))
|
|
{
|
|
my_error(ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION, MYF(0),
|
|
var_with_base->var->name.str);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (val && val->type() == Item::FIELD_ITEM &&
|
|
((Item_field*)val)->table_name)
|
|
{
|
|
my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), var_with_base->var->name.str);
|
|
return TRUE;
|
|
}
|
|
|
|
if (! (var= new set_var(var_type, var_with_base->var,
|
|
&var_with_base->base_name, val)))
|
|
return TRUE;
|
|
|
|
return lex->var_list.push_back(var);
|
|
}
|
|
|
|
|
|
/**
|
|
Make a new string allocated on THD's mem-root.
|
|
|
|
@param thd thread handler.
|
|
@param start_ptr start of the new string.
|
|
@param end_ptr end of the new string.
|
|
|
|
@return LEX_STRING object, containing a pointer to a newly
|
|
constructed/allocated string, and its length. The pointer is NULL
|
|
in case of out-of-memory error.
|
|
*/
|
|
LEX_STRING make_string(THD *thd, const char *start_ptr, const char *end_ptr)
|
|
{
|
|
LEX_STRING s;
|
|
|
|
s.length= end_ptr - start_ptr;
|
|
s.str= (char *) thd->alloc(s.length + 1);
|
|
|
|
if (s.str)
|
|
strmake(s.str, start_ptr, s.length);
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
/**
|
|
Helper action for a SET statement.
|
|
Used to SET a field of NEW row.
|
|
|
|
@param pc the parse context
|
|
@param trigger_field_name the NEW-row field name
|
|
@param expr_item the value expression being assigned
|
|
@param expr_query the value expression query
|
|
|
|
@return error status (true if error, false otherwise).
|
|
*/
|
|
|
|
bool set_trigger_new_row(Parse_context *pc,
|
|
LEX_STRING trigger_field_name,
|
|
Item *expr_item,
|
|
LEX_STRING expr_query)
|
|
{
|
|
THD *thd= pc->thd;
|
|
LEX *lex= thd->lex;
|
|
sp_head *sp= lex->sphead;
|
|
|
|
assert(expr_item);
|
|
assert(sp->m_trg_chistics.action_time == TRG_ACTION_BEFORE &&
|
|
(sp->m_trg_chistics.event == TRG_EVENT_INSERT ||
|
|
sp->m_trg_chistics.event == TRG_EVENT_UPDATE));
|
|
|
|
Item_trigger_field *trg_fld=
|
|
new (pc->mem_root) Item_trigger_field(POS(),
|
|
TRG_NEW_ROW,
|
|
trigger_field_name.str,
|
|
UPDATE_ACL, false);
|
|
|
|
if (trg_fld == NULL || trg_fld->itemize(pc, (Item **) &trg_fld))
|
|
return true;
|
|
assert(trg_fld->type() == Item::TRIGGER_FIELD_ITEM);
|
|
|
|
sp_instr_set_trigger_field *i=
|
|
new (pc->mem_root)
|
|
sp_instr_set_trigger_field(sp->instructions(),
|
|
lex,
|
|
trigger_field_name,
|
|
trg_fld, expr_item,
|
|
expr_query);
|
|
|
|
if (!i)
|
|
return true;
|
|
|
|
/*
|
|
Let us add this item to list of all Item_trigger_field
|
|
objects in trigger.
|
|
*/
|
|
sp->m_cur_instr_trig_field_items.link_in_list(trg_fld,
|
|
&trg_fld->next_trg_field);
|
|
|
|
return sp->add_instr(thd, i);
|
|
}
|
|
|
|
|
|
void sp_create_assignment_lex(THD *thd, const char *option_ptr)
|
|
{
|
|
sp_head *sp= thd->lex->sphead;
|
|
|
|
/*
|
|
We can come here in the following cases:
|
|
|
|
1. it's a regular SET statement outside stored programs
|
|
(thd->lex->sphead is NULL);
|
|
|
|
2. we're parsing a stored program normally (loading from mysql.proc, ...);
|
|
|
|
3. we're re-parsing SET-statement with a user variable after meta-data
|
|
change. It's guaranteed, that:
|
|
- this SET-statement deals with a user/system variable (otherwise, it
|
|
would be a different SP-instruction, and we would parse an expression);
|
|
- this SET-statement has a single user/system variable assignment
|
|
(that's how we generate sp_instr_stmt-instructions for SET-statements).
|
|
So, in this case, even if thd->lex->sphead is set, we should not process
|
|
further.
|
|
*/
|
|
|
|
if (!sp || // case #1
|
|
sp->is_invoked()) // case #3
|
|
{
|
|
return;
|
|
}
|
|
|
|
LEX *old_lex= thd->lex;
|
|
sp->reset_lex(thd);
|
|
LEX * const lex= thd->lex;
|
|
|
|
/* Set new LEX as if we at start of set rule. */
|
|
lex->sql_command= SQLCOM_SET_OPTION;
|
|
lex->var_list.empty();
|
|
lex->autocommit= false;
|
|
|
|
/*
|
|
It's a SET statement within SP. It will be either translated
|
|
into one or more sp_instr_stmt instructions, or it will be
|
|
sp_instr_set / sp_instr_set_trigger_field instructions.
|
|
In any case, position of SP-variable can not be determined
|
|
reliably. So, we set the start pointer of the current statement
|
|
to NULL.
|
|
*/
|
|
sp->m_parser_data.set_current_stmt_start_ptr(NULL);
|
|
sp->m_parser_data.set_option_start_ptr(option_ptr);
|
|
|
|
/* Inherit from outer lex. */
|
|
lex->option_type= old_lex->option_type;
|
|
}
|
|
|
|
|
|
/**
|
|
Create a SP instruction for a SET assignment.
|
|
|
|
@see sp_create_assignment_lex
|
|
|
|
@param thd Thread context
|
|
@param expr_end_ptr Option-value-expression end pointer
|
|
|
|
@return false if success, true otherwise.
|
|
*/
|
|
|
|
bool sp_create_assignment_instr(THD *thd, const char *expr_end_ptr)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
sp_head *sp= lex->sphead;
|
|
|
|
/*
|
|
We can come here in the following cases:
|
|
|
|
1. it's a regular SET statement outside stored programs
|
|
(lex->sphead is NULL);
|
|
|
|
2. we're parsing a stored program normally (loading from mysql.proc, ...);
|
|
|
|
3. we're re-parsing SET-statement with a user variable after meta-data
|
|
change. It's guaranteed, that:
|
|
- this SET-statement deals with a user/system variable (otherwise, it
|
|
would be a different SP-instruction, and we would parse an expression);
|
|
- this SET-statement has a single user/system variable assignment
|
|
(that's how we generate sp_instr_stmt-instructions for SET-statements).
|
|
So, in this case, even if lex->sphead is set, we should not process
|
|
further.
|
|
*/
|
|
|
|
if (!sp || // case #1
|
|
sp->is_invoked()) // case #3
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!lex->var_list.is_empty())
|
|
{
|
|
/* Extract expression string. */
|
|
|
|
const char *expr_start_ptr= sp->m_parser_data.get_option_start_ptr();
|
|
|
|
LEX_STRING expr;
|
|
expr.str= (char *) expr_start_ptr;
|
|
expr.length= expr_end_ptr - expr_start_ptr;
|
|
|
|
/* Construct SET-statement query. */
|
|
|
|
LEX_STRING set_stmt_query;
|
|
|
|
set_stmt_query.length= expr.length + 3;
|
|
set_stmt_query.str= (char *) thd->alloc(set_stmt_query.length + 1);
|
|
|
|
if (!set_stmt_query.str)
|
|
return true;
|
|
|
|
strmake(strmake(set_stmt_query.str, "SET", 3),
|
|
expr.str, expr.length);
|
|
|
|
/*
|
|
We have assignment to user or system variable or option setting, so we
|
|
should construct sp_instr_stmt for it.
|
|
*/
|
|
|
|
sp_instr_stmt *i=
|
|
new (thd->mem_root)
|
|
sp_instr_stmt(sp->instructions(), lex, set_stmt_query);
|
|
|
|
if (!i || sp->add_instr(thd, i))
|
|
return true;
|
|
}
|
|
|
|
/* Remember option_type of the currently parsed LEX. */
|
|
enum_var_type inner_option_type= lex->option_type;
|
|
|
|
if (sp->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;
|
|
}
|
|
|
|
|