diff options
Diffstat (limited to 'sql/sql_lex.cc')
-rw-r--r-- | sql/sql_lex.cc | 860 |
1 files changed, 587 insertions, 273 deletions
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 92fec3e1ec1..8edd9b3fbd8 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -280,7 +280,6 @@ void Lex_input_stream::reset(char *buffer, unsigned int length) { yylineno= 1; - yytoklen= 0; yylval= NULL; lookahead_token= -1; lookahead_yylval= NULL; @@ -325,9 +324,7 @@ void Lex_input_stream::body_utf8_start(THD *thd, const char *begin_ptr) DBUG_ASSERT(begin_ptr); DBUG_ASSERT(m_cpp_buf <= begin_ptr && begin_ptr <= m_cpp_buf + m_buf_length); - uint body_utf8_length= - (m_buf_length / thd->variables.character_set_client->mbminlen) * - my_charset_utf8_bin.mbmaxlen; + uint body_utf8_length= get_body_utf8_maximum_length(thd); m_body_utf8= (char *) thd->alloc(body_utf8_length + 1); m_body_utf8_ptr= m_body_utf8; @@ -336,6 +333,22 @@ void Lex_input_stream::body_utf8_start(THD *thd, const char *begin_ptr) m_cpp_utf8_processed_ptr= begin_ptr; } + +uint Lex_input_stream::get_body_utf8_maximum_length(THD *thd) +{ + /* + String literals can grow during escaping: + 1a. Character string '<TAB>' can grow to '\t', 3 bytes to 4 bytes growth. + 1b. Character string '1000 times <TAB>' grows from + 1002 to 2002 bytes (including quotes), which gives a little bit + less than 2 times growth. + "2" should be a reasonable multiplier that safely covers escaping needs. + */ + return (m_buf_length / thd->variables.character_set_client->mbminlen) * + my_charset_utf8_bin.mbmaxlen * 2/*for escaping*/; +} + + /** @brief The operation appends unprocessed part of pre-processed buffer till the given pointer (ptr) and sets m_cpp_utf8_processed_ptr to end_ptr. @@ -403,15 +416,15 @@ void Lex_input_stream::body_utf8_append(const char *ptr) operation. */ -void Lex_input_stream::body_utf8_append_literal(THD *thd, - const LEX_STRING *txt, - CHARSET_INFO *txt_cs, - const char *end_ptr) +void Lex_input_stream::body_utf8_append_ident(THD *thd, + const LEX_STRING *txt, + const char *end_ptr) { if (!m_cpp_utf8_processed_ptr) return; LEX_STRING utf_txt; + CHARSET_INFO *txt_cs= thd->charset(); if (!my_charset_same(txt_cs, &my_charset_utf8_general_ci)) { @@ -435,6 +448,189 @@ void Lex_input_stream::body_utf8_append_literal(THD *thd, m_cpp_utf8_processed_ptr= end_ptr; } + + + +extern "C" { + +/** + Escape a character. Consequently puts "escape" and "wc" characters into + the destination utf8 string. + @param cs - the character set (utf8) + @param escape - the escape character (backslash, single quote, double quote) + @param wc - the character to be escaped + @param str - the destination string + @param end - the end of the destination string + @returns - a code according to the wc_mb() convension. +*/ +int my_wc_mb_utf8_with_escape(CHARSET_INFO *cs, my_wc_t escape, my_wc_t wc, + uchar *str, uchar *end) +{ + DBUG_ASSERT(escape > 0); + if (str + 1 >= end) + return MY_CS_TOOSMALL2; // Not enough space, need at least two bytes. + *str= (uchar)escape; + int cnvres= my_charset_utf8_handler.wc_mb(cs, wc, str + 1, end); + if (cnvres > 0) + return cnvres + 1; // The character was normally put + if (cnvres == MY_CS_ILUNI) + return MY_CS_ILUNI; // Could not encode "wc" (e.g. non-BMP character) + DBUG_ASSERT(cnvres <= MY_CS_TOOSMALL); + return cnvres - 1; // Not enough space +} + + +/** + Optionally escape a character. + If "escape" is non-zero, then both "escape" and "wc" are put to + the destination string. Otherwise, only "wc" is put. + @param cs - the character set (utf8) + @param wc - the character to be optionally escaped + @param escape - the escape character, or 0 + @param ewc - the escaped replacement of "wc" (e.g. 't' for '\t') + @param str - the destination string + @param end - the end of the destination string + @returns - a code according to the wc_mb() conversion. +*/ +int my_wc_mb_utf8_opt_escape(CHARSET_INFO *cs, + my_wc_t wc, my_wc_t escape, my_wc_t ewc, + uchar *str, uchar *end) +{ + return escape ? my_wc_mb_utf8_with_escape(cs, escape, ewc, str, end) : + my_charset_utf8_handler.wc_mb(cs, wc, str, end); +} + +/** + Encode a character with optional backlash escaping and quote escaping. + Quote marks are escaped using another quote mark. + Additionally, if "escape" is non-zero, then special characters are + also escaped using "escape". + Otherwise (if "escape" is zero, e.g. in case of MODE_NO_BACKSLASH_ESCAPES), + then special characters are not escaped and handled as normal characters. + + @param cs - the character set (utf8) + @param wc - the character to be encoded + @param str - the destination string + @param end - the end of the destination string + @param sep - the string delimiter (e.g. ' or ") + @param escape - the escape character (backslash, or 0) + @returns - a code according to the wc_mb() convension. +*/ +int my_wc_mb_utf8_escape(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end, + my_wc_t sep, my_wc_t escape) +{ + DBUG_ASSERT(escape == 0 || escape == '\\'); + DBUG_ASSERT(sep == '"' || sep == '\''); + switch (wc) { + case 0: return my_wc_mb_utf8_opt_escape(cs, wc, escape, '0', str, end); + case '\t': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 't', str, end); + case '\r': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'r', str, end); + case '\n': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'n', str, end); + case '\032': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'Z', str, end); + case '\'': + case '\"': + if (wc == sep) + return my_wc_mb_utf8_with_escape(cs, wc, wc, str, end); + } + return my_charset_utf8_handler.wc_mb(cs, wc, str, end); // No escaping needed +} + + +/** wc_mb() compatible routines for all sql_mode and delimiter combinations */ +int my_wc_mb_utf8_escape_single_quote_and_backslash(CHARSET_INFO *cs, + my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '\'', '\\'); +} + + +int my_wc_mb_utf8_escape_double_quote_and_backslash(CHARSET_INFO *cs, + my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '"', '\\'); +} + + +int my_wc_mb_utf8_escape_single_quote(CHARSET_INFO *cs, my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '\'', 0); +} + + +int my_wc_mb_utf8_escape_double_quote(CHARSET_INFO *cs, my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '"', 0); +} + +}; // End of extern "C" + + +/** + Get an escaping function, depending on the current sql_mode and the + string separator. +*/ +my_charset_conv_wc_mb +Lex_input_stream::get_escape_func(THD *thd, my_wc_t sep) const +{ + return thd->backslash_escapes() ? + (sep == '"' ? my_wc_mb_utf8_escape_double_quote_and_backslash: + my_wc_mb_utf8_escape_single_quote_and_backslash) : + (sep == '"' ? my_wc_mb_utf8_escape_double_quote: + my_wc_mb_utf8_escape_single_quote); +} + + +/** + Append a text literal to the end of m_body_utf8. + The string is escaped according to the current sql_mode and the + string delimiter (e.g. ' or "). + + @param thd - current THD + @param txt - the string to be appended to m_body_utf8. + Note, the string must be already unescaped. + @param cs - the character set of the string + @param end_ptr - m_cpp_utf8_processed_ptr will be set to this value + (see body_utf8_append_ident for details) + @param sep - the string delimiter (single or double quote) +*/ +void Lex_input_stream::body_utf8_append_escape(THD *thd, + const LEX_STRING *txt, + CHARSET_INFO *cs, + const char *end_ptr, + my_wc_t sep) +{ + DBUG_ASSERT(sep == '\'' || sep == '"'); + if (!m_cpp_utf8_processed_ptr) + return; + uint errors; + /** + We previously alloced m_body_utf8 to be able to store the query with all + strings properly escaped. See get_body_utf8_maximum_length(). + So here we have guaranteedly enough space to append any string literal + with escaping. Passing txt->length*2 as "available space" is always safe. + For better safety purposes we could calculate get_body_utf8_maximum_length() + every time we append a string, but this would affect performance negatively, + so let's check that we don't get beyond the allocated buffer in + debug build only. + */ + DBUG_ASSERT(m_body_utf8 + get_body_utf8_maximum_length(thd) >= + m_body_utf8_ptr + txt->length * 2); + uint32 cnv_length= my_convert_using_func(m_body_utf8_ptr, txt->length * 2, + &my_charset_utf8_general_ci, + get_escape_func(thd, sep), + txt->str, txt->length, + cs, cs->cset->mb_wc, + &errors); + m_body_utf8_ptr+= cnv_length; + *m_body_utf8_ptr= 0; + m_cpp_utf8_processed_ptr= end_ptr; +} + + void Lex_input_stream::add_digest_token(uint token, LEX_YYSTYPE yylval) { if (m_digest != NULL) @@ -485,7 +681,7 @@ void lex_start(THD *thd) lex->unit.next= lex->unit.master= lex->unit.link_next= lex->unit.return_to= 0; lex->unit.prev= lex->unit.link_prev= 0; - lex->unit.slave= lex->unit.global_parameters= lex->current_select= + lex->unit.slave= lex->current_select= lex->all_selects_list= &lex->select_lex; lex->select_lex.master= &lex->unit; lex->select_lex.prev= &lex->unit.slave; @@ -498,6 +694,8 @@ void lex_start(THD *thd) if (lex->select_lex.group_list_ptrs) lex->select_lex.group_list_ptrs->clear(); lex->describe= 0; + lex->analyze_stmt= 0; + lex->explain_json= false; lex->subqueries= FALSE; lex->context_analysis_only= 0; lex->derived_tables= 0; @@ -517,7 +715,6 @@ void lex_start(THD *thd) lex->duplicates= DUP_ERROR; lex->ignore= 0; lex->spname= NULL; - lex->sphead= NULL; lex->spcont= NULL; lex->proc_list.first= 0; lex->escape_used= FALSE; @@ -527,38 +724,27 @@ void lex_start(THD *thd) lex->use_only_table_context= FALSE; lex->parse_vcol_expr= FALSE; lex->check_exists= FALSE; + lex->create_info.lex_start(); lex->verbose= 0; - lex->name.str= 0; - lex->name.length= 0; + lex->name= null_lex_str; lex->event_parse_data= NULL; lex->profile_options= PROFILE_NONE; lex->nest_level=0 ; lex->select_lex.nest_level_base= &lex->unit; lex->allow_sum_func= 0; lex->in_sum_func= NULL; - /* - ok, there must be a better solution for this, long-term - I tried "bzero" in the sql_yacc.yy code, but that for - some reason made the values zero, even if they were set - */ - lex->server_options.server_name= 0; - lex->server_options.server_name_length= 0; - lex->server_options.host= 0; - lex->server_options.db= 0; - lex->server_options.username= 0; - lex->server_options.password= 0; - lex->server_options.scheme= 0; - lex->server_options.socket= 0; - lex->server_options.owner= 0; - lex->server_options.port= -1; - lex->is_lex_started= TRUE; lex->used_tables= 0; lex->only_view= FALSE; lex->reset_slave_info.all= false; lex->limit_rows_examined= 0; lex->limit_rows_examined_cnt= ULONGLONG_MAX; + lex->var_list.empty(); + lex->stmt_var_list.empty(); + lex->proc_list.elements=0; + + lex->is_lex_started= TRUE; DBUG_VOID_RETURN; } @@ -585,8 +771,20 @@ void lex_end_stage1(LEX *lex) } reset_dynamic(&lex->plugins); - delete lex->sphead; - lex->sphead= NULL; + if (lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_PREPARE) + { + /* + Don't delete lex->sphead, it'll be needed for EXECUTE. + Note that of all statements that populate lex->sphead + only SQLCOM_COMPOUND can be PREPAREd + */ + DBUG_ASSERT(lex->sphead == 0 || lex->sql_command == SQLCOM_COMPOUND); + } + else + { + delete lex->sphead; + lex->sphead= NULL; + } DBUG_VOID_RETURN; } @@ -601,7 +799,7 @@ void lex_end_stage2(LEX *lex) DBUG_ENTER("lex_end_stage2"); /* Reset LEX_MASTER_INFO */ - lex->mi.reset(); + lex->mi.reset(lex->sql_command == SQLCOM_CHANGE_MASTER); DBUG_VOID_RETURN; } @@ -679,7 +877,7 @@ static LEX_STRING get_token(Lex_input_stream *lip, uint skip, uint length) { LEX_STRING tmp; lip->yyUnget(); // ptr points now after last token char - tmp.length=lip->yytoklen=length; + tmp.length= length; tmp.str= lip->m_thd->strmake(lip->get_tok_start() + skip, tmp.length); lip->m_cpp_text_start= lip->get_cpp_tok_start() + skip; @@ -703,7 +901,7 @@ static LEX_STRING get_quoted_token(Lex_input_stream *lip, const char *from, *end; char *to; lip->yyUnget(); // ptr points now after last token char - tmp.length= lip->yytoklen=length; + tmp.length= length; tmp.str=(char*) lip->m_thd->alloc(tmp.length+1); from= lip->get_tok_start() + skip; to= tmp.str; @@ -725,135 +923,152 @@ static LEX_STRING get_quoted_token(Lex_input_stream *lip, } +static size_t +my_unescape(CHARSET_INFO *cs, char *to, const char *str, const char *end, + int sep, bool backslash_escapes) +{ + char *start= to; + for ( ; str != end ; str++) + { +#ifdef USE_MB + int l; + if (use_mb(cs) && (l= my_ismbchar(cs, str, end))) + { + while (l--) + *to++ = *str++; + str--; + continue; + } +#endif + if (backslash_escapes && *str == '\\' && str + 1 != end) + { + switch(*++str) { + case 'n': + *to++='\n'; + break; + case 't': + *to++= '\t'; + break; + case 'r': + *to++ = '\r'; + break; + case 'b': + *to++ = '\b'; + break; + case '0': + *to++= 0; // Ascii null + break; + case 'Z': // ^Z must be escaped on Win32 + *to++='\032'; + break; + case '_': + case '%': + *to++= '\\'; // remember prefix for wildcard + /* Fall through */ + default: + *to++= *str; + break; + } + } + else if (*str == sep) + *to++= *str++; // Two ' or " + else + *to++ = *str; + } + *to= 0; + return to - start; +} + + +size_t +Lex_input_stream::unescape(CHARSET_INFO *cs, char *to, + const char *str, const char *end, + int sep) +{ + return my_unescape(cs, to, str, end, sep, m_thd->backslash_escapes()); +} + + /* Return an unescaped text literal without quotes Fix sometimes to do only one scan of the string */ -static char *get_text(Lex_input_stream *lip, int pre_skip, int post_skip) +bool Lex_input_stream::get_text(LEX_STRING *dst, uint sep, + int pre_skip, int post_skip) { - reg1 uchar c,sep; + reg1 uchar c; uint found_escape=0; - CHARSET_INFO *cs= lip->m_thd->charset(); + CHARSET_INFO *cs= m_thd->charset(); - lip->tok_bitmap= 0; - sep= lip->yyGetLast(); // String should end with this - while (! lip->eof()) + tok_bitmap= 0; + while (! eof()) { - c= lip->yyGet(); - lip->tok_bitmap|= c; + c= yyGet(); + tok_bitmap|= c; #ifdef USE_MB { int l; if (use_mb(cs) && (l = my_ismbchar(cs, - lip->get_ptr() -1, - lip->get_end_of_query()))) { - lip->skip_binary(l-1); + get_ptr() -1, + get_end_of_query()))) { + skip_binary(l-1); continue; } } #endif if (c == '\\' && - !(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)) + !(m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)) { // Escaped character found_escape=1; - if (lip->eof()) - return 0; - lip->yySkip(); + if (eof()) + return true; + yySkip(); } else if (c == sep) { - if (c == lip->yyGet()) // Check if two separators in a row + if (c == yyGet()) // Check if two separators in a row { found_escape=1; // duplicate. Remember for delete continue; } else - lip->yyUnget(); + yyUnget(); /* Found end. Unescape and return string */ const char *str, *end; - char *start; - str= lip->get_tok_start(); - end= lip->get_ptr(); + str= get_tok_start(); + end= get_ptr(); /* Extract the text from the token */ str += pre_skip; end -= post_skip; DBUG_ASSERT(end >= str); - if (!(start= (char*) lip->m_thd->alloc((uint) (end-str)+1))) - return (char*) ""; // Sql_alloc has set error flag + if (!(dst->str= (char*) m_thd->alloc((uint) (end - str) + 1))) + { + dst->str= (char*) ""; // Sql_alloc has set error flag + dst->length= 0; + return true; + } - lip->m_cpp_text_start= lip->get_cpp_tok_start() + pre_skip; - lip->m_cpp_text_end= lip->get_cpp_ptr() - post_skip; + m_cpp_text_start= get_cpp_tok_start() + pre_skip; + m_cpp_text_end= get_cpp_ptr() - post_skip; if (!found_escape) { - lip->yytoklen=(uint) (end-str); - memcpy(start,str,lip->yytoklen); - start[lip->yytoklen]=0; + memcpy(dst->str, str, dst->length= (end - str)); + dst->str[dst->length]= 0; } else { - char *to; - - for (to=start ; str != end ; str++) - { -#ifdef USE_MB - int l; - if (use_mb(cs) && - (l = my_ismbchar(cs, str, end))) { - while (l--) - *to++ = *str++; - str--; - continue; - } -#endif - if (!(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) && - *str == '\\' && str+1 != end) - { - switch(*++str) { - case 'n': - *to++='\n'; - break; - case 't': - *to++= '\t'; - break; - case 'r': - *to++ = '\r'; - break; - case 'b': - *to++ = '\b'; - break; - case '0': - *to++= 0; // Ascii null - break; - case 'Z': // ^Z must be escaped on Win32 - *to++='\032'; - break; - case '_': - case '%': - *to++= '\\'; // remember prefix for wildcard - /* Fall through */ - default: - *to++= *str; - break; - } - } - else if (*str == sep) - *to++= *str++; // Two ' or " - else - *to++ = *str; - } - *to=0; - lip->yytoklen=(uint) (to-start); + dst->length= unescape(cs, dst->str, str, end, sep); } - return start; + return false; } } - return 0; // unexpected end of query + return true; // unexpected end of query } @@ -1064,7 +1279,7 @@ int MYSQLlex(YYSTYPE *yylval, THD *thd) static int lex_one_token(YYSTYPE *yylval, THD *thd) { - reg1 uchar c; + reg1 uchar UNINIT_VAR(c); bool comment_closed; int tokval, result_state; uint length; @@ -1075,7 +1290,6 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) const uchar *const state_map= cs->state_map; const uchar *const ident_map= cs->ident_map; - LINT_INIT(c); lip->yylval=yylval; // The global state lip->start_token(); @@ -1152,6 +1366,8 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) return((int) c); case MY_LEX_IDENT_OR_NCHAR: + { + uint sep; if (lip->yyPeek() != '\'') { state= MY_LEX_IDENT; @@ -1159,15 +1375,20 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) } /* Found N'string' */ lip->yySkip(); // Skip ' - if (!(yylval->lex_str.str = get_text(lip, 2, 1))) + if (lip->get_text(&yylval->lex_str, (sep= lip->yyGetLast()), 2, 1)) { state= MY_LEX_CHAR; // Read char by char break; } - yylval->lex_str.length= lip->yytoklen; + + lip->body_utf8_append(lip->m_cpp_text_start); + lip->body_utf8_append_escape(thd, &yylval->lex_str, + national_charset_info, + lip->m_cpp_text_end, sep); + lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1; return(NCHAR_STRING); - + } case MY_LEX_IDENT_OR_HEX: if (lip->yyPeek() == '\'') { // Found x'hex-number' @@ -1272,8 +1493,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); // IDENT or IDENT_QUOTED @@ -1377,8 +1597,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); @@ -1421,8 +1640,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(IDENT_QUOTED); } @@ -1531,24 +1749,23 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) /* " used for strings */ /* fall through */ case MY_LEX_STRING: // Incomplete text string - if (!(yylval->lex_str.str = get_text(lip, 1, 1))) + { + uint sep; + if (lip->get_text(&yylval->lex_str, (sep= lip->yyGetLast()), 1, 1)) { state= MY_LEX_CHAR; // Read char by char break; } - yylval->lex_str.length=lip->yytoklen; - + CHARSET_INFO *strcs= lip->m_underscore_cs ? lip->m_underscore_cs : cs; lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, - lip->m_underscore_cs ? lip->m_underscore_cs : cs, - lip->m_cpp_text_end); - + lip->body_utf8_append_escape(thd, &yylval->lex_str, strcs, + lip->m_cpp_text_end, sep); lip->m_underscore_cs= NULL; lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1; return(TEXT_STRING); - + } case MY_LEX_COMMENT: // Comment lex->select_lex.options|= OPTION_FOUND_COMMENT; while ((c = lip->yyGet()) != '\n' && c) ; @@ -1623,7 +1840,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) else { #ifdef WITH_WSREP - if (version == 99997 && thd->wsrep_exec_mode == LOCAL_STATE) + if (WSREP(thd) && version == 99997 && thd->wsrep_exec_mode == LOCAL_STATE) { WSREP_DEBUG("consistency check: %s", thd->query()); thd->wsrep_consistency_check= CONSISTENCY_CHECK_DECLARED; @@ -1797,8 +2014,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); } @@ -1852,7 +2068,6 @@ void st_select_lex_unit::init_query() { st_select_lex_node::init_query(); linkage= GLOBAL_OPTIONS_TYPE; - global_parameters= first_select(); select_limit_cnt= HA_POS_ERROR; offset_limit_cnt= 0; union_distinct= 0; @@ -1861,6 +2076,7 @@ void st_select_lex_unit::init_query() union_result= 0; table= 0; fake_select_lex= 0; + saved_fake_select_lex= 0; cleaned= 0; item_list.empty(); describe= 0; @@ -1895,7 +2111,7 @@ void st_select_lex::init_query() thus push_context should be moved to a place where query initialization is checked for failure. */ - parent_lex->push_context(&context); + parent_lex->push_context(&context, parent_lex->thd->mem_root); cond_count= between_count= with_wild= 0; max_equal_elems= 0; ref_pointer_array= 0; @@ -1935,7 +2151,6 @@ void st_select_lex::init_select() in_sum_expr= with_wild= 0; options= 0; sql_cache= SQL_CACHE_UNSPECIFIED; - braces= 0; interval_list.empty(); ftfunc_list_alloc.empty(); inner_sum_func_list= 0; @@ -2249,15 +2464,79 @@ bool st_select_lex::test_limit() } -st_select_lex_unit* st_select_lex_unit::master_unit() + +st_select_lex* st_select_lex_unit::outer_select() { - return this; + return (st_select_lex*) master; } -st_select_lex* st_select_lex_unit::outer_select() +ha_rows st_select_lex::get_offset() { - return (st_select_lex*) master; + ulonglong val= 0; + + if (offset_limit) + { + // see comment for st_select_lex::get_limit() + bool fix_fields_successful= true; + if (!offset_limit->fixed) + { + fix_fields_successful= !offset_limit->fix_fields(master_unit()->thd, + NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? offset_limit->val_uint() : HA_POS_ERROR; + } + + return (ha_rows)val; +} + + +ha_rows st_select_lex::get_limit() +{ + ulonglong val= HA_POS_ERROR; + + if (select_limit) + { + /* + fix_fields() has not been called for select_limit. That's due to the + historical reasons -- this item could be only of type Item_int, and + Item_int does not require fix_fields(). Thus, fix_fields() was never + called for select_limit. + + Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. + However, the fix_fields() behavior was not updated, which led to a crash + in some cases. + + There is no single place where to call fix_fields() for LIMIT / OFFSET + items during the fix-fields-phase. Thus, for the sake of readability, + it was decided to do it here, on the evaluation phase (which is a + violation of design, but we chose the lesser of two evils). + + We can call fix_fields() here, because select_limit can be of two + types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, + and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) + has the following properties: + 1) it does not affect other items; + 2) it does not fail. + + Nevertheless DBUG_ASSERT was added to catch future changes in + fix_fields() implementation. Also added runtime check against a result + of fix_fields() in order to handle error condition in non-debug build. + */ + bool fix_fields_successful= true; + if (!select_limit->fixed) + { + fix_fields_successful= !select_limit->fix_fields(master_unit()->thd, + NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? select_limit->val_uint() : HA_POS_ERROR; + } + + return (ha_rows)val; } @@ -2272,11 +2551,12 @@ bool st_select_lex::add_gorder_to_list(THD *thd, Item *item, bool asc) return add_to_list(thd, gorder_list, item, asc); } + bool st_select_lex::add_item_to_list(THD *thd, Item *item) { DBUG_ENTER("st_select_lex::add_item_to_list"); DBUG_PRINT("info", ("Item: 0x%lx", (long) item)); - DBUG_RETURN(item_list.push_back(item)); + DBUG_RETURN(item_list.push_back(item, thd->mem_root)); } @@ -2286,15 +2566,9 @@ bool st_select_lex::add_group_to_list(THD *thd, Item *item, bool asc) } -bool st_select_lex::add_ftfunc_to_list(Item_func_match *func) -{ - return !func || ftfunc_list->push_back(func); // end of memory? -} - - -st_select_lex_unit* st_select_lex::master_unit() +bool st_select_lex::add_ftfunc_to_list(THD *thd, Item_func_match *func) { - return (st_select_lex_unit*) master; + return !func || ftfunc_list->push_back(func, thd->mem_root); // end of memory? } @@ -2399,7 +2673,7 @@ void st_select_lex_unit::print(String *str, enum_query_type query_type) if (sl->braces) str->append(')'); } - if (fake_select_lex == global_parameters) + if (fake_select_lex) { if (fake_select_lex->order_list.elements) { @@ -2410,6 +2684,8 @@ void st_select_lex_unit::print(String *str, enum_query_type query_type) } fake_select_lex->print_limit(thd, str, query_type); } + else if (saved_fake_select_lex) + saved_fake_select_lex->print_limit(thd, str, query_type); } @@ -2421,30 +2697,22 @@ void st_select_lex::print_order(String *str, { if (order->counter_used) { - if (query_type != QT_VIEW_INTERNAL) + char buffer[20]; + size_t length= my_snprintf(buffer, 20, "%d", order->counter); + str->append(buffer, (uint) length); + } + else + { + /* replace numeric reference with equivalent for ORDER constant */ + if (order->item[0]->type() == Item::INT_ITEM && + order->item[0]->basic_const_item()) { - char buffer[20]; - size_t length= my_snprintf(buffer, 20, "%d", order->counter); - str->append(buffer, (uint) length); + /* make it expression instead of integer constant */ + str->append(STRING_WITH_LEN("''")); } else - { - /* replace numeric reference with expression */ - if (order->item[0]->type() == Item::INT_ITEM && - order->item[0]->basic_const_item()) - { - char buffer[20]; - size_t length= my_snprintf(buffer, 20, "%d", order->counter); - str->append(buffer, (uint) length); - /* make it expression instead of integer constant */ - str->append(STRING_WITH_LEN("+0")); - } - else - (*order->item)->print(str, query_type); - } + (*order->item)->print(str, query_type); } - else - (*order->item)->print(str, query_type); if (!order->asc) str->append(STRING_WITH_LEN(" desc")); if (order->next) @@ -2460,7 +2728,7 @@ void st_select_lex::print_limit(THD *thd, SELECT_LEX_UNIT *unit= master_unit(); Item_subselect *item= unit->item; - if (item && unit->global_parameters == this) + if (item && unit->global_parameters() == this) { Item_subselect::subs_type subs_type= item->substype(); if (subs_type == Item_subselect::EXISTS_SUBS || @@ -2600,14 +2868,14 @@ void Query_tables_list::destroy_query_tables_list() LEX::LEX() : explain(NULL), - result(0), option_type(OPT_DEFAULT), is_lex_started(0), - limit_rows_examined_cnt(ULONGLONG_MAX) + result(0), arena_for_set_stmt(0), mem_root_for_set_stmt(0), + option_type(OPT_DEFAULT), context_analysis_only(0), sphead(0), + is_lex_started(0), limit_rows_examined_cnt(ULONGLONG_MAX) { - my_init_dynamic_array2(&plugins, sizeof(plugin_ref), - plugins_static_buffer, - INITIAL_LEX_PLUGIN_LIST_SIZE, - INITIAL_LEX_PLUGIN_LIST_SIZE, 0); + init_dynamic_array2(&plugins, sizeof(plugin_ref), plugins_static_buffer, + INITIAL_LEX_PLUGIN_LIST_SIZE, + INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); } @@ -2838,7 +3106,7 @@ uint8 LEX::get_effective_with_check(TABLE_LIST *view) bool LEX::copy_db_to(char **p_db, size_t *p_db_length) const { - if (sphead) + if (sphead && sphead->m_name.str) { DBUG_ASSERT(sphead->m_db.str && sphead->m_db.length); /* @@ -2853,97 +3121,39 @@ LEX::copy_db_to(char **p_db, size_t *p_db_length) const return thd->copy_db_to(p_db, p_db_length); } -/* - initialize limit counters +/** + Initialize offset and limit counters. - SYNOPSIS - st_select_lex_unit::set_limit() - values - SELECT_LEX with initial values for counters + @param sl SELECT_LEX to get offset and limit from. */ void st_select_lex_unit::set_limit(st_select_lex *sl) { - ha_rows select_limit_val; - ulonglong val; - - DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); - if (sl->select_limit) - { - Item *item = sl->select_limit; - /* - fix_fields() has not been called for sl->select_limit. That's due to the - historical reasons -- this item could be only of type Item_int, and - Item_int does not require fix_fields(). Thus, fix_fields() was never - called for sl->select_limit. + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. - However, the fix_fields() behavior was not updated, which led to a crash - in some cases. - - There is no single place where to call fix_fields() for LIMIT / OFFSET - items during the fix-fields-phase. Thus, for the sake of readability, - it was decided to do it here, on the evaluation phase (which is a - violation of design, but we chose the lesser of two evils). - - We can call fix_fields() here, because sl->select_limit can be of two - types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, - and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) - has the following specific: - 1) it does not affect other items; - 2) it does not fail. - - Nevertheless DBUG_ASSERT was added to catch future changes in - fix_fields() implementation. Also added runtime check against a result - of fix_fields() in order to handle error condition in non-debug build. - */ - bool fix_fields_successful= true; - if (!item->fixed) - { - fix_fields_successful= !item->fix_fields(thd, NULL); - - DBUG_ASSERT(fix_fields_successful); - } - val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; - } + offset_limit_cnt= sl->get_offset(); + select_limit_cnt= sl->get_limit(); + if (select_limit_cnt + offset_limit_cnt >= select_limit_cnt) + select_limit_cnt+= offset_limit_cnt; else - val= HA_POS_ERROR; + select_limit_cnt= HA_POS_ERROR; +} - select_limit_val= (ha_rows)val; -#ifndef BIG_TABLES - /* - Check for overflow : ha_rows can be smaller then ulonglong if - BIG_TABLES is off. - */ - if (val != (ulonglong)select_limit_val) - select_limit_val= HA_POS_ERROR; -#endif - if (sl->offset_limit) - { - Item *item = sl->offset_limit; - // see comment for sl->select_limit branch. - bool fix_fields_successful= true; - if (!item->fixed) - { - fix_fields_successful= !item->fix_fields(thd, NULL); - DBUG_ASSERT(fix_fields_successful); - } - val= fix_fields_successful ? item->val_uint() : 0; - } - else - val= 0; +/** + Decide if a temporary table is needed for the UNION. - offset_limit_cnt= (ha_rows)val; -#ifndef BIG_TABLES - /* Check for truncation. */ - if (val != (ulonglong)offset_limit_cnt) - offset_limit_cnt= HA_POS_ERROR; -#endif - select_limit_cnt= select_limit_val + offset_limit_cnt; - if (select_limit_cnt < select_limit_val) - select_limit_cnt= HA_POS_ERROR; // no limit -} + @retval true A temporary table is needed. + @retval false A temporary table is not needed. + */ +bool st_select_lex_unit::union_needs_tmp_table() +{ + return union_distinct != NULL || + global_parameters()->order_list.elements != 0 || + thd->lex->sql_command == SQLCOM_INSERT_SELECT || + thd->lex->sql_command == SQLCOM_REPLACE_SELECT; +} /** @brief Set the initial purpose of this TABLE_LIST object in the list of used @@ -3476,10 +3686,10 @@ void st_select_lex::alloc_index_hints (THD *thd) */ bool st_select_lex::add_index_hint (THD *thd, char *str, uint length) { - return index_hints->push_front (new (thd->mem_root) + return index_hints->push_front(new (thd->mem_root) Index_hint(current_index_hint_type, current_index_hint_clause, - str, length)); + str, length), thd->mem_root); } @@ -3538,6 +3748,8 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) bool empty_union_result= true; bool is_correlated_unit= false; + bool first= true; + bool union_plan_saved= false; /* If the subquery is a UNION, optimize all the subqueries in the UNION. If there is no UNION, then the loop will execute once for the subquery. @@ -3545,13 +3757,24 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) { JOIN *inner_join= sl->join; + if (first) + first= false; + else + { + if (!union_plan_saved) + { + union_plan_saved= true; + if (un->save_union_explain(un->thd->lex->explain)) + return true; /* Failure */ + } + } if (!inner_join) continue; SELECT_LEX *save_select= un->thd->lex->current_select; ulonglong save_options; int res; /* We need only 1 row to determine existence */ - un->set_limit(un->global_parameters); + un->set_limit(un->global_parameters()); un->thd->lex->current_select= sl; save_options= inner_join->select_options; if (options & SELECT_DESCRIBE) @@ -3811,7 +4034,7 @@ bool SELECT_LEX::merge_subquery(THD *thd, TABLE_LIST *derived, { derived->wrap_into_nested_join(subq_select->top_join_list); - ftfunc_list->concat(subq_select->ftfunc_list); + ftfunc_list->append(subq_select->ftfunc_list); if (join || thd->lex->sql_command == SQLCOM_UPDATE_MULTI || thd->lex->sql_command == SQLCOM_DELETE_MULTI) @@ -3820,7 +4043,7 @@ bool SELECT_LEX::merge_subquery(THD *thd, TABLE_LIST *derived, Item_in_subselect *in_subq; while ((in_subq= li++)) { - sj_subselects.push_back(in_subq); + sj_subselects.push_back(in_subq, thd->mem_root); if (in_subq->emb_on_expr_nest == NO_JOIN_NEST) in_subq->emb_on_expr_nest= derived; } @@ -3886,7 +4109,8 @@ void SELECT_LEX::update_used_tables() tab->covering_keys.intersect(tab->keys_in_use_for_query); tab->merge_keys.clear_all(); bitmap_clear_all(tab->read_set); - bitmap_clear_all(tab->vcol_set); + if (tab->vcol_set) + bitmap_clear_all(tab->vcol_set); break; } } @@ -3964,7 +4188,7 @@ void SELECT_LEX::update_used_tables() } for (ORDER *order= group_list.first; order; order= order->next) (*order->item)->update_used_tables(); - if (!master_unit()->is_union() || master_unit()->global_parameters != this) + if (!master_unit()->is_union() || master_unit()->global_parameters() != this) { for (ORDER *order= order_list.first; order; order= order->next) (*order->item)->update_used_tables(); @@ -4184,7 +4408,7 @@ bool st_select_lex::save_leaf_tables(THD *thd) TABLE_LIST *table; while ((table= li++)) { - if (leaf_tables_exec.push_back(table)) + if (leaf_tables_exec.push_back(table, thd->mem_root)) return 1; table->tablenr_exec= table->get_tablenr(); table->map_exec= table->get_map(); @@ -4307,12 +4531,12 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) */ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything) + bool is_analyze, bool *printed_anything) { int res; if (explain && explain->have_query_plan()) { - res= explain->print_explain(output, explain_flags); + res= explain->print_explain(output, explain_flags, is_analyze); *printed_anything= true; } else @@ -4324,6 +4548,80 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, } +/** + Allocates and set arena for SET STATEMENT old values. + + @param backup where to save backup of arena. + + @retval 1 Error + @retval 0 OK +*/ + +bool LEX::set_arena_for_set_stmt(Query_arena *backup) +{ + DBUG_ENTER("LEX::set_arena_for_set_stmt"); + DBUG_ASSERT(arena_for_set_stmt== 0); + if (!mem_root_for_set_stmt) + { + mem_root_for_set_stmt= new MEM_ROOT(); + if (!(mem_root_for_set_stmt)) + DBUG_RETURN(1); + init_sql_alloc(mem_root_for_set_stmt, ALLOC_ROOT_SET, ALLOC_ROOT_SET, + MYF(MY_THREAD_SPECIFIC)); + } + if (!(arena_for_set_stmt= new(mem_root_for_set_stmt) + Query_arena_memroot(mem_root_for_set_stmt, + Query_arena::STMT_INITIALIZED))) + DBUG_RETURN(1); + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) mem_root_for_set_stmt, + (ulong) arena_for_set_stmt)); + thd->set_n_backup_active_arena(arena_for_set_stmt, backup); + DBUG_RETURN(0); +} + + +void LEX::reset_arena_for_set_stmt(Query_arena *backup) +{ + DBUG_ENTER("LEX::reset_arena_for_set_stmt"); + DBUG_ASSERT(arena_for_set_stmt); + thd->restore_active_arena(arena_for_set_stmt, backup); + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) arena_for_set_stmt->mem_root, + (ulong) arena_for_set_stmt)); + DBUG_VOID_RETURN; +} + + +void LEX::free_arena_for_set_stmt() +{ + DBUG_ENTER("LEX::free_arena_for_set_stmt"); + if (!arena_for_set_stmt) + return; + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) arena_for_set_stmt->mem_root, + (ulong) arena_for_set_stmt)); + arena_for_set_stmt->free_items(); + delete(arena_for_set_stmt); + free_root(mem_root_for_set_stmt, MYF(MY_KEEP_PREALLOC)); + arena_for_set_stmt= 0; + DBUG_VOID_RETURN; +} + +void LEX::restore_set_statement_var() +{ + DBUG_ENTER("LEX::restore_set_statement_var"); + if (!old_var_list.is_empty()) + { + DBUG_PRINT("info", ("vars: %d", old_var_list.elements)); + sql_set_variables(thd, &old_var_list, false); + old_var_list.empty(); + free_arena_for_set_stmt(); + } + DBUG_ASSERT(!is_arena_for_set_stmt()); + DBUG_VOID_RETURN; +} + /* Save explain structures of a UNION. The only variable member is whether the union has "Using filesort". @@ -4343,13 +4641,28 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, int st_select_lex_unit::save_union_explain(Explain_query *output) { SELECT_LEX *first= first_select(); - Explain_union *eu= new (output->mem_root) Explain_union; + + if (output->get_union(first->select_number)) + return 0; /* Already added */ + + Explain_union *eu= + new (output->mem_root) Explain_union(output->mem_root, + thd->lex->analyze_stmt); + + + if (derived) + eu->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + /* + Note: Non-merged semi-joins cannot be made out of UNIONs currently, so we + dont ever set EXPLAIN_NODE_NON_MERGED_SJ. + */ for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) eu->add_select(sl->select_number); eu->fake_select_type= "UNION RESULT"; - eu->using_filesort= MY_TEST(global_parameters->order_list.first); + eu->using_filesort= MY_TEST(global_parameters()->order_list.first); + eu->using_tmp= union_needs_tmp_table(); // Save the UNION node output->add_node(eu); @@ -4378,6 +4691,7 @@ int st_select_lex_unit::save_union_explain_part2(Explain_query *output) eu->add_child(unit->first_select()->select_number); } } + fake_select_lex->join->explain= &eu->fake_select_lex_explain; } return 0; } |