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:
committed by
Sergei Golubchik
parent
f778a5d5e2
commit
9370c6e83c
344
sql/sql_help.cc
344
sql/sql_help.cc
@ -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,
|
||||
|
Reference in New Issue
Block a user