Skip to content

feat: PgBouncer migration tooling - Stage 1: Config parser library#5566

Open
renecannao wants to merge 6 commits intov3.0from
feature/pgbouncer-compat
Open

feat: PgBouncer migration tooling - Stage 1: Config parser library#5566
renecannao wants to merge 6 commits intov3.0from
feature/pgbouncer-compat

Conversation

@renecannao
Copy link
Copy Markdown
Contributor

@renecannao renecannao commented Apr 3, 2026

Summary

  • Add standalone C++ library that parses PgBouncer config files (pgbouncer.ini, userlist.txt, pg_hba.conf) into structured data
  • Foundation for PgBouncer-to-ProxySQL migration tooling (Stages 2 and 3 follow)
  • Zero ProxySQL dependencies — pure C++ standard library, independently testable

What's included

Parser library (lib/pgbouncer_compat/):

  • PgBouncer_Config.h — All data structures (GlobalSettings, Database, User, Peer, AuthFileEntry, HBARule)
  • PgBouncer_ConfigParser — Full INI parser for pgbouncer.ini with all 4 sections, %include directives, and connection string key=value parsing
  • PgBouncer_AuthFileParser — userlist.txt parser with MD5/SCRAM/plain password detection and "" escape handling
  • PgBouncer_HBAParser — pg_hba.conf parser supporting local/host/hostssl/hostnossl records, all auth methods, and key=value options

Unit tests (test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp):

  • 127 TAP tests covering: minimal config, full config with all sections, auth file formats, HBA rules, malformed config (strict error detection), %include directives, nonexistent files, default value verification

Build integration:

  • New .oo files added to lib/Makefile
  • Test registered in test/tap/tests/unit/Makefile
  • Standalone test build (no libproxysql.a link needed)

Test plan

  • make build_lib -j$(nproc) compiles successfully
  • make pgbouncer_config_parser_unit-t builds the unit test
  • ./pgbouncer_config_parser_unit-t — all 127 tests pass
  • CI passes

Related issues

Design spec

See docs/superpowers/specs/2026-04-03-pgbouncer-migration-tooling-design.md in the pgbouncer_comp working directory for the full design.

Summary by CodeRabbit

  • New Features

    • PgBouncer config, auth and HBA parsing with include resolution and diagnostic reporting
    • Conversion of PgBouncer configs to ProxySQL SQL with dry-run, warnings/errors and summary
    • Translation of PgBouncer SHOW commands to ProxySQL queries
    • New proxysql-cli import-pgbouncer command and admin IMPORT PGBOUNCER integration (dry-run, ignore-warnings)
  • Tests

    • New unit tests and fixtures covering parsing, conversion, SHOW translations, include and error cases
  • Chores

    • Build and CI updates for PgBouncer compatibility

Add a standalone C++ library that parses PgBouncer configuration files
into structured data, as the foundation for PgBouncer-to-ProxySQL
migration tooling.

Components:
- PgBouncer_Config.h: Data structures for all PgBouncer config entities
- PgBouncer_ConfigParser: INI parser for pgbouncer.ini with all 4 sections
  ([pgbouncer], [databases], [users], [peers]), %include directive support,
  and connection string parsing
- PgBouncer_AuthFileParser: Parser for userlist.txt with MD5/SCRAM/plain
  password type detection and double-quote escaping
- PgBouncer_HBAParser: Parser for pg_hba.conf with support for all
  connection types, auth methods, and key=value options

The parser library has zero ProxySQL dependencies - it uses only the
C++ standard library. This makes it independently testable and reusable.

Unit test covers: minimal config, full config with all sections, auth file
formats, HBA rules, malformed configs (strict error detection), %include
directives, nonexistent files, and default value verification (127 tests).
Copilot AI review requested due to automatic review settings April 3, 2026 13:14
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a PgBouncer compatibility subsystem: public Config data model and parser APIs, INI/auth/HBA parsers with include resolution, conversion to ProxySQL SQL (with dry‑run/strict modes), SHOW-command translation, a proxysql‑cli, Admin integration for import, build/test updates, and unit tests/fixtures.

Changes

