Added more testcases

v3.0_pgsql-pipeline-set-reset-discard-fix-5432
Rahim Kanji 1 month ago
parent 4894bf4d72
commit 45707f5a07

@ -47,7 +47,18 @@ PGConnPtr createNewConnection(ConnType conn_type, const std::string& options = "
ss << " options='" << options << "'";
}
PGconn* conn = PQconnectdb(ss.str().c_str());
std::string conninfo = ss.str();
// Mask password for logging
std::string conninfo_display = conninfo;
size_t pwd_pos = conninfo_display.find("password=");
if (pwd_pos != std::string::npos) {
size_t pwd_end = conninfo_display.find(" ", pwd_pos);
if (pwd_end == std::string::npos) pwd_end = conninfo_display.length();
conninfo_display.replace(pwd_pos + 9, pwd_end - (pwd_pos + 9), "***");
}
diag("Connection string: %s", conninfo_display.c_str());
PGconn* conn = PQconnectdb(conninfo.c_str());
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection failed to '%s': %s", (conn_type == BACKEND ? "Backend" : "Admin"), PQerrorMessage(conn));
PQfinish(conn);
@ -771,15 +782,76 @@ bool test_discard_all_failure_pipeline() {
return got_error && correct_error;
}
// Test: Verify startup parameters are applied
bool test_startup_parameters() {
diag("=== Test: Verify startup parameters ===");
// Create connection with explicit startup parameter (space must be escaped with double backslash)
const std::string startup_value = "SQL,\\\\ DMY"; // C++: \\ -> actual: \ -> libpq sees: escaped space
PGConnPtr conn = createNewConnection(BACKEND, "-c DateStyle=" + startup_value);
if (!conn) {
diag("Failed to create connection");
return false;
}
// Check connection status
if (PQstatus(conn.get()) != CONNECTION_OK) {
diag("Connection not OK: %s", PQerrorMessage(conn.get()));
return false;
}
// Get the actual value from backend
PGresult* res = PQexec(conn.get(), "SHOW DateStyle");
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
diag("SHOW failed: %s", PQresultErrorMessage(res));
PQclear(res);
return false;
}
if (PQntuples(res) == 0) {
diag("SHOW returned no rows");
PQclear(res);
return false;
}
char* val = PQgetvalue(res, 0, 0);
std::string actual_value = val ? val : "";
diag("Startup parameter set to: '%s'", startup_value.c_str());
diag("Actual DateStyle from backend: '%s'", actual_value.c_str());
PQclear(res);
// In ProxySQL, startup parameters might not be forwarded to backend
// This test documents the current behavior
bool startup_applied = (actual_value.find("SQL") != std::string::npos);
diag("Startup parameter applied: %s", startup_applied ? "yes" : "no (pooled connection used)");
// For this test, we just verify we can read the value, not that startup params work
return !actual_value.empty();
}
// Test: RESET single variable should work in pipeline mode
bool test_reset_single_var_pipeline() {
diag("=== Test: RESET single variable in pipeline mode ===");
// Note: Connection pooling may interfere with explicit startup parameters.
// We test RESET behavior by:
// 1. Get current value
// 2. SET to a different value
// 3. RESET in pipeline mode
// 4. Verify value changed (not necessarily to original)
PGConnPtr conn = createNewConnection(BACKEND);
if (!conn) return false;
// First set a non-default value via simple query
if (!set_variable_simple(conn.get(), "DateStyle", "Postgres, DMY")) {
// Get current value (whatever the pooled connection has)
const std::string initial_value = get_variable_simple(conn.get(), "DateStyle");
diag("Initial DateStyle: '%s'", initial_value.c_str());
// Choose a test value different from current
std::string test_value = (initial_value.find("Postgres") != std::string::npos) ?
"SQL, DMY" : "Postgres, DMY";
if (!set_variable_simple(conn.get(), "DateStyle", test_value)) {
diag("Failed to SET DateStyle");
return false;
}
@ -787,6 +859,12 @@ bool test_reset_single_var_pipeline() {
std::string set_value = get_variable_simple(conn.get(), "DateStyle");
diag("After SET: DateStyle = '%s'", set_value.c_str());
// Verify value was actually changed
if (set_value == initial_value) {
diag("SET did not change the value - test cannot proceed");
return false;
}
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
@ -821,6 +899,7 @@ bool test_reset_single_var_pipeline() {
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
diag("Got result: status=%d (%s), ntuples=%d", status, PQresStatus(status), PQntuples(res));
if (status == PGRES_COMMAND_OK) {
cmd_count++;
diag("RESET command succeeded");
@ -831,6 +910,10 @@ bool test_reset_single_var_pipeline() {
diag("After RESET: DateStyle = '%s'", reset_result.c_str());
}
show_count++;
} else if (status == PGRES_TUPLES_OK && PQntuples(res) == 0) {
diag("SHOW returned 0 tuples");
} else if (status == PGRES_FATAL_ERROR) {
diag("Error: %s", PQresultErrorMessage(res));
} else if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
@ -853,12 +936,75 @@ bool test_reset_single_var_pipeline() {
PQexitPipelineMode(conn.get());
// Verify the value changed from what we SET
bool value_changed = (reset_result != set_value);
diag("Value changed from '%s' to '%s': %s",
set_value.c_str(), reset_result.c_str(), value_changed ? "yes" : "no");
// Debug output
diag("Results summary: cmd_count=%d, show_count=%d, total_count=%d", cmd_count, show_count, count);
diag("reset_result='%s', initial_value='%s'", reset_result.c_str(), initial_value.c_str());
// Verify RESET worked - value should be different from what we SET
// Note: Due to connection pooling, it may not exactly match initial_value,
// but it should be different from set_value (proving RESET executed)
bool reset_executed = (!reset_result.empty() && reset_result != set_value);
diag("Value reset from '%s' to '%s': %s",
set_value.c_str(), reset_result.c_str(),
reset_executed ? "success (value changed)" : "failed");
return (cmd_count >= 1) && (show_count >= 1) && reset_executed;
}
// Test: RESET reverts to startup parameter value
bool test_reset_reverts_to_startup_param() {
diag("=== Test: RESET reverts to startup parameter value ===");
// Create connection with explicit startup parameter (space must be escaped with double backslash)
const std::string startup_value = "SQL,\\\\ DMY"; // C++: \\ -> actual: \ -> libpq sees: escaped space
PGConnPtr conn = createNewConnection(BACKEND, "-c DateStyle=" + startup_value);
if (!conn) {
diag("Failed to create connection with startup parameter");
return false;
}
// Get startup value (what we set in connection string)
std::string actual_startup = get_variable_simple(conn.get(), "DateStyle");
diag("Startup parameter value: '%s'", startup_value.c_str());
diag("Actual DateStyle after connect: '%s'", actual_startup.c_str());
// Choose a different value for SET
std::string new_value = (actual_startup.find("Postgres") != std::string::npos) ?
"ISO, MDY" : "Postgres, DMY";
// SET to a different value
if (!set_variable_simple(conn.get(), "DateStyle", new_value)) {
diag("Failed to SET DateStyle to '%s'", new_value.c_str());
return false;
}
std::string after_set = get_variable_simple(conn.get(), "DateStyle");
diag("After SET: DateStyle = '%s'", after_set.c_str());
// Verify SET worked
if (after_set == actual_startup) {
diag("SET did not change the value");
return false;
}
// RESET the variable
PGresult* res = PQexec(conn.get(), "RESET DateStyle");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
diag("RESET failed: %s", PQresultErrorMessage(res));
PQclear(res);
return false;
}
PQclear(res);
// Get value after RESET
std::string after_reset = get_variable_simple(conn.get(), "DateStyle");
diag("After RESET: DateStyle = '%s'", after_reset.c_str());
// Verify RESET reverted to startup value
bool reverted = (after_reset == actual_startup);
diag("RESET reverted to startup value: %s", reverted ? "yes" : "no");
return (cmd_count >= 1) && (show_count >= 1) && value_changed;
return reverted;
}
// Forward declarations for pipeline mode tests
@ -881,6 +1027,7 @@ bool test_multiple_vars_out_of_sync_pipeline();
bool test_pipeline_with_locked_hostgroup();
bool test_reset_all_locked_hostgroup_pipeline();
bool test_discard_all_locked_hostgroup_pipeline();
bool test_reset_reverts_to_startup_param();
int main(int argc, char** argv) {
if (cl.getEnv())
@ -965,8 +1112,8 @@ int main(int argc, char** argv) {
{"SET datestyle = ;", false, "missing value"}
};
// Add pipeline tests to the plan (19 total: 16 pipeline + 3 simple query RESET/DISCARD)
const int num_pipeline_tests = 19;
// Add pipeline tests to the plan (20 total: 16 pipeline + 4 simple query RESET/DISCARD)
const int num_pipeline_tests = 20;
if (cl.use_noise) {
plan(tests.size() + num_pipeline_tests + 3);
@ -996,6 +1143,7 @@ int main(int argc, char** argv) {
ok(test_reset_simple_query(), "RESET single variable in simple query mode");
ok(test_reset_all_simple_query(), "RESET ALL in simple query mode");
ok(test_discard_all_simple_query(), "DISCARD ALL in simple query mode");
ok(test_reset_reverts_to_startup_param(), "RESET reverts to startup parameter value");
// Run pipeline tests
ok(test_set_simple_verify_pipeline(), "SET in simple query, verify in pipeline mode");

@ -29,8 +29,8 @@ PGConnPtr createNewConnection(ConnType conn_type, const std::string& options = "
const char* host = (conn_type == BACKEND) ? cl.pgsql_host : cl.pgsql_admin_host;
int port = (conn_type == BACKEND) ? cl.pgsql_port : cl.pgsql_admin_port;
const char* username = (conn_type == BACKEND) ? cl.pgsql_root_username : cl.admin_username;
const char* password = (conn_type == BACKEND) ? cl.pgsql_root_password : cl.admin_password;
const char* username = (conn_type == BACKEND) ? cl.pgsql_username : cl.admin_username;
const char* password = (conn_type == BACKEND) ? cl.pgsql_password : cl.admin_password;
std::stringstream ss;
@ -609,6 +609,179 @@ int main(int argc, char** argv) {
return true;
});
// ============================================================================
// Variable Sync Verification Tests for Simple and Pipeline Mode
// ============================================================================
// Test: Verify SET variable sync in SIMPLE query mode
// This test confirms that when SET is executed in simple query mode,
// the variable is properly synced to the backend
add_test("Variable sync: SET in simple query mode", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original value
const auto original = getVariable(conn.get(), "DateStyle");
diag("Original DateStyle: %s", original.c_str());
// Set a specific value in simple query mode
const std::string test_value = "Postgres, DMY";
PGresult* set_res = PQexec(conn.get(), ("SET DateStyle = '" + test_value + "'").c_str());
bool set_ok = (PQresultStatus(set_res) == PGRES_COMMAND_OK);
PQclear(set_res);
if (!set_ok) {
diag("SET failed: %s", PQerrorMessage(conn.get()));
return false;
}
// Verify client-side value is set
const auto client_val = getVariable(conn.get(), "DateStyle");
diag("Client DateStyle after SET: %s", client_val.c_str());
// Verify value was actually set (not still original)
bool client_set = (client_val.find(test_value.substr(0, 7)) != std::string::npos);
if (!client_set) {
diag("Client value was not set correctly. Got '%s', expected '%s'",
client_val.c_str(), test_value.c_str());
return false;
}
// Execute a query to trigger backend sync and verify backend has the value
// The backend should have the same value if sync worked
PGresult* sel_res = PQexec(conn.get(), "SELECT 1");
PQclear(sel_res);
const auto after_query = getVariable(conn.get(), "DateStyle");
diag("DateStyle after query: %s", after_query.c_str());
// Cleanup
PGresult* cleanup_res2 = PQexec(conn.get(), ("SET DateStyle = '" + original + "'").c_str());
PQclear(cleanup_res2);
// In simple mode, SET should be immediately effective
bool synced = (after_query.find(test_value.substr(0, 7)) != std::string::npos);
if (!synced) {
diag("Variable not synced to backend! Got '%s'", after_query.c_str());
}
return synced;
});
// Test: Verify SET variable sync in PIPELINE mode
// This test confirms that when SET is executed in pipeline mode,
// the variable is properly synced to the backend via extended query protocol
add_test("Variable sync: SET in pipeline mode", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original value
const auto original = getVariable(conn.get(), "DateStyle");
diag("Original DateStyle: %s", original.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Set a specific value using extended query protocol
const std::string test_value = "SQL, DMY";
std::string set_query = "SET DateStyle = '" + test_value + "'";
// Send SET via pipeline (extended query protocol)
if (PQsendQueryParams(conn.get(), set_query.c_str(), 0, NULL, NULL, NULL, NULL, 0) != 1) {
diag("Failed to send SET in pipeline");
PQexitPipelineMode(conn.get());
return false;
}
// Send a SELECT to verify the value
if (PQsendQueryParams(conn.get(), "SHOW DateStyle", 0, NULL, NULL, NULL, NULL, 0) != 1) {
diag("Failed to send SHOW in pipeline");
PQexitPipelineMode(conn.get());
return false;
}
// Sync
if (PQpipelineSync(conn.get()) != 1) {
diag("PQpipelineSync failed");
PQexitPipelineMode(conn.get());
return false;
}
// Consume results
int count = 0;
int set_success = 0;
int show_received = 0;
std::string show_value;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 3) { // SET result + SHOW result + sync
if (PQconsumeInput(conn.get()) == 0) {
diag("PQconsumeInput failed");
PQexitPipelineMode(conn.get());
return false;
}
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) {
set_success++;
diag("SET succeeded in pipeline");
} else if (status == PGRES_TUPLES_OK) {
show_received++;
if (PQntuples(res) > 0) {
show_value = PQgetvalue(res, 0, 0);
diag("SHOW returned: %s", show_value.c_str());
}
} else if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 3) break;
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// Cleanup
PGresult* cleanup_res = PQexec(conn.get(), ("SET DateStyle = '" + original + "'").c_str());
PQclear(cleanup_res);
// Verify results
if (set_success == 0) {
diag("SET did not succeed in pipeline");
return false;
}
if (show_received == 0) {
diag("SHOW did not return value in pipeline");
return false;
}
// Check if the value was properly synced
bool value_synced = (show_value.find(test_value.substr(0, 3)) != std::string::npos);
if (!value_synced) {
diag("Value not synced in pipeline! Got '%s', expected '%s'",
show_value.c_str(), test_value.c_str());
}
return value_synced;
});
// ============================================================================
// Pipeline Mode Tests for SET Variable Tracking
// ============================================================================

Loading…
Cancel
Save