1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-31721: Cursor protocol increases the counter of "Empty_queries" for select

Problem:
  Empty queries are incremented if no rows are sent to the client in the
  EXECUTE phase of select query. With cursor protocol, rows are not sent
  during EXECUTE phase; they are sent later in FETCH phase. Hence,
  queries executed with cursor protocol are always falsely treated as
  empty in EXECUTE phase.

Fix:
  For cursor protocol, empty queries are now counted during the FETCH
  phase. This ensures counter correctly reflects whether any rows were
  actually sent to the client.

Tests included in `mysql-test/main/show.test`.
This commit is contained in:
Raghunandan Bhat
2025-06-24 12:59:16 +05:30
committed by Raghunandan Bhat
parent 7ab205b009
commit 2c7cea28da
8 changed files with 154 additions and 67 deletions

View File

@@ -65,3 +65,37 @@ drop table t1;
# #
# End of 10.3 tests # End of 10.3 tests
# #
#
# MDEV-31721: Cursor protocol increases the counter of "Empty_queries" for select
#
FLUSH STATUS;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
SELECT COUNT(*) FROM t1;
COUNT(*)
1
SELECT * FROM t1;
a
1
SELECT * FROM t1 LIMIT 0;
a
SHOW STATUS LIKE "Empty_queries";
Variable_name Value
Empty_queries 1
DROP TABLE t1;
#------------------------------
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (b INT);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (2);
SELECT * FROM t1 UNION SELECT * FROM t2;
a
1
2
SELECT * FROM t1 UNION SELECT * FROM t2 LIMIT 0;
a
SHOW STATUS LIKE "Empty_queries";
Variable_name Value
Empty_queries 2
DROP TABLE t1, t2;
# End of 10.11 tests

View File

@@ -56,3 +56,40 @@ drop table t1;
--echo # --echo #
--echo # End of 10.3 tests --echo # End of 10.3 tests
--echo # --echo #
--echo #
--echo # MDEV-31721: Cursor protocol increases the counter of "Empty_queries" for select
--echo #
FLUSH STATUS;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
--disable_ps2_protocol
SELECT COUNT(*) FROM t1;
SELECT * FROM t1;
SELECT * FROM t1 LIMIT 0;
--enable_ps2_protocol
SHOW STATUS LIKE "Empty_queries";
DROP TABLE t1;
--echo #------------------------------
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (b INT);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (2);
--disable_ps2_protocol
SELECT * FROM t1 UNION SELECT * FROM t2;
SELECT * FROM t1 UNION SELECT * FROM t2 LIMIT 0;
--enable_ps2_protocol
SHOW STATUS LIKE "Empty_queries";
DROP TABLE t1, t2;
--echo # End of 10.11 tests

View File

@@ -65,6 +65,7 @@
#include "lock.h" #include "lock.h"
#include "wsrep_mysqld.h" #include "wsrep_mysqld.h"
#include "sql_connect.h" #include "sql_connect.h"
#include "sql_cursor.h" //Select_materialize
#ifdef WITH_WSREP #ifdef WITH_WSREP
#include "wsrep_thd.h" #include "wsrep_thd.h"
#include "wsrep_trans_observer.h" #include "wsrep_trans_observer.h"
@@ -8737,3 +8738,9 @@ void Charset_loader_server::raise_not_applicable_error(const char *cs,
{ {
my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), cl, cs); my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), cl, cs);
} }
bool THD::is_cursor_execution() const
{
return dynamic_cast<Select_materialize*>(this->lex->result);
}

View File

@@ -5850,6 +5850,17 @@ public:
return false; return false;
return !is_set_timestamp_forbidden(this); return !is_set_timestamp_forbidden(this);
} }
/**
@brief
Return true if current statement uses cursor protocol for execution.
@details
Cursor protocol execution is determined by checking if lex->result is a
Select_materialize object, which is exclusively used by the server for
cursor result set materialization.
*/
bool is_cursor_execution() const;
}; };

View File