Cohort / File(s) Summary
Public API & CLI
include/PgBouncer_Config.h, include/PgBouncer_AuthFileParser.h, include/PgBouncer_ConfigParser.h, include/PgBouncer_HBAParser.h, include/PgBouncer_ShowCommands.h, include/PgBouncer_ConfigConverter.h, include/ProxySQL_CLI.h
New public headers: Config data model, parse diagnostics, parser class declarations, HBA/Auth types, conversion result types, SHOW translation API, and CLI entrypoint.
Parsers (impl)
lib/pgbouncer_compat/PgBouncer_ConfigParser.{cpp,h}, lib/pgbouncer_compat/PgBouncer_AuthFileParser.{cpp,h}, lib/pgbouncer_compat/PgBouncer_HBAParser.{cpp,h}
Implements INI parser with %include and depth limiting, connection-string tokenization, per-section handlers; auth-file parser with quoted-string unescaping and password-type detection; pg_hba tokenizer and record parser with validation and options parsing.
Converter & Show commands (impl)
lib/pgbouncer_compat/PgBouncer_ConfigConverter.{cpp,h}, lib/pgbouncer_compat/PgBouncer_ShowCommands.{cpp,h}
Implements Config→ProxySQL conversion producing SQL entries, warnings/errors, dry‑run formatting, strict vs relaxed handling; implements SHOW-to-SQL translation and unsupported-command messaging.
CLI, Admin & main integration
lib/pgbouncer_compat/ProxySQL_CLI.{cpp,h}, lib/Admin_Handler.cpp, src/main.cpp
Adds proxysql-cli entrypoint, import-pgbouncer admin command (DRY RUN / IGNORE WARNINGS), integrates SHOW translation into admin query handling, and dispatches proxysql-cli from main().
Build files
lib/Makefile
Adds new object modules and explicit compile rules for PgBouncer compat sources.
Unit test build & targets
test/tap/tests/unit/Makefile
Adds standalone PgBouncer test binaries, includes compat sources, and sets include paths for tests.
Unit tests & fixtures
test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp, .../pgbouncer_converter_unit-t.cpp, .../pgbouncer_show_commands_unit-t.cpp, test/tap/tests/unit/fixtures/pgbouncer_compat/*
Adds comprehensive TAP tests and fixtures (minimal/full/malformed INIs, includes, userlist, pg_hba.conf) covering parser, converter, SHOW translations, and edge cases.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant ConfigParser as ConfigParser (INI)
    participant FileSystem as FileSystem
    participant AuthParser as AuthFileParser
    participant HBAParser as HBAParser

    Caller->>ConfigParser: parse_config_file(path, config)
    ConfigParser->>FileSystem: open pgbouncer.ini
    FileSystem-->>ConfigParser: contents
    ConfigParser->>ConfigParser: parse sections & connstrs
    alt %include encountered
        ConfigParser->>FileSystem: open included file
        FileSystem-->>ConfigParser: included contents
        ConfigParser->>ConfigParser: parse included contents (recursively)
    end
    alt resolve_referenced_files enabled
        ConfigParser->>FileSystem: open auth_file (if set)
        FileSystem-->>ConfigParser: auth contents
        ConfigParser->>AuthParser: parse_auth_file(path)
        AuthParser-->>ConfigParser: entries + errors

        ConfigParser->>FileSystem: open auth_hba_file (if set)
        FileSystem-->>ConfigParser: hba contents
        ConfigParser->>HBAParser: parse_hba_file(path)
        HBAParser-->>ConfigParser: rules + errors
    end
    ConfigParser-->>Caller: success/failure + config (errors/warnings populated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #5564 — Conversion + proxysql-cli additions align with Stage 2 objectives (converter and CLI integration).
  • #5565 — SHOW-command translation and admin interception implemented here match the Stage 3 request.
  • #5563 — Implements Stage 1 goals: Config/auth/HBA parsers, include handling, data model, and unit tests.

Possibly related PRs

  • #5416 — Touches Admin_Handler.cpp similarly; likely overlaps in admin query/SHOW handling and may conflict or complement changes.
  • #5482 — Related to test/infrastructure adjustments; likely connected to added unit-test targets and Makefile updates.

Poem

🐇 I nibble lines of INI light,

quoted names and hashes bright,
HBA hops and users parsed with care,
I hop your config to ProxySQL there.
Tiny rabbit cheers — convert and share!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main objective: introducing a PgBouncer config parser library as Stage 1 of a migration tooling initiative.
Linked Issues check ✅ Passed All Stage 1 deliverables from issue #5563 are present: Config data structures, ConfigParser (INI+connstr), AuthFileParser (userlist.txt), HBAParser (pg_hba.conf), comprehensive unit tests, and build integration.
Out of Scope Changes check ✅ Passed Admin_Handler.cpp and src/main.cpp additions are Stage 2 converter/CLI integration, appearing premature for a Stage 1 library PR but required for functional import support; all changes align with Stage 1 foundation and documented objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/pgbouncer-compat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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 introduces a PgBouncer compatibility module, providing parsers for pgbouncer.ini, userlist.txt, and pg_hba.conf files, along with a comprehensive unit testing suite. The review feedback highlights several areas for improvement in the parsing logic, specifically regarding robust integer range checking, handling quoted values in the global configuration section, refining comment-stripping to respect quotes, and supporting escaped double quotes in the HBA tokenizer.

Comment on lines +1 to +6
#include "PgBouncer_ConfigParser.h"

#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Add <limits> to support range checking in integer parsing functions.

Suggested change
#include "PgBouncer_ConfigParser.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include "PgBouncer_ConfigParser.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <limits>

Comment on lines +54 to +78
bool ConfigParser::parse_int(const std::string& value, int& result) {
if (value.empty()) return false;
try {
size_t pos = 0;
long v = std::stol(value, &pos);
if (pos != value.size()) return false;
result = static_cast<int>(v);
return true;
} catch (...) {
return false;
}
}

bool ConfigParser::parse_uint(const std::string& value, unsigned int& result) {
if (value.empty()) return false;
try {
size_t pos = 0;
unsigned long v = std::stoul(value, &pos);
if (pos != value.size()) return false;
result = static_cast<unsigned int>(v);
return true;
} catch (...) {
return false;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation of parse_int and parse_uint has potential issues with narrowing conversions and range checking. std::stol and std::stoul can return values that exceed the range of int or unsigned int, and the subsequent static_cast will result in overflow or incorrect values. Using std::stoi for int (which throws std::out_of_range) and adding an explicit range check for unsigned int is safer.

bool ConfigParser::parse_int(const std::string& value, int& result) {
    if (value.empty()) return false;
    try {
        size_t pos = 0;
        result = std::stoi(value, &pos);
        return pos == value.size();
    } catch (...) {
        return false;
    }
}

bool ConfigParser::parse_uint(const std::string& value, unsigned int& result) {
    if (value.empty()) return false;
    try {
        size_t pos = 0;
        unsigned long v = std::stoul(value, &pos);
        if (pos != value.size()) return false;
        if (v > std::numeric_limits<unsigned int>::max()) return false;
        result = static_cast<unsigned int>(v);
        return true;
    } catch (...) {
        return false;
    }
}

Comment on lines +161 to +173
#define SET_STR(name) do { if (key == #name) { settings.name = value; return true; } } while(0)
#define SET_INT(name) do { if (key == #name) { \
int v; if (!parse_int(value, v)) { \
errors.push_back({file, line, "invalid integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_UINT(name) do { if (key == #name) { \
unsigned int v; if (!parse_uint(value, v)) { \
errors.push_back({file, line, "invalid unsigned integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_BOOL(name) do { if (key == #name) { \
bool v; if (!parse_bool(value, v)) { \
errors.push_back({file, line, "invalid boolean for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

PgBouncer configuration values in the [pgbouncer] section can be quoted (e.g., listen_port = '6432'). The current parser does not unquote these values before processing them, which will cause parse_int, parse_bool, and other functions to fail or store incorrect data (including the literal quotes).

Suggested change
#define SET_STR(name) do { if (key == #name) { settings.name = value; return true; } } while(0)
#define SET_INT(name) do { if (key == #name) { \
int v; if (!parse_int(value, v)) { \
errors.push_back({file, line, "invalid integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_UINT(name) do { if (key == #name) { \
unsigned int v; if (!parse_uint(value, v)) { \
errors.push_back({file, line, "invalid unsigned integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_BOOL(name) do { if (key == #name) { \
bool v; if (!parse_bool(value, v)) { \
errors.push_back({file, line, "invalid boolean for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_STR(name) do { if (key == #name) { settings.name = unquote(value); return true; } } while(0)
#define SET_INT(name) do { if (key == #name) { \
int v; if (!parse_int(unquote(value), v)) { \
errors.push_back({file, line, "invalid integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_UINT(name) do { if (key == #name) { \
unsigned int v; if (!parse_uint(unquote(value), v)) { \
errors.push_back({file, line, "invalid unsigned integer for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)
#define SET_BOOL(name) do { if (key == #name) { \
bool v; if (!parse_bool(unquote(value), v)) { \
errors.push_back({file, line, "invalid boolean for '" + key + "': " + value}); return false; } \
settings.name = v; return true; } } while(0)

Comment on lines +594 to +602
if (!value.empty() && value[0] != '\'') {
auto comment_pos = value.find(" #");
if (comment_pos == std::string::npos) comment_pos = value.find(" ;");
if (comment_pos == std::string::npos) comment_pos = value.find("\t#");
if (comment_pos == std::string::npos) comment_pos = value.find("\t;");
if (comment_pos != std::string::npos) {
value = trim(value.substr(0, comment_pos));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The comment stripping logic for the [pgbouncer] section is flawed when values are quoted. If a value starts with a single quote, stripping is skipped entirely, which means trailing comments like key = 'val' # comment will not be handled correctly. A more robust approach is to find the first comment character that is not inside a quoted string.

            // Remove trailing comments while respecting quoted values
            bool in_q = false;
            size_t comment_pos = std::string::npos;
            for (size_t i = 0; i < value.size(); ++i) {
                if (value[i] == '\'') {
                    if (in_q && i + 1 < value.size() && value[i+1] == '\'') { i++; } // escaped quote
                    else { in_q = !in_q; }
                } else if (!in_q && (value[i] == '#' || value[i] == ';')) {
                    if (i > 0 && std::isspace(static_cast<unsigned char>(value[i-1]))) {
                        comment_pos = i;
                        break;
                    }
                }
            }
            if (comment_pos != std::string::npos) {
                value = trim(value.substr(0, comment_pos));
            }

Comment on lines +52 to +57
if (in_quotes) {
if (c == '"') {
in_quotes = false;
} else {
token += c;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The HBA tokenizer does not handle escaped double quotes ("") inside quoted strings, which is standard in PostgreSQL/PgBouncer HBA files to represent a literal double quote.

        if (in_quotes) {
            if (c == '"') {
                if (i + 1 < line.size() && line[i + 1] == '"') {
                    token += '"';
                    ++i;
                } else {
                    in_quotes = false;
                }
            } else {
                token += c;
            }

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an initial, standalone C++ parsing library under lib/pgbouncer_compat/ to read PgBouncer configuration files into structured data, forming Stage 1 of the PgBouncer→ProxySQL migration tooling effort.

Changes:

  • Introduces PgBouncer_Config.h plus parsers for pgbouncer.ini, userlist.txt, and pg_hba.conf.
  • Adds TAP unit tests and fixture files covering expected and malformed inputs.
  • Integrates the new objects into lib/Makefile and registers a standalone unit-test target in test/tap/tests/unit/Makefile.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
include/PgBouncer_Config.h Defines the public data model + free-function parsing API.
lib/pgbouncer_compat/PgBouncer_ConfigParser.{h,cpp} Implements INI + connstr parsing, includes, and referenced-file resolution.
lib/pgbouncer_compat/PgBouncer_AuthFileParser.{h,cpp} Implements userlist.txt parsing with password type detection.
lib/pgbouncer_compat/PgBouncer_HBAParser.{h,cpp} Implements PgBouncer-flavored pg_hba.conf parsing.
test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp Adds unit coverage for config/auth/HBA parsing and include behavior.
test/tap/tests/unit/Makefile Registers new unit test and adds a standalone build rule.
test/tap/tests/unit/fixtures/pgbouncer_compat/* Adds test inputs for minimal/full/include/malformed configs, HBA, and userlist.
lib/Makefile Adds new .oo objects and explicit build rules for the new module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +61
bool ConfigParser::parse_int(const std::string& value, int& result) {
if (value.empty()) return false;
try {
size_t pos = 0;
long v = std::stol(value, &pos);
if (pos != value.size()) return false;
result = static_cast<int>(v);
return true;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

parse_int() casts the result of std::stol() from long to int without any range check. Values outside the int range will truncate/overflow, producing incorrect settings rather than a parse error. Consider validating v against std::numeric_limits::min()/max() before assigning and reporting an error if out of range.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +74
bool ConfigParser::parse_uint(const std::string& value, unsigned int& result) {
if (value.empty()) return false;
try {
size_t pos = 0;
unsigned long v = std::stoul(value, &pos);
if (pos != value.size()) return false;
result = static_cast<unsigned int>(v);
return true;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

parse_uint() casts the result of std::stoul() from unsigned long to unsigned int without a range check. On platforms where unsigned long is wider, out-of-range values will truncate silently; please validate against std::numeric_limits::max() and return a parse error when exceeded.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +138
if (i < len && connstr[i] == '\'') {
// Quoted value: collect until unescaped closing quote
// PgBouncer escapes single quotes by doubling: ''
++i; // skip opening quote
std::string raw = "'";
while (i < len) {
if (connstr[i] == '\'') {
if (i + 1 < len && connstr[i + 1] == '\'') {
raw += "''";
i += 2;
} else {
// closing quote
++i;
break;
}
} else {
raw += connstr[i];
++i;
}
}
raw += "'";
value = unquote(raw);
} else {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

parse_connstr_pairs(): if a single-quoted value is missing its closing quote, the loop falls off the end and the code still appends a quote and accepts the value. This can turn malformed configs into silently-accepted values; please detect unterminated quotes and return a parse error for that line.

Copilot uses AI. Check for mistakes.
{
std::ifstream ifs(filepath);
if (!ifs.is_open()) {
config.errors.push_back({"", 0, "cannot open file: " + filepath});
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When the INI file cannot be opened, the ParseMessage uses an empty file field (""), unlike other errors that set file to the path. This makes errors harder to attribute/format consistently; please set the file field to filepath here too.

Suggested change
config.errors.push_back({"", 0, "cannot open file: " + filepath});
config.errors.push_back({filepath, 0, "cannot open file: " + filepath});

Copilot uses AI. Check for mistakes.
Comment on lines +680 to +686
bool ConfigParser::parse(
const std::string& filepath, Config& config,
bool resolve_includes, bool resolve_referenced_files)
{
include_depth_ = 0;
return parse_ini(filepath, config, resolve_includes, resolve_referenced_files);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

ConfigParser::parse()/parse_ini() append into config.databases/users/peers/auth_entries/hba_rules/errors without clearing. Re-parsing into the same Config instance will accumulate duplicates and stale errors. Consider clearing/resetting the relevant vectors (or documenting the append semantics explicitly in the public API).

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +77
std::vector<std::string> HBAParser::tokenize(const std::string& line) {
std::vector<std::string> tokens;
std::string token;
bool in_quotes = false;

for (size_t i = 0; i < line.size(); ++i) {
char c = line[i];

if (in_quotes) {
if (c == '"') {
in_quotes = false;
} else {
token += c;
}
} else {
if (c == '#') {
// Rest of line is a comment
break;
} else if (c == '"') {
in_quotes = true;
} else if (std::isspace(static_cast<unsigned char>(c))) {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
} else {
token += c;
}
}
}
if (!token.empty()) {
tokens.push_back(token);
}
return tokens;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

HBAParser::tokenize() does not detect unterminated double-quoted strings: it will return tokens even if in_quotes remains true at end-of-line. This can lead to confusing downstream errors or accepting malformed input; consider explicitly erroring on unmatched quotes (e.g., by returning a status and adding a ParseMessage for that line).

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +88
bool AuthFileParser::parse(const std::string& filepath,
std::vector<AuthFileEntry>& entries,
std::vector<ParseMessage>& errors) {
std::ifstream file(filepath);
if (!file.is_open()) {
errors.push_back({filepath, 0, "Cannot open auth file: " + filepath});
return false;
}

std::string line;
int line_num = 0;
bool had_errors = false;

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

AuthFileParser::parse() appends to entries/errors without clearing them. If the caller reuses the same vectors across calls, results will be duplicated and error reporting will be confusing. Consider clearing these outputs at the start of parse() (or documenting that the function appends).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp (2)

536-542: Inconsistent path separator handling for relative %include paths.

Line 520 correctly handles both / and \\ when extracting base_dir, but line 540 only checks for / when determining if a path is absolute. On Windows, paths like C:\path\file.ini would be incorrectly treated as relative.

♻️ Consistent cross-platform path check
             std::string inc_path = trim(trimmed.substr(9));
             // Resolve relative paths against the base directory
-            if (!inc_path.empty() && inc_path[0] != '/') {
+            if (!inc_path.empty() && inc_path[0] != '/' &&
+                !(inc_path.size() >= 2 && inc_path[1] == ':')) {  // Windows drive letter
                 inc_path = base_dir + inc_path;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp` around lines 536 - 542, The
code treats an include path as absolute only if it starts with '/', which breaks
on Windows; update the logic in PgBouncer_ConfigParser.cpp around the %include
handling (variables: trimmed, resolve_includes, inc_path, base_dir) to detect
absolute paths cross-platform — accept paths starting with '/' or '\\', and also
treat Windows drive-letter prefixes (e.g., 'C:') as absolute — otherwise prepend
base_dir when making inc_path relative.

54-65: Potential integer overflow on long to int cast.

std::stol returns long which is 64-bit on many platforms. The cast to int can truncate large values. For PgBouncer config values this is unlikely to cause issues in practice, but consider adding range validation.

♻️ Optional: Add range check before cast
 bool ConfigParser::parse_int(const std::string& value, int& result) {
     if (value.empty()) return false;
     try {
         size_t pos = 0;
         long v = std::stol(value, &pos);
         if (pos != value.size()) return false;
+        if (v < std::numeric_limits<int>::min() || v > std::numeric_limits<int>::max()) return false;
         result = static_cast<int>(v);
         return true;
     } catch (...) {
         return false;
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp` around lines 54 - 65, The
parse_int function currently parses into a long via std::stol and blindly casts
to int (in ConfigParser::parse_int), which can truncate/overflow; modify
parse_int to validate that the parsed long (v) is within the int range (INT_MIN
<= v <= INT_MAX) before assigning to result and returning true, and return false
if out-of-range or on parse errors; use the existing pos check from std::stol
and keep the same error handling behavior.
lib/pgbouncer_compat/PgBouncer_HBAParser.cpp (1)

44-78: Tokenizer doesn't handle escaped quotes inside double-quoted strings.

Unlike AuthFileParser::parse_quoted_string, this tokenizer doesn't handle "" escape sequences within quoted tokens. If an HBA file contains a value with embedded quotes (e.g., "user with ""quotes"""), it would be incorrectly parsed. This is unlikely in practice for pg_hba.conf, but worth noting.

♻️ Optional: Add escaped quote handling for consistency
         if (in_quotes) {
             if (c == '"') {
+                // Check for escaped quote ""
+                if (i + 1 < line.size() && line[i + 1] == '"') {
+                    token += '"';
+                    ++i;
+                } else {
                     in_quotes = false;
+                }
             } else {
                 token += c;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_HBAParser.cpp` around lines 44 - 78, The
tokenizer HBAParser::tokenize must handle escaped double quotes inside quoted
tokens like AuthFileParser::parse_quoted_string does; update the in_quotes
branch so when you see a '"' check if the next character is also '"' (i+1 <
line.size() && line[i+1] == '"'), and if so append a single '"' to token and
advance the index to skip the escaped quote, otherwise treat it as the closing
quote; preserve existing behavior for comments, whitespace splitting, and final
token push.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/PgBouncer_Config.h`:
- Around line 1-2: The include guard macro PGBOUNCER_CONFIG_H does not follow
the required pattern for headers in include/, so replace the existing guard
macro (PGBOUNCER_CONFIG_H) with the formal pattern __CLASS_PGBOUNCER_CONFIG_H
throughout the file (both the `#ifndef/`#define and the trailing `#endif` comment)
so the header uses __CLASS_PGBOUNCER_CONFIG_H consistently; update any
references to PGBOUNCER_CONFIG_H in this header to the new macro name to avoid
mismatches.
- Around line 10-220: Rename all public types and function signatures to include
the protocol prefix (e.g., PgSQL_) and update all references; specifically
rename structs and enum: ParseMessage -> PgSQL_ParseMessage, GlobalSettings ->
PgSQL_GlobalSettings, Database -> PgSQL_Database, User -> PgSQL_User, Peer ->
PgSQL_Peer, AuthFileEntry -> PgSQL_AuthFileEntry, HBARule -> PgSQL_HBARule,
Config -> PgSQL_Config, and enum AuthType -> PgSQL_AuthType (adjust enum values
if needed); also update parser function signatures parse_config_file,
parse_auth_file, parse_hba_file to accept/return PgSQL_Config / PgSQL_* types
accordingly and update any code that constructs or references these types and
their members to use the new names.

In `@lib/pgbouncer_compat/PgBouncer_HBAParser.h`:
- Line 10: Rename the class HBAParser to PgSQL_HBAParser and update all
corresponding declarations and usages: change the class declaration name in the
header, rename any constructors/destructors (e.g., HBAParser::HBAParser ->
PgSQL_HBAParser::PgSQL_HBAParser), update forward declarations, type references,
factory functions, method calls, casts, and any places the symbol HBAParser is
referenced in .cpp/.hpp/.h files and tests; ensure header guards, exports, and
ABI-visible symbols (if any) are updated to match the new class name so builds
and linkage continue to succeed.

In `@test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp`:
- Around line 351-363: The TAP plan in main() is wrong: replace the call
plan(127) with plan(135) so the declared test count matches the actual 135
assertions run by test_minimal_config(), test_full_config(), test_auth_file(),
test_hba_file(), test_malformed_config(), test_include_directive(),
test_nonexistent_file(), and test_defaults(); ensure the documented per-test
comments (counts next to each test call) are updated to reflect the correct
distribution or remove stale counts, then return exit_status() as before.

---

Nitpick comments:
In `@lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp`:
- Around line 536-542: The code treats an include path as absolute only if it
starts with '/', which breaks on Windows; update the logic in
PgBouncer_ConfigParser.cpp around the %include handling (variables: trimmed,
resolve_includes, inc_path, base_dir) to detect absolute paths cross-platform —
accept paths starting with '/' or '\\', and also treat Windows drive-letter
prefixes (e.g., 'C:') as absolute — otherwise prepend base_dir when making
inc_path relative.
- Around line 54-65: The parse_int function currently parses into a long via
std::stol and blindly casts to int (in ConfigParser::parse_int), which can
truncate/overflow; modify parse_int to validate that the parsed long (v) is
within the int range (INT_MIN <= v <= INT_MAX) before assigning to result and
returning true, and return false if out-of-range or on parse errors; use the
existing pos check from std::stol and keep the same error handling behavior.

In `@lib/pgbouncer_compat/PgBouncer_HBAParser.cpp`:
- Around line 44-78: The tokenizer HBAParser::tokenize must handle escaped
double quotes inside quoted tokens like AuthFileParser::parse_quoted_string
does; update the in_quotes branch so when you see a '"' check if the next
character is also '"' (i+1 < line.size() && line[i+1] == '"'), and if so append
a single '"' to token and advance the index to skip the escaped quote, otherwise
treat it as the closing quote; preserve existing behavior for comments,
whitespace splitting, and final token push.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e48e2aa3-5ba7-40c0-9a6a-009621a4590c

📥 Commits

Reviewing files that changed from the base of the PR and between 63007cf and a3b59b3.

📒 Files selected for processing (17)
  • include/PgBouncer_Config.h
  • lib/Makefile
  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp
  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.h
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.h
  • lib/pgbouncer_compat/PgBouncer_HBAParser.cpp
  • lib/pgbouncer_compat/PgBouncer_HBAParser.h
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/fixtures/pgbouncer_compat/full.ini
  • test/tap/tests/unit/fixtures/pgbouncer_compat/include_databases.ini
  • test/tap/tests/unit/fixtures/pgbouncer_compat/include_main.ini
  • test/tap/tests/unit/fixtures/pgbouncer_compat/malformed.ini
  • test/tap/tests/unit/fixtures/pgbouncer_compat/minimal.ini
  • test/tap/tests/unit/fixtures/pgbouncer_compat/pg_hba.conf
  • test/tap/tests/unit/fixtures/pgbouncer_compat/userlist.txt
  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: CI-builds / builds (ubuntu22,-tap)
  • GitHub Check: CI-builds / builds (ubuntu24,-tap-genai-gcov)
  • GitHub Check: CI-builds / builds (debian12,-dbg)
  • GitHub Check: Agent
  • GitHub Check: run / trigger
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{cpp,h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)
Member variables must use snake_case
Constants and macros must use UPPER_SNAKE_CASE
C++17 is required; use conditional compilation via #ifdef PROXYSQLGENAI, #ifdef PROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters

Files:

  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.h
  • lib/pgbouncer_compat/PgBouncer_HBAParser.h
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.h
  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp
  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
  • lib/pgbouncer_compat/PgBouncer_HBAParser.cpp
  • include/PgBouncer_Config.h
**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Use RAII for resource management and jemalloc for memory allocation

Files:

  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp
  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
  • lib/pgbouncer_compat/PgBouncer_HBAParser.cpp
lib/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

One class per file is the typical convention

Files:

  • lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
  • lib/pgbouncer_compat/PgBouncer_HBAParser.cpp
test/tap/tests/unit/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests in test/tap/tests/unit/ must use test_globals.h and test_init.h and link against libproxysql.a via the custom test harness

Files:

  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
include/**/*.{h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Include guards must follow the pattern #ifndef _CLASS*_H

Files:

  • include/PgBouncer_Config.h
🧠 Learnings (9)
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : C++17 is required; use conditional compilation via `#ifdef` PROXYSQLGENAI, `#ifdef` PROXYSQL31, etc. for feature flags

Applied to files:

  • lib/Makefile
  • test/tap/tests/unit/Makefile
📚 Learning: 2026-04-01T21:27:00.297Z
Learnt from: wazir-ahmed
Repo: sysown/proxysql PR: 5557
File: test/tap/tests/unit/gtid_set_unit-t.cpp:14-17
Timestamp: 2026-04-01T21:27:00.297Z
Learning: In ProxySQL's unit test directory (test/tap/tests/unit/), test_globals.h and test_init.h are only required for tests that depend on the ProxySQL runtime globals/initialization (i.e., tests that exercise components linked against libproxysql.a). Pure data-structure or utility tests (e.g., ezoption_parser_unit-t.cpp, gtid_set_unit-t.cpp, gtid_trxid_interval_unit-t.cpp) only need tap.h and the relevant project header — omitting test_globals.h and test_init.h is correct and intentional in these cases.

Applied to files:

  • lib/Makefile
  • test/tap/tests/unit/Makefile
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Feature tiers are controlled by build flags: PROXYSQL31=1 for v3.1.x, PROXYSQLGENAI=1 for v4.0.x; PROXYSQLGENAI=1 implies PROXYSQL31=1

Applied to files:

  • lib/Makefile
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Admin interface uses SQLite3 backend for SQL-based configuration and schema versions are tracked in ProxySQL_Admin_Tables_Definitions.h

Applied to files:

  • lib/Makefile
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)

