1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

MDEV-16708: Unsupported commands for prepared statements

Withing this task the following changes were made:
- Added sending of metadata info in prepare phase for the admin related
  command (check table, checksum table, repair, optimize, analyze).

- Refactored implmentation of HELP command to support its execution in
  PS mode

- Added support for execution of LOAD INTO and XA- related statements
  in PS mode

- Modified mysqltest.cc to run statements in PS mode unconditionally
  in case the option --ps-protocol is set. Formerly, only those statements
  were executed using PS protocol that matched the hard-coded regular expression

- Fixed the following issues:
    The statement
      explain select (select 2)
    executed in regular and PS mode produces different results:

    MariaDB [test]> prepare stmt from "explain select (select 2)";
    Query OK, 0 rows affected (0,000 sec)
    Statement prepared
    MariaDB [test]> execute stmt;
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    | id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    |    1 | PRIMARY     | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |
    |    2 | SUBQUERY    | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    2 rows in set (0,000 sec)
    MariaDB [test]> explain select (select 2);
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    | id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    |    1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |
    +------+-------------+-------+------+---------------+------+---------+------+------+----------------+
    1 row in set, 1 warning (0,000 sec)

    In case the statement
      CREATE TABLE t1 SELECT * FROM (SELECT 1 AS a, (SELECT a+0)) a
    is run in PS mode it fails with the error
      ERROR 1054 (42S22): Unknown column 'a' in 'field list'.

- Uniform handling of read-only variables both in case the SET var=val
  statement is executed as regular or prepared statememt.

- Fixed assertion firing on handling LOAD DATA statement for temporary tables

- Relaxed assert condition in the function lex_end_stage1() by adding
  the commands SQLCOM_ALTER_EVENT, SQLCOM_CREATE_PACKAGE,
  SQLCOM_CREATE_PACKAGE_BODY to a list of supported command

- Removed raising of the error ER_UNSUPPORTED_PS in the function
  check_prepared_statement() for the ALTER VIEW command

- Added initialization of the data memember st_select_lex_unit::last_procedure
  (assign NULL value) in the constructor

  Without this change the test case main.ctype_utf8 fails with the following
  report in case it is run with the optoin --ps-protocol.
    mysqltest: At line 2278: query 'VALUES (_latin1 0xDF) UNION VALUES(_utf8'a' COLLATE utf8_bin)' failed: 2013: Lost connection

- The following bug reports were fixed:
      MDEV-24460: Multiple rows result set returned from stored
                  routine over prepared statement binary protocol is
                  handled incorrectly
      CONC-519: mariadb client library doesn't handle server_status and
                warnign_count fields received in the packet
                COM_STMT_EXECUTE_RESPONSE.

  Reasons for these bug reports have the same nature and caused by
  missing loop iteration on results sent by server in response to
  COM_STMT_EXECUTE packet.

  Enclosing of statements for processing of COM_STMT_EXECUTE response
  in the construct like
    do
    {
      ...
    } while (!mysql_stmt_next_result());
  fixes the above mentioned bug reports.
This commit is contained in:
Dmitry Shulga
2021-04-22 14:52:19 +07:00
committed by Sergei Golubchik
parent f778a5d5e2
commit 9370c6e83c
39 changed files with 2965 additions and 383 deletions

View File

