Skip to content

Commit

Permalink
MDEV-16708: Unsupported commands for prepared statements
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dmitryshulga authored and vuvova committed Jun 17, 2021
1 parent f778a5d commit 9370c6e
Show file tree
Hide file tree
Showing 39 changed files with 2,955 additions and 373 deletions.
199 changes: 96 additions & 103 deletions client/mysqltest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ static const char *opt_suite_dir, *opt_overlay_dir;
static size_t suite_dir_len, overlay_dir_len;

/* Precompiled re's */
static regex_t ps_re; /* the query can be run using PS protocol */
static regex_t sp_re; /* the query can be run as a SP */
static regex_t view_re; /* the query can be run as a view*/

Expand Down Expand Up @@ -8324,116 +8323,119 @@ void run_query_stmt(struct st_connection *cn, struct st_command *command,
goto end;
}

/*
When running in cursor_protocol get the warnings from execute here
and keep them in a separate string for later.
*/
if (cursor_protocol_enabled && !disable_warnings)
append_warnings(&ds_execute_warnings, mysql);

/*
We instruct that we want to update the "max_length" field in
mysql_stmt_store_result(), this is our only way to know how much
buffer to allocate for result data
*/
do
{
my_bool one= 1;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one))
die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
/*
When running in cursor_protocol get the warnings from execute here
and keep them in a separate string for later.
*/
if (cursor_protocol_enabled && !disable_warnings)
append_warnings(&ds_execute_warnings, mysql);

/*
If we got here the statement succeeded and was expected to do so,
get data. Note that this can still give errors found during execution!
Store the result of the query if if will return any fields
*/
if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
goto end;
}
/*
We instruct that we want to update the "max_length" field in
mysql_stmt_store_result(), this is our only way to know how much
buffer to allocate for result data
*/
{
my_bool one= 1;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one))
die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}

/* If we got here the statement was both executed and read successfully */
handle_no_error(command);
if (!disable_result_log)
{
/*
Not all statements creates a result set. If there is one we can
now create another normal result set that contains the meta
data. This set can be handled almost like any other non prepared
statement result set.
If we got here the statement succeeded and was expected to do so,
get data. Note that this can still give errors found during execution!
Store the result of the query if if will return any fields
*/
if ((res= mysql_stmt_result_metadata(stmt)) != NULL)
if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt))
{
/* Take the column count from meta info */
MYSQL_FIELD *fields= mysql_fetch_fields(res);
uint num_fields= mysql_num_fields(res);
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
goto end;
}

if (display_metadata)
append_metadata(ds, fields, num_fields);
/* If we got here the statement was both executed and read successfully */
handle_no_error(command);
if (!disable_result_log)
{
/*
Not all statements creates a result set. If there is one we can
now create another normal result set that contains the meta
data. This set can be handled almost like any other non prepared
statement result set.
*/
if ((res= mysql_stmt_result_metadata(stmt)) != NULL)
{
/* Take the column count from meta info */
MYSQL_FIELD *fields= mysql_fetch_fields(res);
uint num_fields= mysql_num_fields(res);

if (!display_result_vertically)
append_table_headings(ds, fields, num_fields);
if (display_metadata)
append_metadata(ds, fields, num_fields);

append_stmt_result(ds, stmt, fields, num_fields);
if (!display_result_vertically)
append_table_headings(ds, fields, num_fields);

mysql_free_result(res); /* Free normal result set with meta data */
append_stmt_result(ds, stmt, fields, num_fields);

/*
Normally, if there is a result set, we do not show warnings from the
prepare phase. This is because some warnings are generated both during
prepare and execute; this would generate different warning output
between normal and ps-protocol test runs.
mysql_free_result(res); /* Free normal result set with meta data */

The --enable_prepare_warnings command can be used to change this so
that warnings from both the prepare and execute phase are shown.
*/
if (!disable_warnings && !prepare_warnings_enabled)
dynstr_set(&ds_prepare_warnings, NULL);
}
else
{
/*
This is a query without resultset
*/
}
/*
Normally, if there is a result set, we do not show warnings from the
prepare phase. This is because some warnings are generated both during
prepare and execute; this would generate different warning output
between normal and ps-protocol test runs.
/*
Fetch info before fetching warnings, since it will be reset
otherwise.
*/
if (!disable_info)
append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql));
The --enable_prepare_warnings command can be used to change this so
that warnings from both the prepare and execute phase are shown.
*/
if (!disable_warnings && !prepare_warnings_enabled)
dynstr_set(&ds_prepare_warnings, NULL);
}
else
{
/*
This is a query without resultset
*/
}

if (display_session_track_info)
append_session_track_info(ds, mysql);
/*
Fetch info before fetching warnings, since it will be reset
otherwise.
*/
if (!disable_info)
append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql));

if (display_session_track_info)
append_session_track_info(ds, mysql);

