Skip to content

Conversation

@renecannao
Copy link
Contributor

Summary

Fixes issue #4855 where mysql_affected_rows() in ProxySQL Admin interface was incorrectly reporting affected rows for DDL queries. After executing DML operations, DDL queries would return the affected rows count from previous DML operations instead of 0.

Root Cause

The problem was caused by SQLite's sqlite3_changes() function, which doesn't reset the counter for DDL statements that don't modify data (CREATE, DROP, ALTER, VACUUM, etc.).

Solution

Implemented a robust mathematical approach using sqlite3_total_changes64() difference:

Core Changes:

  • Added function pointer: proxy_sqlite3_total_changes64 in include/sqlite3db.h
  • Function initialization: Added pointer initialization in lib/sqlite3db.cpp
  • Modified affected rows calculation: Updated both execute_statement() functions to calculate affected rows as the difference between total changes before and after query execution

Key Benefits:

  • Mathematically accurate: Measures actual change difference
  • Universal compatibility: Works with all query types (DDL, DML, comments)
  • No brittle parsing: No need for DDL detection heuristics
  • Handles edge cases: Works correctly with commented queries

Testing

Added comprehensive TAP test reg_test_4855_affected_rows_ddl-t.cpp with 14 test cases:

Setup Tests:

  • MySQL initialization and connection verification

DDL Tests (verify 0 affected rows):

  • CREATE TABLE
  • Second CREATE TABLE
  • DROP TABLE
  • ALTER TABLE
  • VACUUM
  • Final DROP TABLE (cleanup)
  • CREATE TABLE with comments

DML Tests (verify correct affected rows):

  • INSERT operations (1 affected row each)
  • UPDATE operation (1 affected row)
  • DELETE operation (2 affected rows)
  • INSERT with multiple values (3 affected rows)

Test Features:

  • SQLite-compatible: Uses only supported SQLite syntax
  • Temporary tables: No modification of ProxySQL configuration
  • Comprehensive cleanup: Proper table management
  • Error handling: Robust error checking throughout

Files Modified

  • include/sqlite3db.h: Added function pointer declaration
  • lib/sqlite3db.cpp: Added function pointer initialization and modified affected rows calculation
  • test/tap/tests/reg_test_4855_affected_rows_ddl-t.cpp: Comprehensive test suite

Testing

The fix has been tested with:

  • ✅ Main ProxySQL builds successfully
  • ✅ TAP test compiles and runs correctly
  • ✅ All DDL operations return 0 affected rows
  • ✅ All DML operations return correct affected rows
  • ✅ Edge cases (comments, VACUUM) handled correctly

Impact

This fix ensures that ProxySQL Admin interface accurately reports affected rows for all query types, resolving the issue where DDL queries incorrectly inherited affected rows counts from previous DML operations.

…erface

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
PROBLEM:
The initial fix used a DDL detection approach which required maintaining a list
of query types that should return 0 affected rows. This approach was brittle
and could miss edge cases like commented queries or complex statements.

SOLUTION:
Instead of detecting DDL queries, use sqlite3_total_changes64() to measure the
actual change count before and after each query execution. The difference between
total_changes before and after represents the true affected rows count for the
current query, regardless of query type.

CHANGES:
- Added proxy_sqlite3_total_changes64 function pointer and initialization
- Rewrote execute_statement() and execute_statement_raw() to use total_changes
  difference approach
- This automatically handles all query types (DDL, DML, comments, etc.)
- Added comprehensive TAP test covering INSERT, CREATE, DROP, VACUUM, UPDATE, and
  BEGIN operations

BENEFITS:
- More robust and accurate than DDL detection approach
- Handles edge cases like commented queries automatically
- No maintenance overhead for new query types
- Simpler and cleaner implementation
- Still fixes both Admin interface and SQLite3 Server

This approach is mathematically sound: affected_rows = total_changes_after -
total_changes_before, which gives the exact number of rows changed by the current
query execution.