Applied to files:

  • lib/Makefile
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to test/tap/tests/unit/**/*.cpp : Unit tests in test/tap/tests/unit/ must use test_globals.h and test_init.h and link against libproxysql.a via the custom test harness

Applied to files:

  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to test/tap/tests/{test_*,*-t}.cpp : Test files in test/tap/tests/ must follow the naming pattern test_*.cpp or *-t.cpp

Applied to files:

  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
📚 Learning: 2026-04-01T21:27:00.297Z
Learnt from: wazir-ahmed
Repo: sysown/proxysql PR: 5557
File: test/tap/tests/unit/gtid_set_unit-t.cpp:14-17
Timestamp: 2026-04-01T21:27:00.297Z
Learning: In ProxySQL unit tests under test/tap/tests/unit/, include test_globals.h and test_init.h only for tests that depend on ProxySQL runtime globals/initialization (i.e., tests that exercise components linked against libproxysql.a). For “pure” data-structure/utility tests (e.g., ezoption_parser_unit-t.cpp, gtid_set_unit-t.cpp, gtid_trxid_interval_unit-t.cpp) that do not require runtime globals/initialization, it is correct to omit test_globals.h and test_init.h and instead include only tap.h plus the relevant project header(s).

Applied to files:

  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
📚 Learning: 2026-01-20T09:34:19.124Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:39-48
Timestamp: 2026-01-20T09:34:19.124Z
Learning: In ProxySQL's TAP test suite, resource leaks (e.g., not calling mysql_close() on early return paths) are commonly tolerated because test processes are short-lived and OS frees resources on exit. This pattern applies to all C++ test files under test/tap/tests. When reviewing, recognize this as a project-wide test convention and focus on test correctness and isolation rather than insisting on fixing such leaks in these test files.

Applied to files:

  • test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp
🪛 Clang (14.0.6)
test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp

[error] 9-9: 'tap.h' file not found

(clang-diagnostic-error)

include/PgBouncer_Config.h

[error] 4-4: 'string' file not found

(clang-diagnostic-error)

🔇 Additional comments (23)
test/tap/tests/unit/fixtures/pgbouncer_compat/include_databases.ini (1)

1-2: Fixture looks correct for include-database coverage.

This is a clean minimal fixture for validating %include database merge behavior.

test/tap/tests/unit/fixtures/pgbouncer_compat/minimal.ini (1)

1-6: Minimal fixture is well-scoped.

Good baseline input for parser default/value-path unit tests.

test/tap/tests/unit/fixtures/pgbouncer_compat/userlist.txt (1)

1-4: Auth fixture coverage is strong.

This includes the key credential formats and quoting edge case needed for parser validation.

test/tap/tests/unit/fixtures/pgbouncer_compat/include_main.ini (1)

1-5: Include fixture is correctly structured.

Good focused input for validating %include resolution from a primary config.

test/tap/tests/unit/fixtures/pgbouncer_compat/malformed.ini (1)

1-9: Malformed fixture is effective for negative-path testing.

This should exercise unknown-key and unknown-section error reporting as intended.

lib/Makefile (1)

119-123: Build integration changes look good.

The new parser modules are consistently added to object aggregation and have explicit compilation rules aligned with their subdirectory layout.

Also applies to: 155-163

test/tap/tests/unit/fixtures/pgbouncer_compat/pg_hba.conf (1)

1-6: HBA fixture has good rule coverage.

It exercises multiple connection types, auth methods, and option parsing in a compact form.

test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp (7)

9-9: Static analysis false positive — tap.h is resolved via Makefile include path.

The Clang error about tap.h not found is a false positive. The Makefile correctly specifies -I$(TAP_IDIR) which resolves the include path at build time.


22-35: LGTM! Minimal config test with appropriate assertions.

The test validates core parsing functionality and default values for unspecified fields.


40-187: LGTM! Comprehensive full config test with thorough coverage.

The test validates all major parsing scenarios: global settings, database entries (including multi-host, wildcard, connect_query), user overrides, peer definitions, and auth file resolution. The iteration-based lookups correctly avoid assumptions about ordering.


192-217: LGTM! Auth file standalone parsing test.

Good coverage of password type detection (PLAIN, MD5, SCRAM) and escaped quote handling.


222-260: LGTM! HBA file parsing test with good rule coverage.

Tests local, host (IPv4/IPv6), hostssl with options, and hostnossl reject rules.


265-289: LGTM! Malformed config error detection test.

Appropriately tests that invalid configs are rejected and errors are populated.


294-312: LGTM! Include directive test with fallback handling.

The conditional check for empty databases vector with explicit skip markers is a good pattern for handling test dependencies.

test/tap/tests/unit/Makefile (1)

316-325: LGTM! Standalone test build rule is correctly structured.

The build rule appropriately compiles the PgBouncer parser sources directly with the test file, links only tap.o and tap_noise_stubs.o (no libproxysql.a), and adds the correct include path. This aligns with the PR objective of a standalone library with no ProxySQL dependencies. Based on learnings: "pure data-structure/utility tests only need tap.h and the relevant project header — omitting test_globals.h and test_init.h is correct and intentional."

test/tap/tests/unit/fixtures/pgbouncer_compat/full.ini (1)

1-52: LGTM! Comprehensive test fixture covering key parsing scenarios.

The fixture appropriately covers multiple parsing scenarios: global settings, multi-host databases with comma-separated hosts, wildcard database entry (*), single-quoted connect_query, per-user overrides, and peer definitions. This provides good coverage for the unit tests.

lib/pgbouncer_compat/PgBouncer_AuthFileParser.h (1)

1-35: LGTM! Clean header design with appropriate encapsulation.

The public API is minimal and well-documented. The class correctly encapsulates parsing logic with private helpers. The namespace PgBouncer:: provides appropriate scoping for this compatibility library.

lib/pgbouncer_compat/PgBouncer_ConfigParser.h (1)

1-57: LGTM! Well-designed public API with appropriate configuration options.

The parse() method's optional parameters (resolve_includes, resolve_referenced_files) provide flexibility for different use cases (e.g., testing without file I/O). The MAX_INCLUDE_DEPTH constant properly guards against infinite recursion.

lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp (1)

1-158: LGTM! Robust auth file parser implementation.