if (!disable_warnings)
{
/* Get the warnings from execute */

/* Append warnings to ds - if there are any */
if (append_warnings(&ds_execute_warnings, mysql) ||
ds_execute_warnings.length ||
ds_prepare_warnings.length ||
ds_warnings->length)
if (!disable_warnings)
{
dynstr_append_mem(ds, "Warnings:\n", 10);
if (ds_warnings->length)
dynstr_append_mem(ds, ds_warnings->str,
ds_warnings->length);
if (ds_prepare_warnings.length)
dynstr_append_mem(ds, ds_prepare_warnings.str,
ds_prepare_warnings.length);
if (ds_execute_warnings.length)
dynstr_append_mem(ds, ds_execute_warnings.str,
ds_execute_warnings.length);
/* Get the warnings from execute */

/* Append warnings to ds - if there are any */
if (append_warnings(&ds_execute_warnings, mysql) ||
ds_execute_warnings.length ||
ds_prepare_warnings.length ||
ds_warnings->length)
{
dynstr_append_mem(ds, "Warnings:\n", 10);
if (ds_warnings->length)
dynstr_append_mem(ds, ds_warnings->str,
ds_warnings->length);
if (ds_prepare_warnings.length)
dynstr_append_mem(ds, ds_prepare_warnings.str,
ds_prepare_warnings.length);
if (ds_execute_warnings.length)
dynstr_append_mem(ds, ds_execute_warnings.str,
ds_execute_warnings.length);
}
}
}
}
} while ( !mysql_stmt_next_result(stmt));

