Fix issue 4855: Reset affected_rows to 0 for DDL queries in Admin interface

Problem:
When executing DDL queries (CREATE TABLE, DROP TABLE, VACUUM, etc.) in the
ProxySQL Admin interface after DML operations, the affected rows count from
the previous DML operation was incorrectly reported instead of 0. This is
because SQLite's sqlite3_changes() function doesn't reset the counter for
DDL statements.

Root Cause:
SQLite's sqlite3_changes() returns the number of rows affected by the most
recent INSERT, UPDATE, or DELETE statement. For DDL statements that don't
modify rows, SQLite doesn't reset this counter, so it continues to return
the value from the last DML operation.

Solution:
- Added is_ddl_query_without_row_changes() function to identify DDL queries
  that don't affect row counts
- Modified both execute_statement() and execute_statement_raw() in SQLite3DB
  to return 0 for affected_rows when executing DDL queries
- The fix ensures that affected_rows is reset to 0 for:
  CREATE, DROP, ALTER, TRUNCATE, VACUUM, REINDEX, ANALYZE, CHECKPOINT,
  PRAGMA, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, EXPLAIN

Testing:
- Created and ran comprehensive tests for DDL detection function
- Verified build completes successfully
- Confirmed the fix correctly identifies DDL vs DML queries

Impact:
This fix resolves the issue where Admin interface incorrectly shows affected
rows for DDL operations, improving the accuracy and reliability of the
ProxySQL Admin interface.

Fixes #4855
fix/issue-4855
Rene Cannao 3 months ago
parent dd847a15e1
commit 05960b5ddb

@ -17,6 +17,13 @@
#define USLEEP_SQLITE_LOCKED 100
/**
* @brief Check if a SQL statement is a DDL query that doesn't affect rows
*
* @param query The SQL statement to check
* @return True if the statement is a DDL query that doesn't affect rows
*/
static bool is_ddl_query_without_row_changes(const char *query);
/**
* @brief Constructor for the SQLite3_column class.
@ -353,7 +360,14 @@ 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);
int changes = (*proxy_sqlite3_changes)(db);
// For DDL queries that don't affect rows, reset affected_rows to 0
// to prevent incorrect reporting of previous DML affected rows
if (is_ddl_query_without_row_changes(str)) {
*affected_rows = 0;
} else {
*affected_rows = changes;
}
ret=true;
} else {
*error=strdup((*proxy_sqlite3_errmsg)(db));
@ -370,9 +384,43 @@ __exit_execute_statement:
return ret;
}
/**
* @brief Check if a SQL statement is a DDL query that doesn't affect rows
*
* @param query The SQL statement to check
* @return True if the statement is a DDL query that doesn't affect rows
*/
static bool is_ddl_query_without_row_changes(const char *query) {
if (!query) return false;
// Skip leading whitespace
while (isspace(*query)) {
query++;
}
// Check for DDL statements that don't affect row counts
return (
(strncasecmp(query, "CREATE", 6) == 0) ||
(strncasecmp(query, "DROP", 4) == 0) ||
(strncasecmp(query, "ALTER", 5) == 0) ||
(strncasecmp(query, "TRUNCATE", 8) == 0) ||
(strncasecmp(query, "VACUUM", 6) == 0) ||
(strncasecmp(query, "REINDEX", 7) == 0) ||
(strncasecmp(query, "ANALYZE", 7) == 0) ||
(strncasecmp(query, "CHECKPOINT", 10) == 0) ||
(strncasecmp(query, "PRAGMA", 6) == 0) ||
(strncasecmp(query, "BEGIN", 5) == 0) ||
(strncasecmp(query, "COMMIT", 6) == 0) ||
(strncasecmp(query, "ROLLBACK", 8) == 0) ||
(strncasecmp(query, "SAVEPOINT", 9) == 0) ||
(strncasecmp(query, "RELEASE", 7) == 0) ||
(strncasecmp(query, "EXPLAIN", 7) == 0)
);
}
/**
* @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.
@ -401,7 +449,14 @@ 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);
int changes = (*proxy_sqlite3_changes)(db);
// For DDL queries that don't affect rows, reset affected_rows to 0
// to prevent incorrect reporting of previous DML affected rows
if (is_ddl_query_without_row_changes(str)) {
*affected_rows = 0;
} else {
*affected_rows = changes;
}
ret=true;
} else {
*error=strdup((*proxy_sqlite3_errmsg)(db));

Loading…
Cancel
Save