The implementation correctly handles:

  • Escaped quotes (""") in quoted strings
  • Password type detection matching PgBouncer's expected formats (SCRAM-SHA-256$, md5+32hex, plain)
  • Graceful error recovery (continues parsing after malformed lines)
  • Comment lines starting with # or ;

The behavior of silently ignoring extra fields after the second quoted string appropriately matches PgBouncer's documented behavior.

lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp (2)

591-603: Inline comment stripping logic could incorrectly truncate values containing # or ;.

The current logic strips inline comments for [pgbouncer] section values that don't start with a single quote. However, unquoted values containing # or ; (e.g., a parameter that legitimately contains these characters) would be truncated. This matches PgBouncer's behavior, but the comment could clarify this is intentional.


650-671: LGTM! Referenced file resolution at correct depth.

The include_depth_ == 0 check ensures auth/HBA files are resolved only once after the main parse completes, avoiding redundant parsing during nested %include processing.

lib/pgbouncer_compat/PgBouncer_HBAParser.cpp (2)

84-166: LGTM! Robust HBA record parsing with appropriate validation.

The parser correctly handles:

  • All valid connection types (local, host, hostssl, hostnossl)
  • Address/mask detection heuristics for non-CIDR addresses
  • All PgBouncer-supported authentication methods
  • Strict key=value option parsing

The mask detection logic at lines 125-136 appropriately handles the ambiguous case where a separate netmask follows an address without CIDR notation.


172-202: LGTM! File parsing with proper error accumulation.

The parser continues processing after individual record errors, accumulating all diagnostics while returning overall success/failure status.

Comment on lines +1 to +2
#ifndef PGBOUNCER_CONFIG_H
#define PGBOUNCER_CONFIG_H
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the required include-guard pattern for include/ headers.

The guard uses PGBOUNCER_CONFIG_H, but headers in include/ must follow __CLASS_*_H.

Suggested fix
-#ifndef PGBOUNCER_CONFIG_H
-#define PGBOUNCER_CONFIG_H
+#ifndef __CLASS_PGBOUNCER_CONFIG_H
+#define __CLASS_PGBOUNCER_CONFIG_H
...
-#endif // PGBOUNCER_CONFIG_H
+#endif // __CLASS_PGBOUNCER_CONFIG_H

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

Also applies to: 224-224

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_Config.h` around lines 1 - 2, The include guard macro
PGBOUNCER_CONFIG_H does not follow the required pattern for headers in include/,
so replace the existing guard macro (PGBOUNCER_CONFIG_H) with the formal pattern
__CLASS_PGBOUNCER_CONFIG_H throughout the file (both the `#ifndef/`#define and the
trailing `#endif` comment) so the header uses __CLASS_PGBOUNCER_CONFIG_H
consistently; update any references to PGBOUNCER_CONFIG_H in this header to the
new macro name to avoid mismatches.

Comment on lines +10 to +220
struct ParseMessage {
std::string file;
int line = 0;
std::string message;
};

struct GlobalSettings {
// Listener
std::string listen_addr;
int listen_port = 6432;
std::string unix_socket_dir;
int listen_backlog = 128;
std::string unix_socket_mode;
std::string unix_socket_group;

// Pooling
std::string pool_mode = "session";
int default_pool_size = 20;
int min_pool_size = 0;
int reserve_pool_size = 0;
int reserve_pool_timeout = 5;
bool server_round_robin = false;

// Limits
int max_client_conn = 100;
int max_db_connections = 0;
int max_db_client_connections = 0;
int max_user_connections = 0;
int max_user_client_connections = 0;

// Authentication
std::string auth_type = "md5";
std::string auth_file;
std::string auth_hba_file;
std::string auth_ident_file;
std::string auth_user;
std::string auth_query;
std::string auth_dbname;
std::string auth_ldap_options;

// Timeouts (in seconds)
int server_lifetime = 3600;
int server_idle_timeout = 600;
int server_connect_timeout = 15;
int server_login_retry = 15;
int client_login_timeout = 60;
int client_idle_timeout = 0;
int query_timeout = 0;
int query_wait_timeout = 120;
int cancel_wait_timeout = 10;
int idle_transaction_timeout = 0;
int transaction_timeout = 0;
int suspend_timeout = 10;
int autodb_idle_timeout = 3600;

// Server maintenance
std::string server_reset_query = "DISCARD ALL";
bool server_reset_query_always = false;
std::string server_check_query;
int server_check_delay = 30;
bool server_fast_close = false;

// TLS client-facing
std::string client_tls_sslmode = "disable";
std::string client_tls_key_file;
std::string client_tls_cert_file;
std::string client_tls_ca_file;
std::string client_tls_protocols = "secure";
std::string client_tls_ciphers;
std::string client_tls13_ciphers;
std::string client_tls_dheparams = "auto";
std::string client_tls_ecdhcurve = "auto";

// TLS server-facing
std::string server_tls_sslmode = "prefer";
std::string server_tls_key_file;
std::string server_tls_cert_file;
std::string server_tls_ca_file;
std::string server_tls_protocols = "secure";
std::string server_tls_ciphers;
std::string server_tls13_ciphers;

// Logging
std::string logfile;
bool syslog = false;
std::string syslog_ident = "pgbouncer";
std::string syslog_facility = "daemon";
bool log_connections = true;
bool log_disconnections = true;
bool log_pooler_errors = true;
bool log_stats = true;
int stats_period = 60;
int verbose = 0;

// Admin
std::string admin_users;
std::string stats_users;

// Networking
bool so_reuseport = false;
bool tcp_defer_accept = false;
bool tcp_keepalive = true;
int tcp_keepcnt = 0;
int tcp_keepidle = 0;
int tcp_keepintvl = 0;
int tcp_socket_buffer = 0;
int tcp_user_timeout = 0;

// Protocol
int max_prepared_statements = 200;
bool disable_pqexec = false;
bool application_name_add_host = false;
std::string track_extra_parameters = "IntervalStyle";
std::string ignore_startup_parameters;
int scram_iterations = 4096;
int pkt_buf = 4096;
unsigned int max_packet_size = 2147483647;
int sbuf_loopcnt = 5;
int query_wait_notify = 5;

// DNS
int dns_max_ttl = 15;
int dns_nxdomain_ttl = 15;
int dns_zone_check_period = 0;
std::string resolv_conf;

// Process
std::string pidfile;
std::string user;
int peer_id = 0;
};

struct Database {
std::string name; // entry name, or "*" for wildcard
std::string host; // comma-separated for multi-host
int port = 5432;
std::string dbname; // destination database (empty = same as name)
std::string user; // forced user (empty = client user)
std::string password;
std::string auth_user;
std::string auth_query;
std::string auth_dbname;
std::string pool_mode; // per-db override, empty = use global
int pool_size = -1; // -1 = use default
int min_pool_size = -1;
int reserve_pool_size = -1;
int max_db_connections = -1;
int max_db_client_connections = -1;
int server_lifetime = -1;
std::string load_balance_hosts;
std::string connect_query;
std::string client_encoding;
std::string datestyle;
std::string timezone;
std::string application_name;
};

struct User {
std::string name;
std::string pool_mode;
int pool_size = -1;
int reserve_pool_size = -1;
int max_user_connections = -1;
int max_user_client_connections = -1;
int query_timeout = -1;
int idle_transaction_timeout = -1;
int transaction_timeout = -1;
int client_idle_timeout = -1;
};

struct Peer {
int peer_id = 0;
std::string host;
int port = 6432;
int pool_size = -1;
};

enum class AuthType { PLAIN, MD5, SCRAM };

struct AuthFileEntry {
std::string username;
std::string password;
AuthType type = AuthType::PLAIN;
};

struct HBARule {
std::string conn_type; // local, host, hostssl, hostnossl
std::string database; // all, sameuser, dbname, @file
std::string user; // all, username, @file
std::string address; // IP/CIDR or "all" (empty for local)
std::string mask; // optional separate mask
std::string method; // trust, reject, md5, scram-sha-256, etc.
std::map<std::string, std::string> options;
};

struct Config {
GlobalSettings global;
std::vector<Database> databases;
std::vector<User> users;
std::vector<Peer> peers;
std::vector<AuthFileEntry> auth_entries;
std::vector<HBARule> hba_rules;
std::vector<ParseMessage> errors;
std::vector<ParseMessage> warnings;
};

// Parser functions
// Returns true on success, false on error (errors populated in config.errors)
bool parse_config_file(const std::string& filepath, Config& config);
bool parse_auth_file(const std::string& filepath, std::vector<AuthFileEntry>& entries, std::vector<ParseMessage>& errors);
bool parse_hba_file(const std::string& filepath, std::vector<HBARule>& rules, std::vector<ParseMessage>& errors);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Rename exported types to include the required protocol prefix.

Public type names (ParseMessage, GlobalSettings, Database, User, Peer, AuthType, AuthFileEntry, HBARule, Config) do not follow the mandated protocol-prefixed class naming convention.

Example direction (apply consistently across declarations and function signatures)
-struct ParseMessage {
+struct PgSQL_ParseMessage {
@@
-struct Config {
-    GlobalSettings global;
-    std::vector<Database> databases;
+struct PgSQL_Config {
+    PgSQL_GlobalSettings global;
+    std::vector<PgSQL_Database> databases;
@@
-bool parse_config_file(const std::string& filepath, Config& config);
+bool parse_config_file(const std::string& filepath, PgSQL_Config& config);

As per coding guidelines, **/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_Config.h` around lines 10 - 220, Rename all public types
and function signatures to include the protocol prefix (e.g., PgSQL_) and update
all references; specifically rename structs and enum: ParseMessage ->
PgSQL_ParseMessage, GlobalSettings -> PgSQL_GlobalSettings, Database ->
PgSQL_Database, User -> PgSQL_User, Peer -> PgSQL_Peer, AuthFileEntry ->
PgSQL_AuthFileEntry, HBARule -> PgSQL_HBARule, Config -> PgSQL_Config, and enum
AuthType -> PgSQL_AuthType (adjust enum values if needed); also update parser
function signatures parse_config_file, parse_auth_file, parse_hba_file to
accept/return PgSQL_Config / PgSQL_* types accordingly and update any code that
constructs or references these types and their members to use the new names.


namespace PgBouncer {

class HBAParser {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rename the class to match repository naming convention.

HBAParser does not follow the required protocol-prefixed class naming style. Please rename it (and corresponding declarations/usages) to a prefixed form like PgSQL_HBAParser or ProxySQL_HBAParser.

As per coding guidelines **/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_HBAParser.h` at line 10, Rename the class
HBAParser to PgSQL_HBAParser and update all corresponding declarations and
usages: change the class declaration name in the header, rename any
constructors/destructors (e.g., HBAParser::HBAParser ->
PgSQL_HBAParser::PgSQL_HBAParser), update forward declarations, type references,
factory functions, method calls, casts, and any places the symbol HBAParser is
referenced in .cpp/.hpp/.h files and tests; ensure header guards, exports, and
ABI-visible symbols (if any) are updated to match the new class name so builds
and linkage continue to succeed.

Comment on lines +351 to +363
int main() {
plan(127);

test_minimal_config(); // 7 tests
test_full_config(); // 42 tests
test_auth_file(); // 11 tests
test_hba_file(); // 19 tests
test_malformed_config(); // 3 tests
test_include_directive(); // 7 tests
test_nonexistent_file(); // 2 tests
test_defaults(); // 9 tests (adjusted: removed 1 duplicate)

return exit_status();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Count actual ok() calls in the test file to verify plan matches
# Each CHECK, CHECK_STR, CHECK_INT macro expands to one ok() call

rg -c '\b(CHECK|CHECK_STR|CHECK_INT|ok)\s*\(' test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp

Repository: sysown/proxysql

Length of output: 62


Update plan() count to match actual test count of 135.

The test file declares plan(127) but actually executes 135 test assertions (verified by counting CHECK, CHECK_STR, CHECK_INT, and ok macro calls). This will cause a TAP plan mismatch error. Either update plan(127) to plan(135) or recount and update the individual test distribution comments (currently documenting only ~100 tests) to account for all 135 assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp` around lines 351 -
363, The TAP plan in main() is wrong: replace the call plan(127) with plan(135)
so the declared test count matches the actual 135 assertions run by
test_minimal_config(), test_full_config(), test_auth_file(), test_hba_file(),
test_malformed_config(), test_include_directive(), test_nonexistent_file(), and
test_defaults(); ensure the documented per-test comments (counts next to each
test call) are updated to reflect the correct distribution or remove stale
counts, then return exit_status() as before.

…ages 2+3) (#5564, #5565)

Stage 2 - Config Converter + CLI:
- PgBouncer_ConfigConverter: Converts parsed PgBouncer config into
  ProxySQL SQL statements with full parameter mapping
  - Databases → pgsql_servers + pgsql_query_rules (auto hostgroup assignment)
  - Multi-host entries → multiple server rows with equal weight
  - Users → pgsql_users with pool mode mapping (session→fast_forward,
    transaction→transaction_persistent, statement→defaults)
  - Globals → pgsql-* variables with unit conversion (s→ms)
  - HBA rules → pgsql_firewall_whitelist entries
  - Strict mode (default): fails on unmappable parameters
  - Relaxed mode (--ignore-warnings): warns and continues
  - Dry-run output: commented SQL with summary
- ProxySQL_CLI: Entry point for proxysql-cli binary (argv[0] detection)
  - Subcommand: import-pgbouncer <path> [--dry-run] [--ignore-warnings]
  - Never starts daemon, always exits after command
- 39 unit tests covering: minimal conversion, multi-host, wildcard
  database, pool mode mapping, global settings, strict/relaxed modes,
  query rules, dry-run format, TLS settings

Stage 3 - PgBouncer-Compatible SHOW Commands:
- PgBouncer_ShowCommands: Translates PgBouncer SHOW commands to
  equivalent ProxySQL SQL queries with exact column output format
  - SHOW POOLS, STATS, SERVERS, CLIENTS, DATABASES, USERS, CONFIG,
    VERSION, STATE, LISTS
  - SHOW EXTENDED variant adds ProxySQL-specific columns
  - Unsupported commands (DNS_HOSTS, PEERS, etc.) return clear errors
  - Case-insensitive, handles trailing semicolons and extra whitespace
- 39 unit tests covering: command recognition, case insensitivity,
  trailing semicolons, EXTENDED variant, SQL output columns,
  non-matching queries, unsupported commands
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp`:
- Around line 138-150: The current generator builds dest_db but still inserts
routing for db.name only, silently dropping db.dbname aliases; update the logic
in PgBouncer_ConfigConverter.cpp around dest_db, sql, comment and rule_id so
that if db.dbname != db.name you do not emit a misleading pgsql_query_rules
INSERT for db.name but instead surface the mapping as unmappable (e.g., add a
warning/error entry or record it in the converter's diagnostics) indicating the
alias (db.name) points to a different backend (db.dbname) until you implement
backend DB rewrite; ensure the diagnostic references the rule_id and both
db.name and db.dbname so callers can find and address these entries.
- Around line 319-345: The UPDATE statements that unconditionally set use_ssl=1
(pushed into result.entries) must be scoped to only the rows inserted by this
import; change the SQL to target only imported servers/users (e.g. add a WHERE
clause that limits to the IDs/hosts inserted or to a temporary/import marker set
during INSERT). Concretely, when creating server/user rows in
PgBouncer_ConfigConverter (the code that pushes INSERTs into result.entries),
record the identifiers (or add a transient column/value like
source='pgbouncer_import') and then replace "UPDATE pgsql_servers SET
use_ssl=1;" and the analogous users UPDATE with "UPDATE pgsql_servers SET
use_ssl=1 WHERE <identifier IN (list) OR source='pgbouncer_import'>". Apply the
same scoping change for the other block mentioned (lines ~401-410) and ensure
emit_set calls remain unchanged.
- Around line 372-463: convert_hba_rules currently appends INSERTs into
pgsql_firewall_whitelist_rules and treats HBA "reject" rules as comments which
can leave stale allow entries and lose ordered-deny semantics; change the logic
so that when processing rules in ConfigConverter::convert_hba_rules you first
emit a statement to rebuild the whitelist table (e.g. TRUNCATE or DELETE FROM
pgsql_firewall_whitelist_rules) before emitting any INSERTs, and treat any
rule.method == "reject" as a hard conversion failure (call add_issue with
strict=true or otherwise fail the conversion for that rule) instead of emitting
a comment; update the code paths that set result.entries and
result.variable_count to reflect the new delete-then-insert flow and ensure the
unique symbols mentioned (convert_hba_rules, pgsql_firewall_whitelist_rules,
rule.method == "reject") are used to locate and modify the logic.

In `@lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp`:
- Around line 71-100: The query_pools function currently cross-joins
runtime_pgsql_users (su) with stats_pgsql_connection_pool (cp) and only GROUPs
BY su.username, making cp.* columns (cp.ConnUsed, cp.ConnFree, cp.hostgroup,
etc.) nondeterministic when multiple cp rows exist; fix by either (A) adding the
pool dimension(s) to the GROUP BY (e.g., include cp.hostgroup or the pool's
unique identifier alongside su.username and ensure selected cp columns are also
grouped) or (B) replacing raw cp columns with explicit aggregates (e.g.,
SUM(cp.ConnUsed) AS sv_active, SUM(cp.ConnFree) AS sv_idle, AVG(cp.Latency_us)
AS latency_us, SUM(cp.Queries) AS Queries, SUM(cp.Bytes_data_sent) AS
Bytes_data_sent, SUM(cp.Bytes_data_recv) AS Bytes_data_recv) and keep GROUP BY
su.username; edit query_pools to implement one of these two deterministic
approaches referencing cp.ConnUsed, cp.ConnFree, cp.Latency_us, cp.Queries,
cp.Bytes_data_sent, cp.Bytes_data_recv and the GROUP BY clause.
- Around line 269-300: The parser currently accepts extra trailing tokens (e.g.,
"SHOW POOLS foo") because it only checks the command token; update the dispatch
in the SHOW handling (use symbols tokens, cmd_idx, cmd, out_query and the
query_* functions like query_pools/query_servers) to reject any extra tokens
after the command by verifying that cmd_idx + 1 == tokens.size() (or
tokens.size() == cmd_idx + 1) before calling the query_* functions and return
false if there are trailing tokens so malformed statements produce a syntax
error.

In `@lib/pgbouncer_compat/ProxySQL_CLI.cpp`:
- Around line 97-101: The loop in ProxySQL_CLI.cpp is appending an extra
semicolon to each SQL statement even though ConversionResult.entries already
contain terminated SQL; update the loop that iterates over result.entries (and
uses entry.sql) to output entry.sql as-is without adding an extra ";" (or
conditionally trim/avoid double-terminating) so the printed statements do not
become ";;".
- Around line 64-73: In the failure branch that checks result.success in
ProxySQL_CLI.cpp, stop writing the generated SQL to stdout; either omit the call
to PgBouncer::ConfigConverter::format_dry_run(...) or send its output to stderr
instead of std::cout so downstream piped commands won't receive partial SQL;
update the block that currently calls std::cout <<
PgBouncer::ConfigConverter::format_dry_run(result, config_path, strict) to not
write to stdout (use std::cerr or remove) and ensure the function still returns
1.

In `@test/tap/tests/unit/pgbouncer_converter_unit-t.cpp`:
- Around line 277-292: The TAP plan in main() is off by one: update the call to
plan(39) to reflect the actual 40 assertions run by the helper tests (change
plan(39) to plan(40)) so the plan count matches the sum of assertions executed
by test_minimal_conversion(), test_multi_host_conversion(),
test_wildcard_database(), test_user_pool_mode_mapping(), test_global_settings(),
test_strict_mode(), test_relaxed_mode(), test_query_rules(),
test_dry_run_format(), and test_tls_conversion().

In `@test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp`:
- Around line 190-203: The TAP plan in main() is wrong—plan(39) but there are 38
checks from the calls to test_show_command_recognition(),
test_case_insensitive(), test_trailing_semicolon(), test_extended_variant(),
test_sql_output_columns(), test_non_matching_queries(), and
test_unsupported_commands(); update the plan to the correct count by replacing
plan(39) with plan(38) (or adjust the individual test functions if you intend a
different total) so TAP passes when all CHECKs succeed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4785b6e6-c70b-4922-b080-368d0e961158

📥 Commits

Reviewing files that changed from the base of the PR and between a3b59b3 and eb0e6b0.

📒 Files selected for processing (10)
  • lib/Makefile
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.h
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.h
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
  • lib/pgbouncer_compat/ProxySQL_CLI.h
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
✅ Files skipped from review due to trivial changes (2)
  • lib/pgbouncer_compat/ProxySQL_CLI.h
  • lib/Makefile
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cpp,h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)
Member variables must use snake_case
Constants and macros must use UPPER_SNAKE_CASE
C++17 is required; use conditional compilation via #ifdef PROXYSQLGENAI, #ifdef PROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters

Files:

  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.h
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.h
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Use RAII for resource management and jemalloc for memory allocation

Files:

  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
test/tap/tests/unit/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests in test/tap/tests/unit/ must use test_globals.h and test_init.h and link against libproxysql.a via the custom test harness

Files:

  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
lib/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

One class per file is the typical convention

Files:

  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Admin interface uses SQLite3 backend for SQL-based configuration and schema versions are tracked in ProxySQL_Admin_Tables_Definitions.h
Learnt from: wazir-ahmed
Repo: sysown/proxysql PR: 5557
File: test/tap/tests/unit/gtid_set_unit-t.cpp:14-17
Timestamp: 2026-04-01T21:27:00.297Z
Learning: In ProxySQL's unit test directory (test/tap/tests/unit/), test_globals.h and test_init.h are only required for tests that depend on the ProxySQL runtime globals/initialization (i.e., tests that exercise components linked against libproxysql.a). Pure data-structure or utility tests (e.g., ezoption_parser_unit-t.cpp, gtid_set_unit-t.cpp, gtid_trxid_interval_unit-t.cpp) only need tap.h and the relevant project header — omitting test_globals.h and test_init.h is correct and intentional in these cases.
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to test/tap/tests/unit/**/*.cpp : Unit tests in test/tap/tests/unit/ must use test_globals.h and test_init.h and link against libproxysql.a via the custom test harness

Applied to files:

  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
📚 Learning: 2026-04-01T21:27:00.297Z
Learnt from: wazir-ahmed
Repo: sysown/proxysql PR: 5557
File: test/tap/tests/unit/gtid_set_unit-t.cpp:14-17
Timestamp: 2026-04-01T21:27:00.297Z
Learning: In ProxySQL's unit test directory (test/tap/tests/unit/), test_globals.h and test_init.h are only required for tests that depend on the ProxySQL runtime globals/initialization (i.e., tests that exercise components linked against libproxysql.a). Pure data-structure or utility tests (e.g., ezoption_parser_unit-t.cpp, gtid_set_unit-t.cpp, gtid_trxid_interval_unit-t.cpp) only need tap.h and the relevant project header — omitting test_globals.h and test_init.h is correct and intentional in these cases.

Applied to files:

  • test/tap/tests/unit/Makefile
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : C++17 is required; use conditional compilation via `#ifdef` PROXYSQLGENAI, `#ifdef` PROXYSQL31, etc. for feature flags

Applied to files:

  • test/tap/tests/unit/Makefile
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.h
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.h
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to test/tap/tests/{test_*,*-t}.cpp : Test files in test/tap/tests/ must follow the naming pattern test_*.cpp or *-t.cpp

Applied to files:

  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
📚 Learning: 2026-04-01T21:27:00.297Z
Learnt from: wazir-ahmed
Repo: sysown/proxysql PR: 5557
File: test/tap/tests/unit/gtid_set_unit-t.cpp:14-17
Timestamp: 2026-04-01T21:27:00.297Z
Learning: In ProxySQL unit tests under test/tap/tests/unit/, include test_globals.h and test_init.h only for tests that depend on ProxySQL runtime globals/initialization (i.e., tests that exercise components linked against libproxysql.a). For “pure” data-structure/utility tests (e.g., ezoption_parser_unit-t.cpp, gtid_set_unit-t.cpp, gtid_trxid_interval_unit-t.cpp) that do not require runtime globals/initialization, it is correct to omit test_globals.h and test_init.h and instead include only tap.h plus the relevant project header(s).

Applied to files:

  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
📚 Learning: 2026-01-20T09:34:19.124Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:39-48
Timestamp: 2026-01-20T09:34:19.124Z
Learning: In ProxySQL's TAP test suite, resource leaks (e.g., not calling mysql_close() on early return paths) are commonly tolerated because test processes are short-lived and OS frees resources on exit. This pattern applies to all C++ test files under test/tap/tests. When reviewing, recognize this as a project-wide test convention and focus on test correctness and isolation rather than insisting on fixing such leaks in these test files.

Applied to files:

  • test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp
  • test/tap/tests/unit/pgbouncer_converter_unit-t.cpp
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)

Applied to files:

  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.h
🪛 Clang (14.0.6)
test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp

[error] 6-6: 'tap.h' file not found

(clang-diagnostic-error)

lib/pgbouncer_compat/PgBouncer_ShowCommands.h

[error] 4-4: 'string' file not found

(clang-diagnostic-error)

test/tap/tests/unit/pgbouncer_converter_unit-t.cpp

[error] 6-6: 'tap.h' file not found

(clang-diagnostic-error)

Comment on lines +138 to +150
std::string dest_db = db.dbname.empty() ? db.name : db.dbname;

std::ostringstream sql;
sql << "INSERT INTO pgsql_query_rules "
<< "(rule_id, active, schemaname, destination_hostgroup, apply) "
<< "VALUES ("
<< rule_id << ", 1, "
<< "'" << sql_escape(db.name) << "', "
<< hg << ", 1);";

std::string comment = "Route database '" + db.name + "' to hostgroup " + std::to_string(hg);
if (db.dbname != "" && db.dbname != db.name)
comment += " (backend db: " + db.dbname + ")";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't silently drop dbname aliases.

dest_db is derived here, but the emitted rule only routes db.name to a hostgroup. For entries like analytics = host=... dbname=shared, the backend still sees analytics, so alias mappings either change behavior or fail outright. Please surface db.dbname != db.name as unmappable until the converter can actually rewrite the backend database.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp` around lines 138 - 150,
The current generator builds dest_db but still inserts routing for db.name only,
silently dropping db.dbname aliases; update the logic in
PgBouncer_ConfigConverter.cpp around dest_db, sql, comment and rule_id so that
if db.dbname != db.name you do not emit a misleading pgsql_query_rules INSERT
for db.name but instead surface the mapping as unmappable (e.g., add a
warning/error entry or record it in the converter's diagnostics) indicating the
alias (db.name) points to a different backend (db.dbname) until you implement
backend DB rewrite; ensure the diagnostic references the rule_id and both
db.name and db.dbname so callers can find and address these entries.

Comment on lines +319 to +345
// -- server_tls_sslmode: require/verify-ca/verify-full -> use_ssl=1 on servers
{
bool need_ssl = (g.server_tls_sslmode == "require" ||
g.server_tls_sslmode == "verify-ca" ||
g.server_tls_sslmode == "verify-full");
if (need_ssl) {
// Update all previously inserted server rows to use_ssl=1
result.entries.push_back({
"UPDATE pgsql_servers SET use_ssl=1;",
"PgBouncer server_tls_sslmode=" + g.server_tls_sslmode +
" -> enable SSL on all backend connections"
});

if (!g.server_tls_ca_file.empty()) {
emit_set("pgsql-ssl_p2s_ca", g.server_tls_ca_file,
"PgBouncer server_tls_ca_file -> ProxySQL pgsql-ssl_p2s_ca");
}
if (!g.server_tls_cert_file.empty()) {
emit_set("pgsql-ssl_p2s_cert", g.server_tls_cert_file,
"PgBouncer server_tls_cert_file -> ProxySQL pgsql-ssl_p2s_cert");
}
if (!g.server_tls_key_file.empty()) {
emit_set("pgsql-ssl_p2s_key", g.server_tls_key_file,
"PgBouncer server_tls_key_file -> ProxySQL pgsql-ssl_p2s_key");
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope the SSL flips to imported rows only.

These UPDATE pgsql_servers SET use_ssl=1 / UPDATE pgsql_users SET use_ssl=1 statements are only safe when the same run also rebuilt those tables. A config that only contains TLS globals or only HBA hostssl rules will mutate unrelated existing ProxySQL servers/users.

Also applies to: 401-410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp` around lines 319 - 345,
The UPDATE statements that unconditionally set use_ssl=1 (pushed into
result.entries) must be scoped to only the rows inserted by this import; change
the SQL to target only imported servers/users (e.g. add a WHERE clause that
limits to the IDs/hosts inserted or to a temporary/import marker set during
INSERT). Concretely, when creating server/user rows in PgBouncer_ConfigConverter
(the code that pushes INSERTs into result.entries), record the identifiers (or
add a transient column/value like source='pgbouncer_import') and then replace
"UPDATE pgsql_servers SET use_ssl=1;" and the analogous users UPDATE with
"UPDATE pgsql_servers SET use_ssl=1 WHERE <identifier IN (list) OR
source='pgbouncer_import'>". Apply the same scoping change for the other block
mentioned (lines ~401-410) and ensure emit_set calls remain unchanged.

Comment on lines +372 to +463
void ConfigConverter::convert_hba_rules(const Config& config,
ConversionResult& result, bool strict) {
if (config.hba_rules.empty()) return;

bool any_converted = false;

for (const auto& rule : config.hba_rules) {
// Unsupported connection types
if (rule.conn_type == "local") {
add_issue(result, strict,
"HBA rule with conn_type 'local' (Unix socket) has no ProxySQL equivalent");
continue;
}
if (rule.conn_type == "hostnossl") {
add_issue(result, strict,
"HBA rule with conn_type 'hostnossl' has no ProxySQL equivalent");
continue;
}

// Unsupported auth methods
if (rule.method == "cert" || rule.method == "peer" || rule.method == "pam" ||
rule.method == "ident" || rule.method == "gss" || rule.method == "sspi") {
add_issue(result, strict,
"HBA rule with method '" + rule.method +
"' has no ProxySQL equivalent");
continue;
}

// hostssl -> mark users for SSL
if (rule.conn_type == "hostssl") {
std::string user_clause;
if (rule.user != "all") {
user_clause = " WHERE username='" + sql_escape(rule.user) + "'";
}
result.entries.push_back({
"UPDATE pgsql_users SET use_ssl=1" + user_clause + ";",
"HBA hostssl rule -> require SSL for " +
(rule.user == "all" ? "all users" : "user '" + rule.user + "'")
});
}

// Resolve address for firewall rule
std::string addr = rule.address;
if (addr.empty() || addr == "all") addr = "0.0.0.0/0";

// reject -> whitelist deny (we still add to the whitelist table but the
// absence from the whitelist effectively blocks access when whitelist mode
// is enabled; we emit a comment explaining this)
if (rule.method == "reject") {
result.entries.push_back({
"-- HBA reject rule: " + rule.conn_type + " " + rule.database +
" " + rule.user + " " + addr + " reject",
"ProxySQL firewall whitelist is allow-only; not adding this "
"source/user means traffic from " + addr + " is implicitly denied "
"when pgsql-firewall_whitelist_enabled=1"
});
any_converted = true;
continue;
}

// host / hostssl with md5, scram-sha-256, trust -> whitelist allow
if (rule.method == "md5" || rule.method == "scram-sha-256" ||
rule.method == "trust" || rule.method == "password") {

std::string user_val = (rule.user == "all") ? "" : rule.user;
std::string db_val = (rule.database == "all") ? "" : rule.database;

std::ostringstream sql;
sql << "INSERT INTO pgsql_firewall_whitelist_rules "
<< "(active, client_address, username, schemaname, flagIN) "
<< "VALUES (1, "
<< "'" << sql_escape(addr) << "', "
<< "'" << sql_escape(user_val) << "', "
<< "'" << sql_escape(db_val) << "', "
<< "0);";

std::string comment = "HBA allow: " + rule.conn_type + " " +
rule.database + " " + rule.user + " " +
addr + " " + rule.method;

result.entries.push_back({sql.str(), comment});
any_converted = true;
}
}

if (any_converted) {
result.entries.push_back({
"SET pgsql-firewall_whitelist_enabled=1;",
"Enable ProxySQL firewall whitelist (converted from PgBouncer HBA rules)"
});
result.variable_count++;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Rebuild the whitelist from scratch and fail on reject rules.

Appending to pgsql_firewall_whitelist_rules preserves stale allow entries, and treating reject as a comment loses pg_hba.conf's ordered deny semantics. That can widen access after import.

🔒 Suggested direction
@@
     if (config.hba_rules.empty()) return;
 
     bool any_converted = false;
+    result.entries.push_back({
+        "DELETE FROM pgsql_firewall_whitelist_rules;",
+        "Remove existing firewall whitelist rules before importing"
+    });
@@
-        if (rule.method == "reject") {
-            result.entries.push_back({
-                "-- HBA reject rule: " + rule.conn_type + " " + rule.database +
-                " " + rule.user + " " + addr + " reject",
-                "ProxySQL firewall whitelist is allow-only; not adding this "
-                "source/user means traffic from " + addr + " is implicitly denied "
-                "when pgsql-firewall_whitelist_enabled=1"
-            });
-            any_converted = true;
+        if (rule.method == "reject") {
+            add_issue(result, strict,
+                      "HBA reject rules are ordered and cannot be expressed with "
+                      "ProxySQL's allow-only firewall whitelist");
             continue;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp` around lines 372 - 463,
convert_hba_rules currently appends INSERTs into pgsql_firewall_whitelist_rules
and treats HBA "reject" rules as comments which can leave stale allow entries
and lose ordered-deny semantics; change the logic so that when processing rules
in ConfigConverter::convert_hba_rules you first emit a statement to rebuild the
whitelist table (e.g. TRUNCATE or DELETE FROM pgsql_firewall_whitelist_rules)
before emitting any INSERTs, and treat any rule.method == "reject" as a hard
conversion failure (call add_issue with strict=true or otherwise fail the
conversion for that rule) instead of emitting a comment; update the code paths
that set result.entries and result.variable_count to reflect the new
delete-then-insert flow and ensure the unique symbols mentioned
(convert_hba_rules, pgsql_firewall_whitelist_rules, rule.method == "reject") are
used to locate and modify the logic.

Comment thread lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
Comment on lines +269 to +300
if (tokens.size() > 1 && tokens[1] == "EXTENDED") {
is_extended = true;
cmd_idx = 2;
}

if (cmd_idx >= tokens.size()) {
return false;
}

const std::string& cmd = tokens[cmd_idx];

if (cmd == "POOLS") {
out_query = query_pools(is_extended);
} else if (cmd == "STATS") {
out_query = query_stats(is_extended);
} else if (cmd == "SERVERS") {
out_query = query_servers(is_extended);
} else if (cmd == "CLIENTS") {
out_query = query_clients(is_extended);
} else if (cmd == "DATABASES") {
out_query = query_databases(is_extended);
} else if (cmd == "USERS") {
out_query = query_users(is_extended);
} else if (cmd == "CONFIG") {
out_query = query_config(is_extended);
} else if (cmd == "VERSION") {
out_query = query_version(is_extended);
} else if (cmd == "STATE") {
out_query = query_state(is_extended);
} else if (cmd == "LISTS") {
out_query = query_lists(is_extended);
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reject trailing tokens after the command.

SHOW POOLS foo and SHOW EXTENDED SERVERS bar currently translate as valid because only the first command token is checked. That hijacks malformed statements instead of letting the normal parser return a syntax error.

🔎 Minimal fix
-    if (cmd_idx >= tokens.size()) {
+    if (cmd_idx >= tokens.size() || cmd_idx + 1 != tokens.size()) {
         return false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp` around lines 269 - 300, The
parser currently accepts extra trailing tokens (e.g., "SHOW POOLS foo") because
it only checks the command token; update the dispatch in the SHOW handling (use
symbols tokens, cmd_idx, cmd, out_query and the query_* functions like
query_pools/query_servers) to reject any extra tokens after the command by
verifying that cmd_idx + 1 == tokens.size() (or tokens.size() == cmd_idx + 1)
before calling the query_* functions and return false if there are trailing
tokens so malformed statements produce a syntax error.

Comment thread lib/pgbouncer_compat/ProxySQL_CLI.cpp
Comment thread lib/pgbouncer_compat/ProxySQL_CLI.cpp Outdated
Comment on lines +277 to +292
int main() {
plan(39);

test_minimal_conversion(); // 6
test_multi_host_conversion(); // 5
test_wildcard_database(); // 3
test_user_pool_mode_mapping(); // 4
test_global_settings(); // 4
test_strict_mode(); // 3
test_relaxed_mode(); // 2
test_query_rules(); // 5
test_dry_run_format(); // 5
test_tls_conversion(); // 3

// Note: plan count = sum above. Adjust if needed.
return exit_status();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the TAP plan count.

The helpers execute 40 assertions, but plan(39) expects 39. This test will fail after the last check.

✅ Minimal fix
-    plan(39);
+    plan(40);
@@
-    // Note: plan count = sum above. Adjust if needed.
     return exit_status();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int main() {
plan(39);
test_minimal_conversion(); // 6
test_multi_host_conversion(); // 5
test_wildcard_database(); // 3
test_user_pool_mode_mapping(); // 4
test_global_settings(); // 4
test_strict_mode(); // 3
test_relaxed_mode(); // 2
test_query_rules(); // 5
test_dry_run_format(); // 5
test_tls_conversion(); // 3
// Note: plan count = sum above. Adjust if needed.
return exit_status();
int main() {
plan(40);
test_minimal_conversion(); // 6
test_multi_host_conversion(); // 5
test_wildcard_database(); // 3
test_user_pool_mode_mapping(); // 4
test_global_settings(); // 4
test_strict_mode(); // 3
test_relaxed_mode(); // 2
test_query_rules(); // 5
test_dry_run_format(); // 5
test_tls_conversion(); // 3
return exit_status();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/pgbouncer_converter_unit-t.cpp` around lines 277 - 292,
The TAP plan in main() is off by one: update the call to plan(39) to reflect the
actual 40 assertions run by the helper tests (change plan(39) to plan(40)) so
the plan count matches the sum of assertions executed by
test_minimal_conversion(), test_multi_host_conversion(),
test_wildcard_database(), test_user_pool_mode_mapping(), test_global_settings(),
test_strict_mode(), test_relaxed_mode(), test_query_rules(),
test_dry_run_format(), and test_tls_conversion().

Comment on lines +190 to +203
int main() {
plan(39);

test_show_command_recognition(); // 10
test_case_insensitive(); // 3
test_trailing_semicolon(); // 3
test_extended_variant(); // 4
test_sql_output_columns(); // 10
test_non_matching_queries(); // 3
test_unsupported_commands(); // 5

// Note: 10+3+3+4+10+3+5 = 38, but plan says 40.
// If count is off, adjust the plan number.
return exit_status();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the TAP plan count.

There are 38 CHECK(...) assertions in this file, but the test announces 39. TAP will report failure even if every assertion passes.

✅ Minimal fix
-    plan(39);
+    plan(38);
@@
-    // Note: 10+3+3+4+10+3+5 = 38, but plan says 40.
-    // If count is off, adjust the plan number.
     return exit_status();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int main() {
plan(39);
test_show_command_recognition(); // 10
test_case_insensitive(); // 3
test_trailing_semicolon(); // 3
test_extended_variant(); // 4
test_sql_output_columns(); // 10
test_non_matching_queries(); // 3
test_unsupported_commands(); // 5
// Note: 10+3+3+4+10+3+5 = 38, but plan says 40.
// If count is off, adjust the plan number.
return exit_status();
int main() {
plan(38);
test_show_command_recognition(); // 10
test_case_insensitive(); // 3
test_trailing_semicolon(); // 3
test_extended_variant(); // 4
test_sql_output_columns(); // 10
test_non_matching_queries(); // 3
test_unsupported_commands(); // 5
return exit_status();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpp` around lines 190 -
203, The TAP plan in main() is wrong—plan(39) but there are 38 checks from the
calls to test_show_command_recognition(), test_case_insensitive(),
test_trailing_semicolon(), test_extended_variant(), test_sql_output_columns(),
test_non_matching_queries(), and test_unsupported_commands(); update the plan to
the correct count by replacing plan(39) with plan(38) (or adjust the individual
test functions if you intend a different total) so TAP passes when all CHECKs
succeed.

Integration of PgBouncer migration tooling into ProxySQL core:

1. Admin command (Admin_Handler.cpp):
   - IMPORT PGBOUNCER CONFIG FROM '/path' [DRY RUN] [IGNORE WARNINGS]
   - Parses PgBouncer config, converts to SQL, executes against admin DB
   - DRY RUN returns converted SQL as a result set
   - Strict mode (default) fails on unmappable parameters
   - IGNORE WARNINGS allows partial conversion

2. PgBouncer SHOW commands (Admin_Handler.cpp):
   - Intercepts SHOW POOLS/STATS/SERVERS/CLIENTS/DATABASES/USERS/CONFIG/
     VERSION/STATE/LISTS on PgSQL admin port
   - Translates to equivalent ProxySQL SQL with PgBouncer-exact columns
   - SHOW EXTENDED variant adds ProxySQL-specific columns
   - Unsupported commands return descriptive error messages
   - Only active on PgSQL sessions (if constexpr guard)

3. proxysql-cli mode (main.cpp):
   - Detects argv[0] == "proxysql-cli" at start of main()
   - Routes to proxysql_cli_main() which never starts the daemon
   - Symlink: ln -s proxysql proxysql-cli

4. Headers copied to include/ following ProxySQL convention.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
include/PgBouncer_HBAParser.h (1)

1-2: Use the repository-standard public header guard.

This guard does not match the required __CLASS_*_H pattern for headers under include/.

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_HBAParser.h` around lines 1 - 2, The header guard macro
PGBOUNCER_HBA_PARSER_H does not follow the project standard; replace it with the
repository-standard pattern for public headers (use the __CLASS_*_H form) so the
include guard matches the guideline. Update the `#ifndef/`#define/#endif trio in
include/PgBouncer_HBAParser.h to use a guard like __CLASS_PGBOUNCER_HBA_PARSER_H
(or the exact class-based name required by the repo), ensuring the symbol is
used consistently in the `#ifndef`, `#define` and trailing `#endif`.
include/PgBouncer_ConfigParser.h (1)

1-2: Use the repository-standard public header guard.

This guard does not match the required __CLASS_*_H pattern for headers under include/.

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_ConfigParser.h` around lines 1 - 2, The header guard macro
in PgBouncer_ConfigParser.h (currently PGBOUNCER_CONFIG_PARSER_H) doesn't follow
the repository standard __CLASS_*_H pattern; rename the macro to the standard
form (e.g., __CLASS_PGBOUNCER_CONFIG_PARSER_H) and update both the `#ifndef` and
`#define` (and the matching `#endif` comment if present) so the include guard around
the contents of the header and the symbol referenced by class/function
declarations (e.g., PgBouncer_ConfigParser) use the new macro consistently.
include/PgBouncer_ConfigConverter.h (1)

1-2: Use the repository-standard public header guard.

This guard does not match the required __CLASS_*_H pattern for headers under include/.

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_ConfigConverter.h` around lines 1 - 2, The header guard in
PgBouncer_ConfigConverter.h uses PGBOUNCER_CONFIG_CONVERTER_H but must follow
the repo standard `#ifndef` __CLASS_*_H pattern; change the guard macro to
__CLASS_PgBouncer_ConfigConverter_H (update both the `#ifndef` and `#define`) and
update the trailing `#endif` comment if present so the file consistently uses
__CLASS_PgBouncer_ConfigConverter_H as the include guard.
include/PgBouncer_ShowCommands.h (1)

1-2: Use the repository-standard public header guard.

This guard does not match the required __CLASS_*_H pattern for headers under include/.

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_ShowCommands.h` around lines 1 - 2, Replace the
non-conforming header guard PGBOUNCER_SHOW_COMMANDS_H with the
repository-standard pattern by renaming the macro to
__CLASS_PGBOUNCER_SHOWCOMMANDS_H (or __CLASS_PGBOUNCER_SHOW_COMMANDS_H if
underscores desired consistently) in the `#ifndef` and `#define` lines and update
the matching `#endif` comment; ensure the new guard uses the __CLASS_*_H format
required by the include/* headers (update both occurrences of the old macro to
the new one).
include/PgBouncer_AuthFileParser.h (1)

1-2: Use the repository-standard public header guard.

This guard does not match the required __CLASS_*_H pattern for headers under include/.

As per coding guidelines, include/**/*.{h,hpp}: Include guards must follow the pattern #ifndef __CLASS_*_H.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_AuthFileParser.h` around lines 1 - 2, The header guard
macro PGBOUNCER_AUTH_FILE_PARSER_H does not follow the repo standard; replace
both occurrences of PGBOUNCER_AUTH_FILE_PARSER_H in
include/PgBouncer_AuthFileParser.h (the `#ifndef` and `#define`) with a guard
following the __CLASS_*_H pattern, e.g. __CLASS_PGBOUNCER_AUTHFILEPARSER_H, and
update the matching `#endif` comment if present to reference the new macro.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/PgBouncer_AuthFileParser.h`:
- Around line 29-30: detect_password_type incorrectly returns AuthType::MD5 from
inside the hex-validation loop, accepting strings where only the first hex
character is valid; fix detect_password_type in PgBouncer_AuthFileParser.cpp to
(1) first check the overall format (must start with "md5" and have exact length
35), (2) then validate that every character in the 32-character suffix
(positions after "md5") is a hex digit without returning early inside the loop,
and (3) only return AuthType::MD5 after the full suffix has been validated;
otherwise return the appropriate non-MD5 AuthType or an error.

In `@include/PgBouncer_ConfigConverter.h`:
- Around line 45-46: convert_users() currently strips AuthFileEntry::type when
mapping parsed user entries to the ConversionResult, losing SCRAM/MD5 metadata;
update the conversion so it preserves the original AuthFileEntry::type (copy the
type field from the parsed user entry) into the corresponding user record placed
into ConversionResult (ensure the ConversionResult user struct has/uses a type
field), modifying convert_users() in
lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp and any related mapping code
so downstream consumers can distinguish plaintext vs MD5 vs SCRAM entries.

In `@lib/Admin_Handler.cpp`:
- Around line 5372-5377: The loop applying conversion statements (iterating
result.entries and calling SPA->admindb->execute(entry.sql.c_str())) must be
made atomic and abort on first SQL error: begin a transaction (e.g., execute
"BEGIN"), execute each entry.sql in order, check the return/throw from
SPA->admindb->execute and if any statement fails immediately ROLLBACK the
transaction and surface/log the failing SQL (include entry.sql and the error),
otherwise COMMIT after all succeed; update the code around the loop to perform
BEGIN before it, per-statement error handling that triggers ROLLBACK on failure
and returns/propagates the error, and a final COMMIT on success.
- Around line 5393-5415: The PgBouncer-specific SHOW handling
(PgBouncer::get_unsupported_show_message and PgBouncer::translate_show_command)
must run before the generic MySQL-style SHOW rewrites so SHOW DATABASES and
similar PgBouncer commands are translated for PgSQL_Session; move the entire
PgBouncer block (the if constexpr branch that calls get_unsupported_show_message
and translate_show_command and performs l_free/l_strdup on query) to execute
earlier, before the generic SHOW rewrite code paths that currently rewrite SHOW
DATABASES (so that query_no_space/query_no_space_length is checked/translated by
PgBouncer::translate_show_command first), preserving the existing error send
(SPA->send_error_msg_to_client), run_query flag setting, and the goto
__run_query behavior.
- Around line 5302-5317: The code currently strips "DRY RUN" and "IGNORE
WARNINGS" by substring which mangles quoted paths; instead detect these flags
only if they appear as trailing tokens in cmd_rest. Update the logic around
cmd_rest/dry_run/ignore_warnings to: trim trailing whitespace, perform a
case-insensitive check that the end of cmd_rest matches the whole flag sequence
(e.g. ends-with "DRY RUN" or "IGNORE WARNINGS") or tokenize cmd_rest respecting
quotes and check the last one or two tokens for the flags, set
dry_run/ignore_warnings and remove only those trailing tokens from cmd_rest;
ensure you use the same identifiers (cmd_rest, dry_run, ignore_warnings) and
preserve quoted paths when modifying cmd_rest.
- Around line 5294-5390: This IMPORT PGBOUNCER CONFIG handling path mutates
SPA->admindb but is not restricted to admin sessions; add a privilege check at
the start of this block to reject non-admin/stats sessions. Specifically, before
parsing or using SPA->admindb, call the same session-admin check used in the
nearby stats-session rejection (reuse the existing helper or condition that
rejects stats sessions), and if the check fails, call
SPA->send_error_msg_to_client(sess, ...) to return a permission error, free
path_buf if allocated, set run_query = false and goto __run_query; reference
variables/functions: sess, SPA->admindb, SPA->send_error_msg_to_client, and the
existing stats-session rejection logic to copy the exact check and message.

---

Nitpick comments:
In `@include/PgBouncer_AuthFileParser.h`:
- Around line 1-2: The header guard macro PGBOUNCER_AUTH_FILE_PARSER_H does not
follow the repo standard; replace both occurrences of
PGBOUNCER_AUTH_FILE_PARSER_H in include/PgBouncer_AuthFileParser.h (the `#ifndef`
and `#define`) with a guard following the __CLASS_*_H pattern, e.g.
__CLASS_PGBOUNCER_AUTHFILEPARSER_H, and update the matching `#endif` comment if
present to reference the new macro.

In `@include/PgBouncer_ConfigConverter.h`:
- Around line 1-2: The header guard in PgBouncer_ConfigConverter.h uses
PGBOUNCER_CONFIG_CONVERTER_H but must follow the repo standard `#ifndef`
__CLASS_*_H pattern; change the guard macro to
__CLASS_PgBouncer_ConfigConverter_H (update both the `#ifndef` and `#define`) and
update the trailing `#endif` comment if present so the file consistently uses
__CLASS_PgBouncer_ConfigConverter_H as the include guard.

In `@include/PgBouncer_ConfigParser.h`:
- Around line 1-2: The header guard macro in PgBouncer_ConfigParser.h (currently
PGBOUNCER_CONFIG_PARSER_H) doesn't follow the repository standard __CLASS_*_H
pattern; rename the macro to the standard form (e.g.,
__CLASS_PGBOUNCER_CONFIG_PARSER_H) and update both the `#ifndef` and `#define` (and
the matching `#endif` comment if present) so the include guard around the contents
of the header and the symbol referenced by class/function declarations (e.g.,
PgBouncer_ConfigParser) use the new macro consistently.

In `@include/PgBouncer_HBAParser.h`:
- Around line 1-2: The header guard macro PGBOUNCER_HBA_PARSER_H does not follow
the project standard; replace it with the repository-standard pattern for public
headers (use the __CLASS_*_H form) so the include guard matches the guideline.
Update the `#ifndef/`#define/#endif trio in include/PgBouncer_HBAParser.h to use a
guard like __CLASS_PGBOUNCER_HBA_PARSER_H (or the exact class-based name
required by the repo), ensuring the symbol is used consistently in the `#ifndef`,
`#define` and trailing `#endif`.

In `@include/PgBouncer_ShowCommands.h`:
- Around line 1-2: Replace the non-conforming header guard
PGBOUNCER_SHOW_COMMANDS_H with the repository-standard pattern by renaming the
macro to __CLASS_PGBOUNCER_SHOWCOMMANDS_H (or __CLASS_PGBOUNCER_SHOW_COMMANDS_H
if underscores desired consistently) in the `#ifndef` and `#define` lines and update
the matching `#endif` comment; ensure the new guard uses the __CLASS_*_H format
required by the include/* headers (update both occurrences of the old macro to
the new one).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3770a0f-0fd6-4502-82d8-fc1cb1022493

📥 Commits

Reviewing files that changed from the base of the PR and between eb0e6b0 and 77fe2ad.

📒 Files selected for processing (8)
  • include/PgBouncer_AuthFileParser.h
  • include/PgBouncer_ConfigConverter.h
  • include/PgBouncer_ConfigParser.h
  • include/PgBouncer_HBAParser.h
  • include/PgBouncer_ShowCommands.h
  • include/ProxySQL_CLI.h
  • lib/Admin_Handler.cpp
  • src/main.cpp
✅ Files skipped from review due to trivial changes (1)
  • include/ProxySQL_CLI.h
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cpp,h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)
Member variables must use snake_case
Constants and macros must use UPPER_SNAKE_CASE
C++17 is required; use conditional compilation via #ifdef PROXYSQLGENAI, #ifdef PROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters

Files:

  • src/main.cpp
  • include/PgBouncer_ShowCommands.h
  • include/PgBouncer_HBAParser.h
  • include/PgBouncer_ConfigParser.h
  • include/PgBouncer_AuthFileParser.h
  • lib/Admin_Handler.cpp
  • include/PgBouncer_ConfigConverter.h
**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Use RAII for resource management and jemalloc for memory allocation

Files:

  • src/main.cpp
  • lib/Admin_Handler.cpp
include/**/*.{h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Include guards must follow the pattern #ifndef _CLASS*_H

Files:

  • include/PgBouncer_ShowCommands.h
  • include/PgBouncer_HBAParser.h
  • include/PgBouncer_ConfigParser.h
  • include/PgBouncer_AuthFileParser.h
  • include/PgBouncer_ConfigConverter.h
lib/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

One class per file is the typical convention

Files:

  • lib/Admin_Handler.cpp
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Admin interface uses SQLite3 backend for SQL-based configuration and schema versions are tracked in ProxySQL_Admin_Tables_Definitions.h
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : C++17 is required; use conditional compilation via `#ifdef` PROXYSQLGENAI, `#ifdef` PROXYSQL31, etc. for feature flags

Applied to files:

  • src/main.cpp
  • include/PgBouncer_ShowCommands.h
  • lib/Admin_Handler.cpp
📚 Learning: 2026-01-20T07:40:34.938Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:24-28
Timestamp: 2026-01-20T07:40:34.938Z
Learning: In ProxySQL test files, calling `mysql_error(NULL)` after `mysql_init()` failure is safe because the MariaDB client library implementation returns an empty string for NULL handles (not undefined behavior).

Applied to files:

  • src/main.cpp
🪛 Clang (14.0.6)
include/PgBouncer_ShowCommands.h

[error] 4-4: 'string' file not found

(clang-diagnostic-error)

🔇 Additional comments (2)
src/main.cpp (2)

2682-2683: LGTM!

Forward declaration for the CLI entrypoint is appropriately placed before main() to enable the early dispatch.


2687-2700: No action needed — pointer comparison is well-defined.

The comparison last_bsep > last_sep is not undefined behavior. Both pointers originate from strrchr() on the same string (binary_name) or are NULL. Per the C++ standard, comparing pointers derived from the same object (or with nullptr) is well-defined. The short-circuit evaluation (if (last_bsep && ...) ensures last_bsep is non-NULL before the comparison, making the operation safe regardless of last_sep's value.

			> Likely an incorrect or invalid review comment.

Comment on lines +29 to +30
// Detect password type from the raw password string
static AuthType detect_password_type(const std::string& password);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The MD5 classifier currently accepts malformed hashes.

The implementation behind detect_password_type() in lib/pgbouncer_compat/PgBouncer_AuthFileParser.cpp returns AuthType::MD5 from inside the hex-validation loop, so any md5 string with only the first hex character valid is accepted. That makes malformed userlist.txt entries parse as valid instead of surfacing an error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_AuthFileParser.h` around lines 29 - 30,
detect_password_type incorrectly returns AuthType::MD5 from inside the
hex-validation loop, accepting strings where only the first hex character is
valid; fix detect_password_type in PgBouncer_AuthFileParser.cpp to (1) first
check the overall format (must start with "md5" and have exact length 35), (2)
then validate that every character in the 32-character suffix (positions after
"md5") is a hex digit without returning early inside the loop, and (3) only
return AuthType::MD5 after the full suffix has been validated; otherwise return
the appropriate non-MD5 AuthType or an error.

Comment on lines +45 to +46
void convert_databases(const Config& config, ConversionResult& result);
void convert_users(const Config& config, ConversionResult& result);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Keep AuthFileEntry::type through user conversion.

convert_users() in lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp currently keeps only username -> password and drops the parsed auth type, so SCRAM and MD5 entries become indistinguishable from plaintext at the conversion boundary. That makes non-plain userlist entries impossible to handle correctly downstream.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/PgBouncer_ConfigConverter.h` around lines 45 - 46, convert_users()
currently strips AuthFileEntry::type when mapping parsed user entries to the
ConversionResult, losing SCRAM/MD5 metadata; update the conversion so it
preserves the original AuthFileEntry::type (copy the type field from the parsed
user entry) into the corresponding user record placed into ConversionResult
(ensure the ConversionResult user struct has/uses a type field), modifying
convert_users() in lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp and any
related mapping code so downstream consumers can distinguish plaintext vs MD5 vs
SCRAM entries.

Comment thread lib/Admin_Handler.cpp
Comment thread lib/Admin_Handler.cpp Outdated
Comment thread lib/Admin_Handler.cpp Outdated
Comment thread lib/Admin_Handler.cpp
Comment on lines +5393 to +5415
// =========================================================================
// PgBouncer-compatible SHOW commands (PgSQL admin port only)
// =========================================================================
if constexpr (std::is_same_v<S, PgSQL_Session>) {
// Check for unsupported PgBouncer SHOW commands first
std::string unsupported_msg = PgBouncer::get_unsupported_show_message(query_no_space, query_no_space_length);
if (!unsupported_msg.empty()) {
SPA->send_error_msg_to_client(sess, (char *)unsupported_msg.c_str());
run_query = false;
goto __run_query;
}

// Try to translate PgBouncer SHOW commands
std::string translated_query;
bool is_extended = false;
if (PgBouncer::translate_show_command(query_no_space, query_no_space_length,
translated_query, is_extended)) {
l_free(query_length, query);
query = l_strdup(translated_query.c_str());
query_length = strlen(query) + 1;
goto __run_query;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Run PgBouncer SHOW translation before the generic SHOW rewrites.

This block is reached only after the existing MySQL-style SHOW handlers. SHOW DATABASES is already rewritten at Lines 5213-5221, so it never hits translate_show_command() on a PgSQL admin session and returns the wrong result shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/Admin_Handler.cpp` around lines 5393 - 5415, The PgBouncer-specific SHOW
handling (PgBouncer::get_unsupported_show_message and
PgBouncer::translate_show_command) must run before the generic MySQL-style SHOW
rewrites so SHOW DATABASES and similar PgBouncer commands are translated for
PgSQL_Session; move the entire PgBouncer block (the if constexpr branch that
calls get_unsupported_show_message and translate_show_command and performs
l_free/l_strdup on query) to execute earlier, before the generic SHOW rewrite
code paths that currently rewrite SHOW DATABASES (so that
query_no_space/query_no_space_length is checked/translated by
PgBouncer::translate_show_command first), preserving the existing error send
(SPA->send_error_msg_to_client), run_query flag setting, and the goto
__run_query behavior.

Lightweight CI that builds and runs the three standalone PgBouncer
compatibility unit tests (205 tests total) on every push to the
feature branch and on PRs touching pgbouncer_compat files.

No Docker or backend infrastructure needed — tests are standalone
with zero ProxySQL runtime dependencies.
Critical fixes:
- Admin_Handler: Gate IMPORT PGBOUNCER CONFIG to admin sessions only
  (stats connections can no longer mutate config)
- Admin_Handler: Wrap import execution in BEGIN/ROLLBACK for atomicity
  (partial failures no longer leave inconsistent state)
- Admin_Handler: Parse DRY RUN/IGNORE WARNINGS as trailing tokens after
  quoted path (paths containing those strings no longer break parsing)
- ProxySQL_CLI: Never write SQL to stdout on failed conversion
  (prevents partial SQL from being piped to mysql in error cases)

Important fixes:
- ConfigParser: Add INT_MIN/INT_MAX range checking in parse_int()
  and UINT_MAX checking in parse_uint() to prevent overflow
- ConfigParser: Detect unterminated single quotes in connection string
  pairs and report parse error instead of silently accepting
- ConfigParser: Include <climits> for range check constants
- ConfigParser: Set file field in "cannot open" error message
- ConfigConverter: DELETE FROM pgsql_firewall_whitelist_rules before
  importing HBA rules (prevents stale entries)
- ShowCommands: Fix SHOW POOLS query to avoid cross-join that produced
  nondeterministic results with multiple pool rows
- ProxySQL_CLI: Remove duplicate semicolons in non-dry-run output
- Admin_Handler: Fix admindb->execute() return type (bool not int)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
lib/pgbouncer_compat/ProxySQL_CLI.cpp (2)

119-120: Quote config_path in the suggested shell command.

The printed example can break for paths with spaces/shell metacharacters when users copy-paste it.

One simple option
-              << "  proxysql-cli import-pgbouncer " << config_path
+              << "  proxysql-cli import-pgbouncer '" << config_path << "'"
               << " | mysql -h 127.0.0.1 -P 6032 -u admin -p\n";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/ProxySQL_CLI.cpp` around lines 119 - 120, The
help/example line prints a shell command using the variable config_path without
quoting, which breaks for paths with spaces or special chars; update the output
in ProxySQL_CLI.cpp where the stream appends config_path to emit a
quoted/escaped path (e.g., wrap it in double quotes by writing '"' <<
config_path << '"' or use std::quoted(config_path) from <iomanip>) so the
printed command is safe to copy-paste in a shell.

25-33: Treat option-like argv[2] as missing required path.

import-pgbouncer --dry-run is currently interpreted as config_path="--dry-run", which produces a parse error instead of immediate CLI usage feedback.

Suggested hardening
-    if (argc < 3) {
+    if (argc < 3 || (argv[2] != nullptr && std::strncmp(argv[2], "--", 2) == 0)) {
         std::cerr << "Error: import-pgbouncer requires a config file path.\n\n";
         print_usage();
         return 1;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/pgbouncer_compat/ProxySQL_CLI.cpp` around lines 25 - 33, The code
currently treats argv[2] as the required config_path unconditionally, so
invoking import-pgbouncer --dry-run sets config_path to "--dry-run" instead of
flagging the missing path; modify the argument validation in main (the argc/argv
handling around config_path, dry_run, ignore_warnings) to detect option-like
values (e.g., argv[2] starts with '-' or is equal to "--dry-run"/"-d"/other
flags) and treat them as a missing config path: if argv[2] begins with '-' then
print the usage/error and return 1 (or alternatively parse flags first using
getopt/arg parsing before assigning config_path) so that config_path is only set
when a non-option argument is provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/pgbouncer_compat/ProxySQL_CLI.cpp`:
- Around line 119-120: The help/example line prints a shell command using the
variable config_path without quoting, which breaks for paths with spaces or
special chars; update the output in ProxySQL_CLI.cpp where the stream appends
config_path to emit a quoted/escaped path (e.g., wrap it in double quotes by
writing '"' << config_path << '"' or use std::quoted(config_path) from
<iomanip>) so the printed command is safe to copy-paste in a shell.
- Around line 25-33: The code currently treats argv[2] as the required
config_path unconditionally, so invoking import-pgbouncer --dry-run sets
config_path to "--dry-run" instead of flagging the missing path; modify the
argument validation in main (the argc/argv handling around config_path, dry_run,
ignore_warnings) to detect option-like values (e.g., argv[2] starts with '-' or
is equal to "--dry-run"/"-d"/other flags) and treat them as a missing config
path: if argv[2] begins with '-' then print the usage/error and return 1 (or
alternatively parse flags first using getopt/arg parsing before assigning
config_path) so that config_path is only set when a non-option argument is
provided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68da265e-e413-4f40-a1ba-033ee22035e1

📥 Commits

Reviewing files that changed from the base of the PR and between 4871acc and 3f1027b.

📒 Files selected for processing (5)
  • lib/Admin_Handler.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
✅ Files skipped from review due to trivial changes (2)
  • lib/Admin_Handler.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigConverter.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/pgbouncer_compat/PgBouncer_ShowCommands.cpp
  • lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,h,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{cpp,h,hpp}: Class names must use PascalCase with protocol prefixes (MySQL_, PgSQL_, ProxySQL_)
Member variables must use snake_case
Constants and macros must use UPPER_SNAKE_CASE
C++17 is required; use conditional compilation via #ifdef PROXYSQLGENAI, #ifdef PROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters

Files:

  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

Use RAII for resource management and jemalloc for memory allocation

Files:

  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
lib/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

One class per file is the typical convention

Files:

  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Admin interface uses SQLite3 backend for SQL-based configuration and schema versions are tracked in ProxySQL_Admin_Tables_Definitions.h
📚 Learning: 2026-03-22T14:38:16.093Z
Learnt from: CR
Repo: sysown/proxysql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-22T14:38:16.093Z
Learning: Applies to **/*.{cpp,h,hpp} : C++17 is required; use conditional compilation via `#ifdef` PROXYSQLGENAI, `#ifdef` PROXYSQL31, etc. for feature flags

Applied to files:

  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
📚 Learning: 2026-03-26T16:39:02.446Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5548
File: lib/mysql_connection.cpp:1837-1843
Timestamp: 2026-03-26T16:39:02.446Z
Learning: In ProxySQL's lib/mysql_connection.cpp, `SHOW WARNINGS` detection for both `update_warning_count_from_connection()` and the `add_eof()` call in `ASYNC_USE_RESULT_CONT` intentionally uses `myds->sess->CurrentQuery.QueryParserArgs.digest_text` (comment-stripped digest text). This means the fix/feature does not work when `mysql-query_digests_keep_comment=1` (digest_text contains comments) or `mysql-query_digests=0` (digest_text is unavailable) — these configurations are explicitly excluded from the regression test for `reg_test_5306-show_warnings_with_comment-t`. This design is consistent across the codebase and is an accepted, documented limitation.

Applied to files:

  • lib/pgbouncer_compat/ProxySQL_CLI.cpp
🔇 Additional comments (2)
lib/pgbouncer_compat/ProxySQL_CLI.cpp (2)

64-75: Good safety fix in failure path output routing.

Failed conversion output stays on stderr (and only in --dry-run), which avoids leaking SQL into piped stdout workflows.


99-104: Good fix: SQL entries are emitted without extra statement terminators.

Printing entry.sql directly avoids producing ;; in non-dry-run output.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PgBouncer migration tooling: Stage 1 - Config parser library

2 participants