@@ -24,72 +24,6 @@
#include "probes_mysql.h" #include "probes_mysql.h"
#include "sql_parse.h" // mysql_execute_command #include "sql_parse.h" // mysql_execute_command
/****************************************************************************
Declarations.
****************************************************************************/
/**
Materialized_cursor -- an insensitive materialized server-side
cursor. The result set of this cursor is saved in a temporary
table at open. The cursor itself is simply an interface for the
handler of the temporary table.
*/
class Materialized_cursor: public Server_side_cursor
{
MEM_ROOT main_mem_root;
/* A fake unit to supply to select_send when fetching */
SELECT_LEX_UNIT fake_unit;
TABLE *table;
List<Item> item_list;
ulong fetch_limit;
ulong fetch_count;
bool is_rnd_inited;
public:
Materialized_cursor(select_result *result, TABLE *table);
int send_result_set_metadata(THD *thd, List<Item> &send_result_set_metadata);
bool is_open() const override { return table != 0; }
int open(JOIN *join __attribute__((unused))) override;
void fetch(ulong num_rows) override;
void close() override;
bool export_structure(THD *thd, Row_definition_list *defs) override
{
return table->export_structure(thd, defs);
}
~Materialized_cursor() override;
void on_table_fill_finished();
};
/**
Select_materialize -- a mediator between a cursor query and the
protocol. In case we were not able to open a non-materialzed
cursor, it creates an internal temporary HEAP table, and insert
all rows into it. When the table reaches max_heap_table_size,
it's converted to a MyISAM table. Later this table is used to
create a Materialized_cursor.
*/
class Select_materialize: public select_unit
{
select_result *result; /**< the result object of the caller (PS or SP) */
public:
Materialized_cursor *materialized_cursor;
Select_materialize(THD *thd_arg, select_result *result_arg):
select_unit(thd_arg), result(result_arg), materialized_cursor(0) {}
bool send_result_set_metadata(List<Item> &list, uint flags) override;
bool send_eof() override { return false; }
bool view_structure_only() const override
{
return result->view_structure_only();
}
};
/**************************************************************************/
/** /**
Attempt to open a materialized cursor. Attempt to open a materialized cursor.

View File

@@ -68,6 +68,66 @@ public:
}; };
/**
Materialized_cursor -- an insensitive materialized server-side
cursor. The result set of this cursor is saved in a temporary
table at open. The cursor itself is simply an interface for the
handler of the temporary table.
*/
class Materialized_cursor: public Server_side_cursor
{
MEM_ROOT main_mem_root;
/* A fake unit to supply to select_send when fetching */
SELECT_LEX_UNIT fake_unit;
TABLE *table;
List<Item> item_list;
ulong fetch_limit;
ulong fetch_count;
bool is_rnd_inited;
public:
Materialized_cursor(select_result *result, TABLE *table);
int send_result_set_metadata(THD *thd, List<Item> &send_result_set_metadata);
bool is_open() const override { return table != 0; }
int open(JOIN *join __attribute__((unused))) override;
void fetch(ulong num_rows) override;
void close() override;
bool export_structure(THD *thd, Row_definition_list *defs) override
{
return table->export_structure(thd, defs);
}
~Materialized_cursor() override;
void on_table_fill_finished();
};
/**
Select_materialize -- a mediator between a cursor query and the
protocol. In case we were not able to open a non-materialzed
cursor, it creates an internal temporary HEAP table, and insert
all rows into it. When the table reaches max_heap_table_size,
it's converted to a MyISAM table. Later this table is used to
create a Materialized_cursor.
*/
class Select_materialize: public select_unit
{
select_result *result; /**< the result object of the caller (PS or SP) */
public:
Materialized_cursor *materialized_cursor;
Select_materialize(THD *thd_arg, select_result *result_arg):
select_unit(thd_arg), result(result_arg), materialized_cursor(0) {}
bool send_result_set_metadata(List<Item> &list, uint flags) override;
bool send_eof() override { return false; }
bool view_structure_only() const override
{
return result->view_structure_only();
}
};
int mysql_open_cursor(THD *thd, select_result *result, int mysql_open_cursor(THD *thd, select_result *result,
Server_side_cursor **res); Server_side_cursor **res);

View File

@@ -56,6 +56,7 @@
#include "sql_test.h" // mysql_print_status #include "sql_test.h" // mysql_print_status
#include "sql_select.h" // handle_select, mysql_select, #include "sql_select.h" // handle_select, mysql_select,
// mysql_explain_union // mysql_explain_union
#include "sql_cursor.h" // Select_materialzie
#include "sql_load.h" // mysql_load #include "sql_load.h" // mysql_load
#include "sql_servers.h" // create_servers, alter_servers, #include "sql_servers.h" // create_servers, alter_servers,
// drop_servers, servers_reload // drop_servers, servers_reload
@@ -6443,7 +6444,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
} }
} }
/* Count number of empty select queries */ /* Count number of empty select queries */
if (!thd->get_sent_row_count() && !res) if (!thd->is_cursor_execution() && !thd->get_sent_row_count() && !res)
status_var_increment(thd->status_var.empty_queries); status_var_increment(thd->status_var.empty_queries);
else else
status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count());

View File

@@ -3767,6 +3767,9 @@ void mysqld_stmt_fetch(THD *thd, char *packet, uint packet_length)
cursor->fetch(num_rows); cursor->fetch(num_rows);
if (!thd->get_sent_row_count())
status_var_increment(thd->status_var.empty_queries);
if (!cursor->is_open()) if (!cursor->is_open())
{ {
stmt->close_cursor(); stmt->close_cursor();