@ -430,6 +430,46 @@ void get_all_items_for_category(THD *thd, TABLE *items, Field *pfname,
DBUG_VOID_RETURN;
}
/**
Collect field names of HELP header that will be sent to a client
@param thd Thread data object
@param[out] field_list List of fields whose metadata should be collected for
sending to client
*/
static void fill_answer_1_fields(THD *thd, List<Item> *field_list)
{
MEM_ROOT *mem_root= thd->mem_root;
field_list->push_back(new (mem_root) Item_empty_string(thd, "name", 64),
mem_root);
field_list->push_back(new (mem_root) Item_empty_string(thd, "description",
1000),
mem_root);
field_list->push_back(new (mem_root) Item_empty_string(thd, "example", 1000),
mem_root);
}
/**
Send metadata of an answer on help request to a client
@param protocol protocol for sending
*/
static bool send_answer_1_metadata(Protocol *protocol)
{
List<Item> field_list;
fill_answer_1_fields(protocol->thd, &field_list);
return protocol->send_result_set_metadata(&field_list,
Protocol::SEND_NUM_ROWS |
Protocol::SEND_EOF);
}
/*
Send to client answer for help request
@ -455,22 +495,11 @@ void get_all_items_for_category(THD *thd, TABLE *items, Field *pfname,
0 Successeful send
*/
int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3)
static int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3)
{
THD *thd= protocol->thd;
MEM_ROOT *mem_root= thd->mem_root;
DBUG_ENTER("send_answer_1");
List<Item> field_list;
field_list.push_back(new (mem_root) Item_empty_string(thd, "name", 64),
mem_root);
field_list.push_back(new (mem_root) Item_empty_string(thd, "description", 1000),
mem_root);
field_list.push_back(new (mem_root) Item_empty_string(thd, "example", 1000),
mem_root);
if (protocol->send_result_set_metadata(&field_list,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
if (send_answer_1_metadata(protocol))
DBUG_RETURN(1);
protocol->prepare_for_resend();
@ -483,13 +512,39 @@ int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3)
}
/**
Collect field names of HELP header that will be sent to a client
@param thd Thread data object
@param[out] field_list List of fields whose metadata should be collected for
sending to client
@param for_category need column 'source_category_name'
*/
static void fill_header_2_fields(THD *thd, List<Item> *field_list,
bool for_category)
{
MEM_ROOT *mem_root= thd->mem_root;
if (for_category)
field_list->push_back(new (mem_root)
Item_empty_string(thd, "source_category_name", 64),
mem_root);
field_list->push_back(new (mem_root)
Item_empty_string(thd, "name", 64),
mem_root);
field_list->push_back(new (mem_root)
Item_empty_string(thd, "is_it_category", 1),
mem_root);
}
/*
Send to client help header
SYNOPSIS
send_header_2()
protocol - protocol for sending
is_it_category - need column 'source_category_name'
for_category - need column 'source_category_name'
IMPLEMENTATION
+- -+
@ -504,22 +559,12 @@ int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3)
result of protocol->send_result_set_metadata
*/
int send_header_2(Protocol *protocol, bool for_category)
static int send_header_2(Protocol *protocol, bool for_category)
{
THD *thd= protocol->thd;
MEM_ROOT *mem_root= thd->mem_root;
DBUG_ENTER("send_header_2");
List<Item> field_list;
if (for_category)
field_list.push_back(new (mem_root)
Item_empty_string(thd, "source_category_name", 64),
mem_root);
field_list.push_back(new (mem_root)
Item_empty_string(thd, "name", 64),
mem_root);
field_list.push_back(new (mem_root)
Item_empty_string(thd, "is_it_category", 1),
mem_root);
fill_header_2_fields(protocol->thd, &field_list, for_category);
DBUG_RETURN(protocol->send_result_set_metadata(&field_list,
Protocol::SEND_NUM_ROWS |
Protocol::SEND_EOF));
@ -639,7 +684,6 @@ SQL_SELECT *prepare_simple_select(THD *thd, Item *cond,
thd Thread handler
mask mask for compare with name
mlen length of mask
tables list of tables, used in WHERE
table goal table
pfname field "name" in table
@ -650,8 +694,7 @@ SQL_SELECT *prepare_simple_select(THD *thd, Item *cond,
*/
SQL_SELECT *prepare_select_for_name(THD *thd, const char *mask, size_t mlen,
TABLE_LIST *tables, TABLE *table,
Field *pfname, int *error)
TABLE *table, Field *pfname, int *error)
{
MEM_ROOT *mem_root= thd->mem_root;
Item *cond= new (mem_root)
@ -668,6 +711,205 @@ SQL_SELECT *prepare_select_for_name(THD *thd, const char *mask, size_t mlen,
}
/**
Initialize the TABLE_LIST with tables used in HELP statement handling.
@param thd Thread handler
@param tables Array of four TABLE_LIST objects to initialize with data
about the tables help_topic, help_category, help_relation,
help_keyword
*/
static void initialize_tables_for_help_command(THD *thd, TABLE_LIST *tables)
{
LEX_CSTRING MYSQL_HELP_TOPIC_NAME= {STRING_WITH_LEN("help_topic") };
LEX_CSTRING MYSQL_HELP_CATEGORY_NAME= {STRING_WITH_LEN("help_category") };
LEX_CSTRING MYSQL_HELP_RELATION_NAME= {STRING_WITH_LEN("help_relation") };
LEX_CSTRING MYSQL_HELP_KEYWORD_NAME= {STRING_WITH_LEN("help_keyword") };
tables[0].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_TOPIC_NAME, 0,
TL_READ);
tables[1].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_CATEGORY_NAME, 0,
TL_READ);
tables[2].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_RELATION_NAME, 0,
TL_READ);
tables[3].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_KEYWORD_NAME, 0,
TL_READ);
tables[0].next_global= tables[0].next_local=
tables[0].next_name_resolution_table= &tables[1];
tables[1].next_global= tables[1].next_local=
tables[1].next_name_resolution_table= &tables[2];
tables[2].next_global= tables[2].next_local=
tables[2].next_name_resolution_table= &tables[3];
}
/**
Setup tables and fields for query.
@param thd Thread handler
@param first_select_lex SELECT_LEX of the parsed statement
@param tables Array of tables used in handling of the HELP
statement
@param used_fields Array of fields used in handling of the HELP
statement
@return false on success, else true.
*/
template <size_t M, size_t N>
static bool init_items_for_help_command(THD *thd,
SELECT_LEX *first_select_lex,
TABLE_LIST (&tables)[M],
st_find_field (& used_fields)[N])
{
List<TABLE_LIST> leaves;
/*
Initialize tables and fields to be usable from items.
tables do not contain VIEWs => we can pass 0 as conds
*/
first_select_lex->context.table_list=
first_select_lex->context.first_name_resolution_table=
&tables[0];
if (setup_tables(thd, &first_select_lex->context,
&first_select_lex->top_join_list,
&tables[0], leaves, false, false))
return true;
memcpy((char*) used_fields, (char*) init_used_fields,
sizeof(used_fields[0]) * N);
if (init_fields(thd, &tables[0], used_fields, N))
return true;
for (size_t i= 0; i < M; i++)
tables[i].table->file->init_table_handle_for_HANDLER();
return false;
}
/**
Prepare (in the sense of prepared statement) the HELP statement.
@param thd Thread handler
@param mask string value passed to the HELP statement
@oaram[out] fields fields for result set metadata
@return false on success, else true.
*/
bool mysqld_help_prepare(THD *thd, const char *mask, List<Item> *fields)
{
TABLE_LIST tables[4];
st_find_field used_fields[array_elements(init_used_fields)];
SQL_SELECT *select;
List<String> topics_list;
Sql_mode_instant_remove sms(thd, MODE_PAD_CHAR_TO_FULL_LENGTH);
initialize_tables_for_help_command(thd, tables);
/*
HELP must be available under LOCK TABLES.
Reset and backup the current open tables state to
make it possible.
*/
start_new_trans new_trans(thd);
if (open_system_tables_for_read(thd, tables))
return true;
auto cleanup_and_return= [&](bool ret)
{
thd->commit_whole_transaction_and_close_tables();
new_trans.restore_old_transaction();
return ret;
};
if (init_items_for_help_command(thd, thd->lex->first_select_lex(),
tables, used_fields))
return cleanup_and_return(false);
size_t mlen= strlen(mask);
int error;
/*
Prepare the query 'SELECT * FROM help_topic WHERE name LIKE mask'
for execution
*/
if (!(select=
prepare_select_for_name(thd,mask, mlen, tables[0].table,
used_fields[help_topic_name].field, &error)))
return cleanup_and_return(true);
String name, description, example;
/*
Run the query 'SELECT * FROM help_topic WHERE name LIKE mask'
*/
int count_topics= search_topics(thd, tables[0].table, used_fields,
select, &topics_list,
&name, &description, &example);
delete select;
if (thd->is_error())
return cleanup_and_return(true);
if (count_topics == 0)
{
int UNINIT_VAR(key_id);
/*
Prepare the query 'SELECT * FROM help_keyword WHERE name LIKE mask'
for execution
*/
if (!(select=
prepare_select_for_name(thd, mask, mlen, tables[3].table,
used_fields[help_keyword_name].field,
&error)))
return cleanup_and_return(true);
/*
Run the query 'SELECT * FROM help_keyword WHERE name LIKE mask'
*/
count_topics= search_keyword(thd,tables[3].table, used_fields, select,
&key_id);
delete select;
count_topics= (count_topics != 1) ? 0 :
get_topics_for_keyword(thd, tables[0].table, tables[2].table,
used_fields, key_id, &topics_list, &name,
&description, &example);
}
if (count_topics == 0)
{
if (!(select=
prepare_select_for_name(thd, mask, mlen, tables[1].table,
used_fields[help_category_name].field,
&error)))
return cleanup_and_return(true);
List<String> categories_list;
int16 category_id;
int count_categories= search_categories(thd, tables[1].table, used_fields,
select,
&categories_list,&category_id);
delete select;
if (count_categories == 1)
fill_header_2_fields(thd, fields, true);
else
fill_header_2_fields(thd, fields, false);
}
else if (count_topics == 1)
fill_answer_1_fields(thd, fields);
else
fill_header_2_fields(thd, fields, false);
return cleanup_and_return(false);
}
/*
Server-side function 'help'
@ -685,30 +927,15 @@ static bool mysqld_help_internal(THD *thd, const char *mask)
Protocol *protocol= thd->protocol;
SQL_SELECT *select;
st_find_field used_fields[array_elements(init_used_fields)];
List<TABLE_LIST> leaves;
TABLE_LIST tables[4];
List<String> topics_list, categories_list, subcategories_list;
String name, description, example;
int count_topics, count_categories, error;
size_t mlen= strlen(mask);
size_t i;
MEM_ROOT *mem_root= thd->mem_root;
LEX_CSTRING MYSQL_HELP_TOPIC_NAME= {STRING_WITH_LEN("help_topic") };
LEX_CSTRING MYSQL_HELP_CATEGORY_NAME= {STRING_WITH_LEN("help_category") };
LEX_CSTRING MYSQL_HELP_RELATION_NAME= {STRING_WITH_LEN("help_relation") };
LEX_CSTRING MYSQL_HELP_KEYWORD_NAME= {STRING_WITH_LEN("help_keyword") };
DBUG_ENTER("mysqld_help");
tables[0].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_TOPIC_NAME, 0, TL_READ);
tables[1].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_CATEGORY_NAME, 0, TL_READ);
tables[2].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_RELATION_NAME, 0, TL_READ);
tables[3].init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HELP_KEYWORD_NAME, 0, TL_READ);
tables[0].next_global= tables[0].next_local=
tables[0].next_name_resolution_table= &tables[1];
tables[1].next_global= tables[1].next_local=
tables[1].next_name_resolution_table= &tables[2];
tables[2].next_global= tables[2].next_local=
tables[2].next_name_resolution_table= &tables[3];
initialize_tables_for_help_command(thd, tables);
/*
HELP must be available under LOCK TABLES.
@ -720,25 +947,12 @@ static bool mysqld_help_internal(THD *thd, const char *mask)
if (open_system_tables_for_read(thd, tables))
goto error2;
/*
Init tables and fields to be usable from items
tables do not contain VIEWs => we can pass 0 as conds
*/
thd->lex->first_select_lex()->context.table_list=
thd->lex->first_select_lex()->context.first_name_resolution_table=
&tables[0];
if (setup_tables(thd, &thd->lex->first_select_lex()->context,
&thd->lex->first_select_lex()->top_join_list,
tables, leaves, FALSE, FALSE))
if (init_items_for_help_command(thd, thd->lex->first_select_lex(),
tables, used_fields))
goto error;
memcpy((char*) used_fields, (char*) init_used_fields, sizeof(used_fields));
if (init_fields(thd, tables, used_fields, array_elements(used_fields)))
goto error;
for (i=0; i<sizeof(tables)/sizeof(TABLE_LIST); i++)
tables[i].table->file->init_table_handle_for_HANDLER();
if (!(select=
prepare_select_for_name(thd,mask,mlen,tables,tables[0].table,
prepare_select_for_name(thd,mask,mlen,tables[0].table,
used_fields[help_topic_name].field,&error)))
goto error;
@ -754,7 +968,7 @@ static bool mysqld_help_internal(THD *thd, const char *mask)
{
int UNINIT_VAR(key_id);
if (!(select=
prepare_select_for_name(thd,mask,mlen,tables,tables[3].table,
prepare_select_for_name(thd,mask,mlen,tables[3].table,
used_fields[help_keyword_name].field,
&error)))
goto error;
@ -773,7 +987,7 @@ static bool mysqld_help_internal(THD *thd, const char *mask)
int16 category_id;
Field *cat_cat_id= used_fields[help_category_parent_category_id].field;
if (!(select=
prepare_select_for_name(thd,mask,mlen,tables,tables[1].table,
prepare_select_for_name(thd,mask,mlen,tables[1].table,
used_fields[help_category_name].field,
&error)))
goto error;
@ -841,7 +1055,7 @@ static bool mysqld_help_internal(THD *thd, const char *mask)
send_variant_2_list(mem_root,protocol, &topics_list, "N", 0))
goto error;
if (!(select=
prepare_select_for_name(thd,mask,mlen,tables,tables[1].table,
prepare_select_for_name(thd,mask,mlen,tables[1].table,
used_fields[help_category_name].field,&error)))
goto error;
search_categories(thd, tables[1].table, used_fields,