Fixes #4855
- Updated main TAP test to remove proxy_sqlite3 wrapper dependency
- Added standalone TAP test using raw SQLite functions
- Both tests verify the sqlite3_total_changes64 difference approach works correctly
- Main ProxySQL build confirms the fix is functional
- TAP test connects to ProxySQL Admin interface on port 6032
- Tests INSERT, CREATE TABLE, DROP TABLE, UPDATE, DELETE operations
- Verifies that DDL queries return 0 affected rows after DML operations
- Confirms the sqlite3_total_changes64 fix works correctly in Admin interface
- This directly tests the issue reported in #4855
- Use CommandLine cl object with getEnv() for configuration
- Remove hardcoded IP, ports, and credentials
- Connect to both Admin interface and SQLite3 Server
- Test DDL queries returning 0 affected rows after DML operations
- Follow ProxySQL TAP test conventions and patterns
- Replace made-up fail()/pass() functions with ok() and diag()
- Follow proper TAP test structure from ProxySQL TAP guide
- Use plan(N), ok(condition, description), diag(), and return exit_status()
- Maintain proper error handling and test organization
Improve the TAP test to be SQLite-compatible and comprehensive:

- Remove TRUNCATE operations (not supported by SQLite)
- Use individual DROP TABLE statements instead of multi-table DROP
- Add VACUUM test to verify DDL affected rows are 0
- Update test plan to match actual number of assertions (14)
- Use temporary tables instead of configuration tables
- Test all major DDL operations: CREATE, DROP, ALTER, VACUUM
- Test all major DML operations: INSERT, UPDATE, DELETE
- Verify affected rows are correctly calculated for all query types
- Add test for commented DDL queries
- Include comprehensive cleanup with proper error handling

The test now accurately verifies that the sqlite3_total_changes64()
difference approach correctly fixes the affected rows calculation
for both DDL and DML operations.
@gemini-code-assist
Copy link

Summary of Changes

Hello @renecannao, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug in the ProxySQL Admin interface where DDL queries were incorrectly reporting affected row counts. The solution involves a fundamental change in how affected rows are calculated for non-SELECT statements, moving from a potentially misleading sqlite3_changes() call to a precise differential measurement using sqlite3_total_changes64(). This ensures that DDL operations, which do not modify data rows, consistently return zero affected rows, thereby improving the accuracy and reliability of the Admin interface's reporting.

Highlights

  • Incorrect Affected Rows for DDL Queries: This pull request resolves an issue where DDL (Data Definition Language) queries in the ProxySQL Admin interface incorrectly reported affected rows, often inheriting counts from preceding DML (Data Manipulation Language) operations. DDL queries should consistently report 0 affected rows.
  • Mathematical Approach for Affected Rows Calculation: The fix implements a robust mathematical approach by utilizing SQLite's sqlite3_total_changes64() function. Instead of relying on sqlite3_changes(), which doesn't reset for non-data-modifying DDL statements, the system now calculates affected rows as the difference between the total changes before and after a query's execution. This ensures accuracy for all query types without brittle parsing.
  • New Function Pointer and Initialization: A new function pointer, proxy_sqlite3_total_changes64, has been added to include/sqlite3db.h and initialized in lib/sqlite3db.cpp to properly integrate the sqlite3_total_changes64 SQLite API.
  • Comprehensive Test Suite: A new TAP test file, reg_test_4855_affected_rows_ddl-t.cpp, has been added with 14 detailed test cases. These tests cover various DDL operations (CREATE, DROP, ALTER, VACUUM) and DML operations (INSERT, UPDATE, DELETE), including edge cases like queries with comments, to thoroughly validate the corrected affected rows reporting.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request provides a solid fix for the incorrect reporting of affected rows for DDL queries. The use of sqlite3_total_changes64() is a robust solution to the problem. The implementation is clean and the accompanying test suite is comprehensive, covering various DDL and DML operations to ensure the fix is working as expected. I have a couple of minor suggestions for the new test file to improve its maintainability.

Comment on lines +55 to +59
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();
}

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.

diag("Additional cleanup DROP TABLE executed");
mysql_close(admin);
return exit_status();
} No newline at end of file

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
}
}

@noizu noizu added this to the Release 3.0.4 milestone Nov 28, 2025
@noizu noizu added area/data-definition type/bug Something isn't working correctly labels Nov 28, 2025
renecannao and others added 2 commits November 28, 2025 18:25
Include the regression test for issue 4855 in the g1 test group configuration
to ensure it runs as part of the standard test suite.
Signed-off-by: René Cannaò <rene@proxysql.com>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 1, 2025

@renecannao
Copy link
Contributor Author

retest this please

@renecannao renecannao merged commit b641c0d into v3.0 Dec 6, 2025
144 of 154 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/data-definition type/bug Something isn't working correctly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants