diff --git a/test/tap/tests/pgsql-set_statement_test-t.cpp b/test/tap/tests/pgsql-set_statement_test-t.cpp index 1afc7bee0..373875969 100644 --- a/test/tap/tests/pgsql-set_statement_test-t.cpp +++ b/test/tap/tests/pgsql-set_statement_test-t.cpp @@ -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"); diff --git a/test/tap/tests/pgsql-transaction_variable_state_tracking-t.cpp b/test/tap/tests/pgsql-transaction_variable_state_tracking-t.cpp index 34f875693..fb1992ee6 100644 --- a/test/tap/tests/pgsql-transaction_variable_state_tracking-t.cpp +++ b/test/tap/tests/pgsql-transaction_variable_state_tracking-t.cpp @@ -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 // ============================================================================