From bbbb73f8ee08d728b51b50a87f613cce45dc7bb6 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Thu, 4 Sep 2025 15:28:48 +0500 Subject: [PATCH] Added TAP test --- test/tap/tests/pgsql-set_statement_test-t.cpp | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 test/tap/tests/pgsql-set_statement_test-t.cpp diff --git a/test/tap/tests/pgsql-set_statement_test-t.cpp b/test/tap/tests/pgsql-set_statement_test-t.cpp new file mode 100644 index 000000000..09e0f916b --- /dev/null +++ b/test/tap/tests/pgsql-set_statement_test-t.cpp @@ -0,0 +1,155 @@ +/** + * @file pgsql-set_statement_test-t.cpp + * @brief Intention: not to test every PostgreSQL variable, but to ensure different forms of SET statements + * are parsed correctly and not wrongly locked on a hostgroup. Covers common syntaxes (`=`, TO, + * multi-word params, aliases) and checks that unsupported forms like LOCAL or function-style values + * trigger the expected hostgroup lock behavior. + */ + +#include +#include +#include +#include +#include +#include "libpq-fe.h" +#include "command_line.h" +#include "tap.h" +#include "utils.h" + +CommandLine cl; + +using PGConnPtr = std::unique_ptr; + +enum ConnType { + ADMIN, + BACKEND +}; + +PGConnPtr createNewConnection(ConnType conn_type, const std::string& options = "", bool with_ssl = false) { + + 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; + + std::stringstream ss; + + ss << "host=" << host << " port=" << port; + ss << " user=" << username << " password=" << password; + ss << (with_ssl ? " sslmode=require" : " sslmode=disable"); + + if (options.empty() == false) { + ss << " options='" << options << "'"; + } + + PGconn* conn = PQconnectdb(ss.str().c_str()); + if (PQstatus(conn) != CONNECTION_OK) { + fprintf(stderr, "Connection failed to '%s': %s", (conn_type == BACKEND ? "Backend" : "Admin"), PQerrorMessage(conn)); + PQfinish(conn); + return PGConnPtr(nullptr, &PQfinish); + } + return PGConnPtr(conn, &PQfinish); +} + +struct TestCase { + std::string sql; + bool should_not_lock_on_hostgroup; + std::string description; +}; + +std::fstream f_proxysql_log{}; + +bool check_logs_for_command(const std::string& command_regex) { + const auto& [_, cmd_lines] { get_matching_lines(f_proxysql_log, command_regex) }; + return !cmd_lines.empty(); +} + +bool run_set_statement(const std::string& stmt, ConnType type = BACKEND) { + PGConnPtr conn = createNewConnection(type); + if (!conn) return false; + + PGresult* res = PQexec(conn.get(), stmt.c_str()); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + return false; + } + PQclear(res); + + return check_logs_for_command(".*\\[WARNING\\] Unable to parse unknown SET query from client.*") == false; +} + +int main(int argc, char** argv) { + + std::vector tests = { + // Standard param/value + {"SET datestyle = 'ISO, MDY';", true, "datestyle with ="}, + {"SET datestyle TO 'ISO,MDY';", true, "datestyle with TO"}, + {"SET standard_conforming_strings TO on;", true, "boolean ON"}, + {"SET enable_seqscan = off;", true, "boolean OFF"}, + {"SET SESSION datestyle = 'ISO, DMY';", true, "SESSION prefix"}, + + // TIME ZONE + {"SET TIME ZONE 'UTC';", true, "TIME ZONE UTC"}, + {"SET TIME ZONE DEFAULT;", true, "TIME ZONE DEFAULT"}, + {"SET TIME ZONE -7;", true, "TIME ZONE numeric offset"}, + {"SET TIME ZONE INTERVAL '+02:30' HOUR TO MINUTE;", true, "TIME ZONE interval"}, + + // TRANSACTION ISOLATION LEVEL + {"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;", false, "TX ISOLATION READ UNCOMMITTED"}, + {"SET TRANSACTION ISOLATION LEVEL READ COMMITTED;", false, "TX ISOLATION READ COMMITTED"}, + {"SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;", false, "TX ISOLATION REPEATABLE READ"}, + {"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;", false, "TX ISOLATION SERIALIZABLE"}, + + // XML OPTION + {"SET XML OPTION DOCUMENT;", false, "XML OPTION DOCUMENT"}, + {"SET XML OPTION CONTENT;", false, "XML OPTION CONTENT"}, + + // SESSION AUTHORIZATION + {"SET SESSION AUTHORIZATION DEFAULT;", false, "SESSION AUTHORIZATION DEFAULT"}, + + // ROLE + {"SET ROLE NONE;", false, "ROLE NONE"}, + + // SCHEMA + {"SET SCHEMA 'pg_catalog';", false, "SCHEMA valid"}, + + // NAMES + {"SET NAMES SQL_ASCII;", true, "NAMES SQL_ASCII"}, + {"SET NAMES UTF8;", true, "NAMES UTF8"}, + + // SEED + {"SET SEED 0.5;", false, "SEED 0.5"}, + {"SET SEED 0;", false, "SEED 0"}, + {"SET SEED 1;", false, "SEED 1"}, + {"SET SEED 1.5;", false, "SEED out of range"}, + + // Failure cases + {"SET ALL TO DEFAULT;", false, "ALL should fail"}, + {"SET LOCAL datestyle TO 'ISO,MDY';", false, "LOCAL should fail"}, + {"SET search_path TO current_schemas(true);", false, "function value should fail"}, + {"SET datestyle = ;", false, "missing value"} + }; + + plan(tests.size()); + + if (cl.getEnv()) + return exit_status(); + + std::string f_path{ get_env("REGULAR_INFRA_DATADIR") + "/proxysql.log" }; + + int of_err = open_file_and_seek_end(f_path, f_proxysql_log); + if (of_err != EXIT_SUCCESS) { + return exit_status(); + } + + for (const auto& t : tests) { + f_proxysql_log.clear(f_proxysql_log.rdstate() & ~std::ios_base::failbit); + f_proxysql_log.seekg(f_proxysql_log.tellg()); + bool result = run_set_statement(t.sql); + ok(result == t.should_not_lock_on_hostgroup, "%s", t.description.c_str()); + usleep(10000); + } + + f_proxysql_log.close(); + return exit_status(); +}