Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/sqlite3db.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 14 additions & 4 deletions lib/sqlite3db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

#define USLEEP_SQLITE_LOCKED 100


/**
* @brief Constructor for the SQLite3_column class.
*
Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand All @@ -372,7 +375,7 @@ bool SQLite3DB::execute_statement(const char *str, char **error, int *cols, int

/**
* @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.
Expand All @@ -394,14 +397,18 @@ 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
usleep(USLEEP_SQLITE_LOCKED);
}
} 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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions test/tap/groups/groups.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" ],
Expand Down
195 changes: 195 additions & 0 deletions test/tap/tests/reg_test_4855_affected_rows_ddl-t.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
Comment on lines +55 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for executing a query and handling errors is repeated multiple times throughout the test. To improve code readability and maintainability, consider abstracting this logic into a helper function. For example, you could create a function like execute_query(MYSQL *admin, const char *query, const char *fail_msg) and call it for each query. This would reduce code duplication and make the test logic easier to follow.


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();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The file is missing a newline character at the end. It's a common convention to end files with a newline to prevent issues with file concatenation and some tools.

Suggested change
}
}