Merge pull request #5232 from sysown/fix/issue-4855

Fix issue 4855: Incorrect affected rows reporting for DDL queries
pull/4889/head^2
René Cannaò 5 months ago committed by GitHub
commit b641c0d627
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -44,6 +44,7 @@ extern void (*proxy_sqlite3_free)(void*);
extern int (*proxy_sqlite3_status)(int op, int *pCurrent, int *pHighwater, int resetFlag);
extern int (*proxy_sqlite3_status64)(int op, long long *pCurrent, long long *pHighwater, int resetFlag);
extern int (*proxy_sqlite3_changes)(sqlite3*);
extern long long (*proxy_sqlite3_total_changes64)(sqlite3*);
extern int (*proxy_sqlite3_step)(sqlite3_stmt*);
extern int (*proxy_sqlite3_config)(int, ...);
extern int (*proxy_sqlite3_shutdown)(void);
@ -93,6 +94,7 @@ int (*proxy_sqlite3_status)(int op, int *pCurrent, int *pHighwater, int resetFla
int (*proxy_sqlite3_status64)(int op, long long *pCurrent, long long *pHighwater, int resetFlag);
int (*proxy_sqlite3_changes)(sqlite3*);
long long (*proxy_sqlite3_total_changes64)(sqlite3*);
int (*proxy_sqlite3_step)(sqlite3_stmt*);
int (*proxy_sqlite3_config)(int, ...);
int (*proxy_sqlite3_shutdown)(void);

@ -17,7 +17,6 @@
#define USLEEP_SQLITE_LOCKED 100
/**
* @brief Constructor for the SQLite3_column class.
*
@ -342,6 +341,8 @@ bool SQLite3DB::execute_statement(const char *str, char **error, int *cols, int
*cols = (*proxy_sqlite3_column_count)(statement);
if (*cols==0) { // not a SELECT
*resultset=NULL;
// Get total changes before executing the statement
long long total_changes_before = (*proxy_sqlite3_total_changes64)(db);
do {
rc=(*proxy_sqlite3_step)(statement);
if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) { // the execution of the prepared statement failed because locked
@ -353,7 +354,9 @@ bool SQLite3DB::execute_statement(const char *str, char **error, int *cols, int
}
} while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);
if (rc==SQLITE_DONE) {
*affected_rows=(*proxy_sqlite3_changes)(db);
// Calculate affected rows as the difference in total changes
long long total_changes_after = (*proxy_sqlite3_total_changes64)(db);
*affected_rows = (int)(total_changes_after - total_changes_before);
ret=true;
} else {
*error=strdup((*proxy_sqlite3_errmsg)(db));
@ -372,7 +375,7 @@ __exit_execute_statement:
/**
* @brief Executes a SQL statement and returns the result set without parsing it.
*
*
* @param str The SQL statement to execute.
* @param error Pointer to a variable to store the error message.
* @param cols Pointer to a variable to store the number of columns.
@ -394,6 +397,8 @@ bool SQLite3DB::execute_statement_raw(const char *str, char **error, int *cols,
*cols = (*proxy_sqlite3_column_count)(*statement);
if (*cols==0) { // not a SELECT
//*resultset=NULL;
// Get total changes before executing the statement
long long total_changes_before = (*proxy_sqlite3_total_changes64)(db);
do {
rc=(*proxy_sqlite3_step)(*statement);
if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) { // the execution of the prepared statement failed because locked
@ -401,7 +406,9 @@ bool SQLite3DB::execute_statement_raw(const char *str, char **error, int *cols,
}
} while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);
if (rc==SQLITE_DONE) {
*affected_rows=(*proxy_sqlite3_changes)(db);
// Calculate affected rows as the difference in total changes
long long total_changes_after = (*proxy_sqlite3_total_changes64)(db);
*affected_rows = (int)(total_changes_after - total_changes_before);
ret=true;
} else {
*error=strdup((*proxy_sqlite3_errmsg)(db));
@ -1010,6 +1017,7 @@ void SQLite3DB::LoadPlugin(const char *plugin_name) {
proxy_sqlite3_status = NULL;
proxy_sqlite3_status64 = NULL;
proxy_sqlite3_changes = NULL;
proxy_sqlite3_total_changes64 = NULL;
proxy_sqlite3_step = NULL;
proxy_sqlite3_shutdown = NULL;
proxy_sqlite3_prepare_v2 = NULL;
@ -1089,6 +1097,7 @@ void SQLite3DB::LoadPlugin(const char *plugin_name) {
proxy_sqlite3_status = sqlite3_status;
proxy_sqlite3_status64 = sqlite3_status64;
proxy_sqlite3_changes = sqlite3_changes;
proxy_sqlite3_total_changes64 = sqlite3_total_changes64;
proxy_sqlite3_step = sqlite3_step;
proxy_sqlite3_shutdown = sqlite3_shutdown;
proxy_sqlite3_prepare_v2 = sqlite3_prepare_v2;
@ -1118,6 +1127,7 @@ void SQLite3DB::LoadPlugin(const char *plugin_name) {
assert(proxy_sqlite3_status);
assert(proxy_sqlite3_status64);
assert(proxy_sqlite3_changes);
assert(proxy_sqlite3_total_changes64);
assert(proxy_sqlite3_step);
assert(proxy_sqlite3_shutdown);
assert(proxy_sqlite3_prepare_v2);

@ -31,6 +31,7 @@
"mysql-reg_test_4716_single_semicolon-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"mysql-reg_test_4723_query_cache_stores_empty_result-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"mysql-reg_test_4867_query_rules-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_4855_affected_rows_ddl-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_5212_tcp_keepalive_warnings-t" : [ "default-g1" ],
"mysql-set_transaction-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"mysql-sql_log_bin-error-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],

@ -0,0 +1,195 @@
/*
Copyright (c) 2025 ProxySQL
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <string>
#include "tap.h"
#include "command_line.h"
#include "utils.h"
#include "mysql.h"
int main(int argc, char** argv) {
plan(14);
CommandLine cl;
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return exit_status();
}
MYSQL *admin = mysql_init(NULL);
ok(admin != NULL, "mysql_init() succeeded");
if (!admin) {
return exit_status();
}
// Connect to ProxySQL Admin using command line arguments
if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
diag("Failed to connect to ProxySQL Admin: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
ok(true, "Connected to ProxySQL Admin");
// Clean up any existing test tables
mysql_query(admin, "DROP TABLE IF EXISTS test_table_4855");
mysql_query(admin, "DROP TABLE IF EXISTS test_table_4855_2");
// Test 1: Run a DDL query - should return 0 affected rows (this was the bug)
if (mysql_query(admin, "CREATE TABLE test_table_4855 (id INT PRIMARY KEY, name VARCHAR(255))")) {
diag("Failed to execute CREATE TABLE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
my_ulonglong affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "CREATE TABLE returns 0 affected rows (bug fix verified): %llu", affected_rows);
diag("CREATE TABLE query executed successfully");
// Test 2: Run a DML query that affects rows
if (mysql_query(admin, "INSERT INTO test_table_4855 (id, name) VALUES (1, 'test1')")) {
diag("Failed to execute INSERT query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 1, "INSERT query returns 1 affected row: %llu", affected_rows);
diag("INSERT query executed successfully");
// Test 3: Run another DML query
if (mysql_query(admin, "INSERT INTO test_table_4855 (id, name) VALUES (2, 'test2')")) {
diag("Failed to execute second INSERT query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 1, "Second INSERT query returns 1 affected row: %llu", affected_rows);
diag("Second INSERT query executed successfully");
// Test 4: Run another DDL query - should return 0 affected rows
if (mysql_query(admin, "CREATE TABLE test_table_4855_2 (id INT PRIMARY KEY, value TEXT)")) {
diag("Failed to execute second CREATE TABLE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "Second CREATE TABLE returns 0 affected rows: %llu", affected_rows);
diag("Second CREATE TABLE query executed successfully");
// Test 5: Run an UPDATE query - should return correct affected rows
if (mysql_query(admin, "UPDATE test_table_4855 SET name = 'updated' WHERE id = 1")) {
diag("Failed to execute UPDATE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 1, "UPDATE query returns 1 affected row: %llu", affected_rows);
diag("UPDATE query executed successfully");
// Test 6: Run a DELETE query - should return correct affected rows
if (mysql_query(admin, "DELETE FROM test_table_4855 WHERE id IN (1, 2)")) {
diag("Failed to execute DELETE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 2, "DELETE query returns 2 affected rows: %llu", affected_rows);
diag("DELETE query executed successfully");
// Test 7: Run DROP TABLE - should return 0 affected rows
if (mysql_query(admin, "DROP TABLE test_table_4855")) {
diag("Failed to execute DROP TABLE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "DROP TABLE returns 0 affected rows: %llu", affected_rows);
diag("DROP TABLE query executed successfully");
// Test 8: Run another DDL to verify the fix again (ALTER TABLE)
if (mysql_query(admin, "ALTER TABLE test_table_4855_2 ADD COLUMN extra INTEGER")) {
diag("Failed to execute ALTER TABLE query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "ALTER TABLE returns 0 affected rows: %llu", affected_rows);
diag("ALTER TABLE query executed successfully");
// Test 9: Test with comments followed by DDL (this was mentioned in the issue)
if (mysql_query(admin, "/* This is a comment */ CREATE TABLE test_table_4855 (id INT PRIMARY KEY)")) {
diag("Failed to execute CREATE TABLE with comment: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "CREATE TABLE with comment returns 0 affected rows: %llu", affected_rows);
diag("CREATE TABLE with comment executed successfully");
// Test 10: Insert data and run SELECT to verify normal operation
if (mysql_query(admin, "INSERT INTO test_table_4855 (id) VALUES (1), (2), (3)")) {
diag("Failed to execute INSERT with multiple values: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 3, "INSERT with multiple values returns 3 affected rows: %llu", affected_rows);
diag("INSERT with multiple values executed successfully");
// Test 11: Run VACUUM - should return 0 affected rows
if (mysql_query(admin, "VACUUM")) {
diag("Failed to execute VACUUM query: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "VACUUM returns 0 affected rows: %llu", affected_rows);
diag("VACUUM query executed successfully");
// Test 12: Clean up - DROP remaining tables individually
if (mysql_query(admin, "DROP TABLE IF EXISTS test_table_4855")) {
diag("Failed to execute final DROP TABLE: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
affected_rows = mysql_affected_rows(admin);
ok(affected_rows == 0, "Final DROP TABLE returns 0 affected rows: %llu", affected_rows);
diag("Final DROP TABLE query executed successfully");
// Additional cleanup without test assertion
if (mysql_query(admin, "DROP TABLE IF EXISTS test_table_4855_2")) {
diag("Failed to execute second DROP TABLE: %s", mysql_error(admin));
mysql_close(admin);
return exit_status();
}
diag("Additional cleanup DROP TABLE executed");
mysql_close(admin);
return exit_status();
}
Loading…
Cancel
Save