feat: PgBouncer migration tooling - Stage 1: Config parser library#5566
feat: PgBouncer migration tooling - Stage 1: Config parser library#5566renecannao wants to merge 6 commits intov3.0from
Conversation
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).
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
| #include "PgBouncer_ConfigParser.h" | ||
|
|
||
| #include <fstream> | ||
| #include <sstream> | ||
| #include <algorithm> | ||
| #include <cctype> |
There was a problem hiding this comment.
Add <limits> to support range checking in integer parsing functions.
| #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> |
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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;
}
}| #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) |
There was a problem hiding this comment.
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).
| #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) |
| 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)); | ||
| } | ||
| } |
There was a problem hiding this comment.
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));
}| if (in_quotes) { | ||
| if (c == '"') { | ||
| in_quotes = false; | ||
| } else { | ||
| token += c; | ||
| } |
There was a problem hiding this comment.
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;
}There was a problem hiding this comment.
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.hplus parsers forpgbouncer.ini,userlist.txt, andpg_hba.conf. - Adds TAP unit tests and fixture files covering expected and malformed inputs.
- Integrates the new objects into
lib/Makefileand registers a standalone unit-test target intest/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.
| 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; |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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 { |
There was a problem hiding this comment.
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.
| { | ||
| std::ifstream ifs(filepath); | ||
| if (!ifs.is_open()) { | ||
| config.errors.push_back({"", 0, "cannot open file: " + filepath}); |
There was a problem hiding this comment.
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.
| config.errors.push_back({"", 0, "cannot open file: " + filepath}); | |
| config.errors.push_back({filepath, 0, "cannot open file: " + filepath}); |
| 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); | ||
| } |
There was a problem hiding this comment.
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).
| 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; |
There was a problem hiding this comment.
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).
| 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; | ||
|
|
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
lib/pgbouncer_compat/PgBouncer_ConfigParser.cpp (2)
536-542: Inconsistent path separator handling for relative%includepaths.Line 520 correctly handles both
/and\\when extractingbase_dir, but line 540 only checks for/when determining if a path is absolute. On Windows, paths likeC:\path\file.iniwould 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 onlongtointcast.
std::stolreturnslongwhich is 64-bit on many platforms. The cast tointcan 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
📒 Files selected for processing (17)
include/PgBouncer_Config.hlib/Makefilelib/pgbouncer_compat/PgBouncer_AuthFileParser.cpplib/pgbouncer_compat/PgBouncer_AuthFileParser.hlib/pgbouncer_compat/PgBouncer_ConfigParser.cpplib/pgbouncer_compat/PgBouncer_ConfigParser.hlib/pgbouncer_compat/PgBouncer_HBAParser.cpplib/pgbouncer_compat/PgBouncer_HBAParser.htest/tap/tests/unit/Makefiletest/tap/tests/unit/fixtures/pgbouncer_compat/full.initest/tap/tests/unit/fixtures/pgbouncer_compat/include_databases.initest/tap/tests/unit/fixtures/pgbouncer_compat/include_main.initest/tap/tests/unit/fixtures/pgbouncer_compat/malformed.initest/tap/tests/unit/fixtures/pgbouncer_compat/minimal.initest/tap/tests/unit/fixtures/pgbouncer_compat/pg_hba.conftest/tap/tests/unit/fixtures/pgbouncer_compat/userlist.txttest/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#ifdefPROXYSQLGENAI,#ifdefPROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters
Files:
lib/pgbouncer_compat/PgBouncer_AuthFileParser.hlib/pgbouncer_compat/PgBouncer_HBAParser.hlib/pgbouncer_compat/PgBouncer_ConfigParser.hlib/pgbouncer_compat/PgBouncer_AuthFileParser.cpptest/tap/tests/unit/pgbouncer_config_parser_unit-t.cpplib/pgbouncer_compat/PgBouncer_ConfigParser.cpplib/pgbouncer_compat/PgBouncer_HBAParser.cppinclude/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.cpptest/tap/tests/unit/pgbouncer_config_parser_unit-t.cpplib/pgbouncer_compat/PgBouncer_ConfigParser.cpplib/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.cpplib/pgbouncer_compat/PgBouncer_ConfigParser.cpplib/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/Makefiletest/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/Makefiletest/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/Makefiletest/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/Makefiletest/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
%includedatabase 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
%includeresolution 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.his resolved via Makefile include path.The Clang error about
tap.hnot 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.oandtap_noise_stubs.o(nolibproxysql.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-quotedconnect_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). TheMAX_INCLUDE_DEPTHconstant 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_ == 0check ensures auth/HBA files are resolved only once after the main parse completes, avoiding redundant parsing during nested%includeprocessing.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.
| #ifndef PGBOUNCER_CONFIG_H | ||
| #define PGBOUNCER_CONFIG_H |
There was a problem hiding this comment.
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_HAs 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.
| 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); |
There was a problem hiding this comment.
🛠️ 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 { |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
🧩 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.cppRepository: 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
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
lib/Makefilelib/pgbouncer_compat/PgBouncer_ConfigConverter.cpplib/pgbouncer_compat/PgBouncer_ConfigConverter.hlib/pgbouncer_compat/PgBouncer_ShowCommands.cpplib/pgbouncer_compat/PgBouncer_ShowCommands.hlib/pgbouncer_compat/ProxySQL_CLI.cpplib/pgbouncer_compat/ProxySQL_CLI.htest/tap/tests/unit/Makefiletest/tap/tests/unit/pgbouncer_converter_unit-t.cpptest/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#ifdefPROXYSQLGENAI,#ifdefPROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters
Files:
test/tap/tests/unit/pgbouncer_show_commands_unit-t.cpplib/pgbouncer_compat/PgBouncer_ShowCommands.htest/tap/tests/unit/pgbouncer_converter_unit-t.cpplib/pgbouncer_compat/PgBouncer_ShowCommands.cpplib/pgbouncer_compat/PgBouncer_ConfigConverter.hlib/pgbouncer_compat/ProxySQL_CLI.cpplib/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.cpptest/tap/tests/unit/pgbouncer_converter_unit-t.cpplib/pgbouncer_compat/PgBouncer_ShowCommands.cpplib/pgbouncer_compat/ProxySQL_CLI.cpplib/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.cpptest/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.cpplib/pgbouncer_compat/ProxySQL_CLI.cpplib/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/Makefiletest/tap/tests/unit/pgbouncer_show_commands_unit-t.cpptest/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/Makefilelib/pgbouncer_compat/PgBouncer_ShowCommands.htest/tap/tests/unit/pgbouncer_converter_unit-t.cpplib/pgbouncer_compat/PgBouncer_ConfigConverter.hlib/pgbouncer_compat/ProxySQL_CLI.cpplib/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/Makefiletest/tap/tests/unit/pgbouncer_show_commands_unit-t.cpptest/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.cpptest/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.cpptest/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)
| 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 + ")"; |
There was a problem hiding this comment.
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.
| // -- 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"); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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++; | ||
| } |
There was a problem hiding this comment.
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.
| 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 { |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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.
| 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().
| 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(); |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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_*_Hpattern for headers underinclude/.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_*_Hpattern for headers underinclude/.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_*_Hpattern for headers underinclude/.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_*_Hpattern for headers underinclude/.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_*_Hpattern for headers underinclude/.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
📒 Files selected for processing (8)
include/PgBouncer_AuthFileParser.hinclude/PgBouncer_ConfigConverter.hinclude/PgBouncer_ConfigParser.hinclude/PgBouncer_HBAParser.hinclude/PgBouncer_ShowCommands.hinclude/ProxySQL_CLI.hlib/Admin_Handler.cppsrc/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#ifdefPROXYSQLGENAI,#ifdefPROXYSQL31, etc. for feature flags
Use pthread mutexes for synchronization and std::atomic<> for counters
Files:
src/main.cppinclude/PgBouncer_ShowCommands.hinclude/PgBouncer_HBAParser.hinclude/PgBouncer_ConfigParser.hinclude/PgBouncer_AuthFileParser.hlib/Admin_Handler.cppinclude/PgBouncer_ConfigConverter.h
**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
Use RAII for resource management and jemalloc for memory allocation
Files:
src/main.cpplib/Admin_Handler.cpp
include/**/*.{h,hpp}
📄 CodeRabbit inference engine (CLAUDE.md)
Include guards must follow the pattern
#ifndef_CLASS*_H
Files:
include/PgBouncer_ShowCommands.hinclude/PgBouncer_HBAParser.hinclude/PgBouncer_ConfigParser.hinclude/PgBouncer_AuthFileParser.hinclude/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.cppinclude/PgBouncer_ShowCommands.hlib/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_sepis not undefined behavior. Both pointers originate fromstrrchr()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 && ...)ensureslast_bsepis non-NULL before the comparison, making the operation safe regardless oflast_sep's value.> Likely an incorrect or invalid review comment.
| // Detect password type from the raw password string | ||
| static AuthType detect_password_type(const std::string& password); |
There was a problem hiding this comment.
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.
| void convert_databases(const Config& config, ConversionResult& result); | ||
| void convert_users(const Config& config, ConversionResult& result); |
There was a problem hiding this comment.
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.
| // ========================================================================= | ||
| // 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/pgbouncer_compat/ProxySQL_CLI.cpp (2)
119-120: Quoteconfig_pathin 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-likeargv[2]as missing required path.
import-pgbouncer --dry-runis currently interpreted asconfig_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
📒 Files selected for processing (5)
lib/Admin_Handler.cpplib/pgbouncer_compat/PgBouncer_ConfigConverter.cpplib/pgbouncer_compat/PgBouncer_ConfigParser.cpplib/pgbouncer_compat/PgBouncer_ShowCommands.cpplib/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#ifdefPROXYSQLGENAI,#ifdefPROXYSQL31, 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 pipedstdoutworkflows.
99-104: Good fix: SQL entries are emitted without extra statement terminators.Printing
entry.sqldirectly avoids producing;;in non-dry-run output.
|




Summary
pgbouncer.ini,userlist.txt,pg_hba.conf) into structured dataWhat'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,%includedirectives, and connection string key=value parsingPgBouncer_AuthFileParser— userlist.txt parser with MD5/SCRAM/plain password detection and""escape handlingPgBouncer_HBAParser— pg_hba.conf parser supporting local/host/hostssl/hostnossl records, all auth methods, and key=value optionsUnit tests (
test/tap/tests/unit/pgbouncer_config_parser_unit-t.cpp):%includedirectives, nonexistent files, default value verificationBuild integration:
lib/Makefiletest/tap/tests/unit/MakefileTest plan
make build_lib -j$(nproc)compiles successfullymake pgbouncer_config_parser_unit-tbuilds the unit test./pgbouncer_config_parser_unit-t— all 127 tests passRelated issues
Design spec
See
docs/superpowers/specs/2026-04-03-pgbouncer-migration-tooling-design.mdin the pgbouncer_comp working directory for the full design.Summary by CodeRabbit
New Features
Tests
Chores