end:
if (!disable_warnings)
Expand Down Expand Up @@ -8719,8 +8721,7 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags)
statement already and we can't do it twice
*/
if (ps_protocol_enabled &&
complete_query &&
match_re(&ps_re, query))
complete_query)
run_query_stmt(cn, command, query, query_len, ds, &ds_warnings);
else
run_query_normal(cn, command, flags, query, query_len,
Expand Down Expand Up @@ -8795,9 +8796,9 @@ void init_re(void)
{
/*
Filter for queries that can be run using the
MySQL Prepared Statements C API
Stored procedures
*/
const char *ps_re_str =
const char *sp_re_str =
"^("
"[[:space:]]*ALTER[[:space:]]+SEQUENCE[[:space:]]|"
"[[:space:]]*ALTER[[:space:]]+TABLE[[:space:]]|"
Expand Down Expand Up @@ -8850,20 +8851,13 @@ void init_re(void)
"[[:space:]]*UPDATE[[:space:]]"
")";

/*
Filter for queries that can be run using the
Stored procedures
*/
const char *sp_re_str =ps_re_str;

/*
Filter for queries that can be run as views
*/
const char *view_re_str =
"^("
"[[:space:]]*SELECT[[:space:]])";

init_re_comp(&ps_re, ps_re_str);
init_re_comp(&sp_re, sp_re_str);
init_re_comp(&view_re, view_re_str);
}
Expand Down Expand Up @@ -8899,7 +8893,6 @@ int match_re(regex_t *re, char *str)

void free_re(void)
{
regfree(&ps_re);
regfree(&sp_re);
regfree(&view_re);
}
Expand Down
4 changes: 3 additions & 1 deletion mysql-test/main/func_time.test
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,10 @@ SET GLOBAL log_bin_trust_function_creators = 1;

create table t1 (a timestamp default '2005-05-05 01:01:01',
b timestamp default '2005-05-05 01:01:01');
delimiter //;
--disable_warnings
drop function if exists t_slow_sysdate;
--enable_warnings
delimiter //;
create function t_slow_sysdate() returns timestamp
begin
do sleep(2);
Expand Down
4 changes: 1 addition & 3 deletions mysql-test/main/get_diagnostics.result
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,10 @@ DROP PROCEDURE p1;
# Test GET DIAGNOSTICS runtime
#

# GET DIAGNOSTICS cannot be the object of a PREPARE statement.
# GET DIAGNOSTICS can be the object of a PREPARE statement.

PREPARE stmt FROM "GET DIAGNOSTICS CONDITION 1 @var = CLASS_ORIGIN";
ERROR HY000: This command is not supported in the prepared statement protocol yet
PREPARE stmt FROM "GET DIAGNOSTICS @var = NUMBER";
ERROR HY000: This command is not supported in the prepared statement protocol yet

# GET DIAGNOSTICS does not clear the diagnostics area.

Expand Down
4 changes: 1 addition & 3 deletions mysql-test/main/get_diagnostics.test
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,10 @@ DROP PROCEDURE p1;
--echo #

--echo
--echo # GET DIAGNOSTICS cannot be the object of a PREPARE statement.
--echo # GET DIAGNOSTICS can be the object of a PREPARE statement.
--echo

--error ER_UNSUPPORTED_PS
PREPARE stmt FROM "GET DIAGNOSTICS CONDITION 1 @var = CLASS_ORIGIN";
--error ER_UNSUPPORTED_PS
PREPARE stmt FROM "GET DIAGNOSTICS @var = NUMBER";

--echo
Expand Down
24 changes: 8 additions & 16 deletions mysql-test/main/ps.result
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ ERROR HY000: Unknown prepared statement handler (no_such_statement) given to DEA
execute stmt1;
ERROR HY000: Incorrect arguments to EXECUTE
prepare stmt2 from 'prepare nested_stmt from "select 1"';
ERROR HY000: This command is not supported in the prepared statement protocol yet
prepare stmt2 from 'execute stmt1';
ERROR HY000: This command is not supported in the prepared statement protocol yet
prepare stmt2 from 'deallocate prepare z';
ERROR HY000: This command is not supported in the prepared statement protocol yet
deallocate prepare stmt2;
prepare stmt3 from 'insert into t1 values (?,?)';
set @arg1=5, @arg2='five';
execute stmt3 using @arg1, @arg2;
Expand Down Expand Up @@ -2726,9 +2724,7 @@ ERROR 42000: FUNCTION test.func_1 does not exist
drop function func_1;
ERROR 42000: FUNCTION test.func_1 does not exist
prepare abc from "create event xyz on schedule at now() do select 123";
ERROR HY000: This command is not supported in the prepared statement protocol yet
deallocate prepare abc;
ERROR HY000: Unknown prepared statement handler (abc) given to DEALLOCATE PREPARE
drop event if exists xyz;
create event xyz on schedule every 5 minute disable do select 123;
create procedure proc_1() alter event xyz comment 'xyz';
Expand All @@ -2748,9 +2744,7 @@ drop procedure proc_1;
create function func_1() returns int begin alter event xyz comment 'xyz'; return 1; end|
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
prepare abc from "alter event xyz comment 'xyz'";
ERROR HY000: This command is not supported in the prepared statement protocol yet
deallocate prepare abc;
ERROR HY000: Unknown prepared statement handler (abc) given to DEALLOCATE PREPARE
drop event if exists xyz;
create event xyz on schedule every 5 minute disable do select 123;
create procedure proc_1() drop event xyz;
Expand All @@ -2765,9 +2759,7 @@ drop procedure proc_1;
create function func_1() returns int begin drop event xyz; return 1; end|
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
prepare abc from "drop event xyz";
ERROR HY000: This command is not supported in the prepared statement protocol yet
deallocate prepare abc;
ERROR HY000: Unknown prepared statement handler (abc) given to DEALLOCATE PREPARE
drop table if exists t1;
create table t1 (a int, b char(5)) engine=myisam;
insert into t1 values (1, "one"), (2, "two"), (3, "three");
Expand Down Expand Up @@ -3087,15 +3079,15 @@ DROP TABLE t1;
CREATE TABLE t1(f1 INT);
INSERT INTO t1 VALUES (1),(1);
PREPARE stmt FROM 'EXPLAIN SELECT 1 FROM t1 WHERE (SELECT (SELECT 1 FROM t1 GROUP BY f1))';
Warnings:
Note 1249 Select 2 was reduced during optimization
EXECUTE stmt;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used
3 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
EXECUTE stmt;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used
3 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
DEALLOCATE PREPARE stmt;
DROP TABLE t1;
Expand Down Expand Up @@ -4727,13 +4719,13 @@ ERROR HY000: Incorrect arguments to EXECUTE
EXECUTE IMMEDIATE 'SELECT ?';
ERROR HY000: Incorrect arguments to EXECUTE
EXECUTE IMMEDIATE 'EXECUTE IMMEDIATE "SELECT 1"';
ERROR HY000: This command is not supported in the prepared statement protocol yet
1
1
EXECUTE IMMEDIATE 'PREPARE stmt FROM "SELECT 1"';
ERROR HY000: This command is not supported in the prepared statement protocol yet
EXECUTE IMMEDIATE 'EXECUTE stmt';
ERROR HY000: This command is not supported in the prepared statement protocol yet
1
1
EXECUTE IMMEDIATE 'DEALLOCATE PREPARE stmt';
ERROR HY000: This command is not supported in the prepared statement protocol yet
EXECUTE IMMEDIATE 'SELECT ?' USING _latin1'a'=_latin2'a';
ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin2_general_ci,COERCIBLE) for operation '='
EXECUTE IMMEDIATE 'SELECT ?' USING ROW(1,2);
Expand Down Expand Up @@ -4944,7 +4936,7 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp
PREPARE stmt FROM CONCAT(NULL);
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NULL' at line 1
EXECUTE IMMEDIATE ? USING 'SELECT 1';
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '? USING 'SELECT 1'' at line 1
Got one of the listed errors
EXECUTE IMMEDIATE 10;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '10' at line 1
EXECUTE IMMEDIATE TIME'10:20:30';
Expand Down
Loading

0 comments on commit 9370c6e

Please sign in to comment.