/**
 * @file pgsql-query_cache_test-t.cpp
 * @brief This test validates the functionality and performance of PostgreSQL's query caching mechanism.
 *        It tests various scenarios including multi-threaded query execution, basic cache operations,
 *        data manipulation queries (INSERT, UPDATE, DELETE), and transaction behavior. The goal is to
 *        ensure that query results are cached, retrieved, and purged according to specified caching rules,
 *        while verifying cache-related metrics for correctness.
 */

#include <unistd.h>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>
#include "libpq-fe.h"
#include "command_line.h"
#include "noise_utils.h"
#include "tap.h"
#include "utils.h"

CommandLine cl;

#define NUM_THREADS 3
#define PGSLEEP_QUERY "SELECT PG_SLEEP(1)"

using PGConnPtr = std::unique_ptr<PGconn, decltype(&PQfinish)>;

class TestMetrics {
public:
    std::map<std::string,int> before;
    std::map<std::string,int> after;

    void swap() { 
        before.swap(after); 
    }
};

TestMetrics metrics;

void printQueryCacheMetrics() {
	diag("Before: Query Cache Metrics");
	for (const auto& obj : metrics.before)
		diag("%s : %d", obj.first.c_str(), obj.second);
	diag("===========================");

	diag("After: Query Cache Metrics");
	for (const auto& obj : metrics.after)
		diag("%s : %d", obj.first.c_str(), obj.second);
	diag("===========================");
}

class Timer {
public:
    Timer() : lastTime(std::chrono::high_resolution_clock::now()) {}

    double elapsed() {
        auto currentTime = std::chrono::high_resolution_clock::now();
        double deltaTime = std::chrono::duration<double>(currentTime - lastTime).count();
        lastTime = currentTime;
        return deltaTime;
    }

private:
    std::chrono::time_point<std::chrono::high_resolution_clock> lastTime;
};

enum ConnType {
    ADMIN,
    BACKEND
};

PGConnPtr createNewConnection(ConnType conn_type, bool with_ssl) {
    std::stringstream ss;
    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_username : cl.admin_username;
    const char* password = (conn_type == BACKEND) ? cl.pgsql_password : cl.admin_password;

    ss << "host=" << host << " port=" << port;
    ss << " user=" << username << " password=" << password;
    ss << (with_ssl ? " sslmode=require" : " sslmode=disable");

    PGconn* conn = PQconnectdb(ss.str().c_str());
    if (PQstatus(conn) != CONNECTION_OK) {
        diag("Connection failed to '%s': %s", (conn_type == BACKEND ? "Backend" : "Admin"), PQerrorMessage(conn));
        PQfinish(conn);
        return PGConnPtr(nullptr, &PQfinish);
    }
    return PGConnPtr(conn, &PQfinish);
}

bool executeQueries(PGconn* conn, const std::vector<std::string>& queries) {
    for (const auto& query : queries) {
        diag("Running: %s", query.c_str());
        PGresult* res = PQexec(conn, query.c_str());
        bool success = PQresultStatus(res) == PGRES_COMMAND_OK ||
            PQresultStatus(res) == PGRES_TUPLES_OK;
        if (!success) {
            diag("Failed to execute query '%s': %s",
                query.c_str(), PQerrorMessage(conn));
            PQclear(res);
            return false;
        }
        PQclear(res);
    }
    return true;
}

bool prepareAndExec(PGconn* conn,
    const char* stmtName,
    const char* query,
    int nParams = 0,
    const Oid* paramTypes = nullptr,
    int nExecParams = 0,
    const char* const* execValues = nullptr)
{
    // Prepare
    diag("Preparing statement '%s': %s", stmtName, query);
    PGresult* res = PQprepare(conn, stmtName, query, nParams, paramTypes);
    if (!res) {
        diag("PQprepare returned NULL: %s", PQerrorMessage(conn));
        return false;
    }
    bool ok = PQresultStatus(res) == PGRES_COMMAND_OK;
    if (!ok) {
        diag("PQprepare failed for '%s': %s", stmtName, PQerrorMessage(conn));
        PQclear(res);
        return false;
    }
    PQclear(res);

    // Execute prepared
    diag("Executing prepared statement '%s'", stmtName);
    res = PQexecPrepared(conn, stmtName, nExecParams, execValues, /*paramLengths*/ nullptr,
        /*paramFormats*/ nullptr, /*resultFormat*/ 0);
    if (!res) {
        diag("PQexecPrepared returned NULL: %s", PQerrorMessage(conn));
        return false;
    }
    ok = PQresultStatus(res) == PGRES_TUPLES_OK || PQresultStatus(res) == PGRES_COMMAND_OK;
    if (!ok) {
        diag("PQexecPrepared failed for '%s': %s", stmtName, PQerrorMessage(conn));
        PQclear(res);
        return false;
    }
    PQclear(res);

    return true;
}

void run_pgsleep_thread(double* timer_result) {
    PGConnPtr pg_conn = createNewConnection(ConnType::BACKEND, false);

    if (!pg_conn) {
        *timer_result = -1.0;
        return;
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        // execute the query at 1.4 , 2.8 and 4.2 second
        // Running 3 queries we verify:
        // 1. the cache before the threshold
        // 2. the cache after the threshold
        // 3. that the cache has been refreshed
        usleep(1400000);  // Sleep for 1.4 seconds

        Timer stopwatch;
        if (!executeQueries(pg_conn.get(), { PGSLEEP_QUERY })) {
            *timer_result = -1.0;
            return;
        }
        *timer_result += stopwatch.elapsed();
    }
}

template<class BinaryOp>
bool checkMetricDelta(const std::string& metric_name, int expected, BinaryOp op) {
	if (metrics.before.find(metric_name) == metrics.before.end() ||
        metrics.after.find(metric_name) == metrics.after.end()) {
        BAIL_OUT("Metric '%s' not found", metric_name.c_str());
		return false;
	}

    int delta = metrics.after[metric_name] - metrics.before[metric_name];
    bool result = op(std::max(0, delta), expected);

    std::string bin_op_name = typeid(BinaryOp).name();
    bin_op_name = bin_op_name.substr(3, bin_op_name.size() - 6);

    ok(result, "Metric `%s` should be '%s' %d. Actual %d", metric_name.c_str(), bin_op_name.c_str(), expected, delta);
	return result;
}

std::map<std::string, int> getQueryCacheMetrics(PGconn* proxy_admin) {
	const char* query = "SELECT Variable_Name, Variable_Value FROM stats_pgsql_global WHERE Variable_Name LIKE 'Query_Cache%';";
    diag("Running: %s", query);
    PGresult* res = PQexec(proxy_admin, query);
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {
        diag("Failed to execute query `%s`: %s", query, PQerrorMessage(proxy_admin));
        PQclear(res);
        return {};
    }

    std::map<std::string, int> metrics{};
    int nRows = PQntuples(res);
    for (int i = 0; i < nRows; i++) {
		const std::string& name = PQgetvalue(res, i, 0);
        metrics[name] = atoi(PQgetvalue(res, i, 1));
    }
    PQclear(res);
    return metrics;
}


void execute_multi_threaded_test(PGconn* admin_conn, PGconn* conn) {
    double timer_results[NUM_THREADS];
    std::vector<std::thread> mythreads(NUM_THREADS);

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME",
        })) 
        return;

    if (!executeQueries(conn, { PGSLEEP_QUERY }))
        return;
    
    for (unsigned int i = 0; i < NUM_THREADS; i++) {
        mythreads[i] = std::thread(run_pgsleep_thread, &timer_results[i]);
    }

    for (auto& thread : mythreads) {
        thread.join();
    }

    for (unsigned int i = 0; i < NUM_THREADS; i++) {
        if (timer_results[i] == -1.0) {
            fprintf(stderr, "Error: one or more threads finished with errors in file %s, line %d\n", __FILE__, __LINE__);
            return;
        }
    }

    // Calculate the number of clients that took more than 1 second to execute the query
    int num_slow_clients = 0;
    for (unsigned int i = 0; i < NUM_THREADS; i++) {
        num_slow_clients += static_cast<int>(timer_results[i]);
    }

    int expected_num_slow_clients = 3;
    ok(num_slow_clients == expected_num_slow_clients,
        "3 clients should take 1 second to execute the query. "
        "Expected: '%d', Actual: '%d'", expected_num_slow_clients, num_slow_clients);

    executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME" }
    );
}

void execute_multi_threaded_purge_test(PGconn* admin_conn, PGconn* conn) {
    double timer_results[NUM_THREADS];
    std::vector<std::thread> mythreads(NUM_THREADS);

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    if (!executeQueries(conn, { PGSLEEP_QUERY }))
        return;

    usleep(5000000);

    metrics.before = getQueryCacheMetrics(admin_conn);

    for (unsigned int i = 0; i < NUM_THREADS; i++) {
        mythreads[i] = std::thread(run_pgsleep_thread, &timer_results[i]);
    }

    for (auto& thread : mythreads) {
        thread.join();
    }

    for (unsigned int i = 0; i < NUM_THREADS; i++) {
        if (timer_results[i] == -1.0) {
            fprintf(stderr, "Error: one or more threads finished with errors in file %s, line %d\n", __FILE__, __LINE__);
            return;
        }
    }

    usleep(5000000);
    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 9, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 9, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 9, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=4194304 WHERE variable_name='pgsql-threshold_resultset_size'",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME" 
    });
}

void execute_basic_test(PGconn* admin_conn, PGconn* conn) {

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT 1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

	// difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "SELECT 1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

	usleep(4000000);

    if (!executeQueries(conn, { "SELECT 1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(admin_conn, {
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    usleep(5000000);

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 2, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME",
        }))
        return;
}

void execute_data_manipulation_test(PGconn* admin_conn, PGconn* conn) {

    // Create query rules for INSERT, DELETE, and UPDATE statements
    if (!executeQueries(admin_conn, {
		"DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (3,1,'^INSERT',4000)",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (4,1,'^DELETE',4000)",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (5,1,'^UPDATE',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        }))
        return;

    // Create test table
    if (!executeQueries(conn, { "CREATE TABLE IF NOT EXISTS test_table(id INT PRIMARY KEY, name TEXT);" }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    // INSERT
    if (!executeQueries(conn, { "INSERT INTO test_table(id, name) VALUES (1, 'test')" }))
        return;

    // DELETE
    if (!executeQueries(conn, { "DELETE FROM test_table WHERE id = 1" }))
        return;

    // UPDATE
    if (!executeQueries(conn, { "UPDATE test_table SET name = 'updated' WHERE id = 1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // Validate that no cache entries were created for DML operations
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 3, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    // Clean up test table
    if (!executeQueries(conn, { "DROP TABLE IF EXISTS test_table;" }))
        return;

	// Clean up query rules
    if (!executeQueries(admin_conn, {
       "DELETE FROM pgsql_query_rules",
       "LOAD PGSQL QUERY RULES TO RUNTIME"
        }))
        return;
}

void execute_threshold_resultset_size_test(PGconn* admin_conn, PGconn* conn) {

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=1024 WHERE variable_name='pgsql-threshold_resultset_size'",
        "UPDATE global_variables SET variable_value=1 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME",
        }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT REPEAT('X', 8197)" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());


    if (!executeQueries(admin_conn, {
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME"
        }))
        return;

    metrics.swap();

    if (!executeQueries(conn, { "SELECT REPEAT('X', 8197)" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 8197, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 8197, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "SELECT REPEAT('X', 8197)" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 8197, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=4194304 WHERE variable_name='pgsql-threshold_resultset_size'",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;
}

void execute_multi_statement_test(PGconn* admin_conn, PGconn* conn) {

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT 1; SELECT 2; SELECT 3;" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "SELECT 1; SELECT 2; SELECT 3;" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;
}

void execute_transaction_status_test(PGconn* admin_conn, PGconn* conn) {

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',10000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT 1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "SELECT 1" }))
        return;
    
    ok(PQtransactionStatus(conn) == PQTRANS_IDLE, "Connection is in IDLE state, Exp:%d : Act:%d", 
        PQTRANS_IDLE, PQtransactionStatus(conn));

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "BEGIN" }))
        return;

    ok(PQtransactionStatus(conn) == PQTRANS_INTRANS, "Connection is in TRANSACTION STATE BEFORE CACHE HIT, Exp:%d : Act:%d",
        PQTRANS_INTRANS, PQtransactionStatus(conn));

    if (!executeQueries(conn, { "SELECT 1" }))
        return;

    ok(PQtransactionStatus(conn) == PQTRANS_INTRANS, "Connection is in TRANSACTION STATE AFTER CACHE HIT, Exp:%d : Act:%d",
        PQTRANS_INTRANS, PQtransactionStatus(conn));

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    if (!executeQueries(conn, { "ROLLBACK" }))
        return;

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;
}

void execute_query_cache_store_empty_result_test(PGconn* admin_conn, PGconn* conn) {

	if (!executeQueries(admin_conn, {
		"DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl,cache_empty_result) VALUES (2,1,'^SELECT',4000,0)",
		"LOAD PGSQL QUERY RULES TO RUNTIME",
		"UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_stores_empty_result'",
		"LOAD PGSQL VARIABLES TO RUNTIME"
		}))
		return;

	metrics.before = getQueryCacheMetrics(admin_conn);

	if (!executeQueries(conn, {"SELECT 1 WHERE 1!=1"}))
		return;

	metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

	checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl,cache_empty_result) VALUES (3,1,'^SELECT',4000,1)",
        "LOAD PGSQL QUERY RULES TO RUNTIME"
        }))
        return;

	if (!executeQueries(conn, { "SELECT 1 WHERE 1!=1" }))
		return;

	metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

	checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
	checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
	checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

	if (!executeQueries(conn, { "SELECT 1 WHERE 1!=1" }))
		return;

	metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    usleep(5000000);  // Sleep for 5 seconds

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (4,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME"
        }))
        return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT 1 WHERE 1!=1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(admin_conn, {
        "UPDATE global_variables SET variable_value=1 WHERE variable_name='pgsql-query_cache_stores_empty_result'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    if (!executeQueries(conn, { "SELECT 1 WHERE 1!=1" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

	metrics.swap();

	if (!executeQueries(conn, { "SELECT 1 WHERE 1!=1" }))
		return;

	metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

	checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
	checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
	checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());


	if (!executeQueries(admin_conn, {
		"DELETE FROM pgsql_query_rules",
		"LOAD PGSQL QUERY RULES TO RUNTIME",
		}))
		return;
}

void execute_query_cache_notice_test(PGconn* admin_conn, PGconn* conn) {

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        }))
        return;

    const char* create_function_query =
        "CREATE OR REPLACE FUNCTION select_and_warn() RETURNS integer AS $$"
        "DECLARE "
        "    result integer; "
        "BEGIN "
        "    SELECT 1 INTO result; "
        "    RAISE WARNING 'This is a warning message'; "
        "    RETURN result; "
        "END; "
        "$$ LANGUAGE plpgsql;";

	if (!executeQueries(conn, { create_function_query }))
		return;

    metrics.before = getQueryCacheMetrics(admin_conn);

    if (!executeQueries(conn, { "SELECT select_and_warn()" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    // difference query cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());

    metrics.swap();

    if (!executeQueries(conn, { "SELECT select_and_warn()" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    usleep(4000000);

    if (!executeQueries(conn, { "SELECT select_and_warn()" }))
        return;

    metrics.after = getQueryCacheMetrics(admin_conn);

    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 1, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 1, std::greater<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());


    executeQueries(conn, { "DROP FUNCTION IF EXISTS select_and_warn()" });

    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME",
        }))
        return;
}

void execute_prepared_test(PGconn* admin_conn, PGconn* conn) {
    // 1) Enable query-cache-for-SELECT rules (same as basic test) so the system *would* cache
    //    simple queries -- but extended query protocol should bypass cache.
    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "INSERT INTO pgsql_query_rules (rule_id,active,match_digest,cache_ttl) VALUES (2,1,'^SELECT',4000)",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=0 WHERE variable_name='pgsql-query_cache_soft_ttl_pct'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        })) {
        return;
    }

    // Collect baseline metrics
    metrics.before = getQueryCacheMetrics(admin_conn);

    // 2) Prepare and execute (extended query) - should NOT create a cache entry
    if (!prepareAndExec(conn, "ps_select_1", "SELECT 1")) {
        // attempt cleanup before returning
        executeQueries(admin_conn, { "DELETE FROM pgsql_query_rules", "LOAD PGSQL QUERY RULES TO RUNTIME" });
        return;
    }

    // Read metrics after extended-query run
    metrics.after = getQueryCacheMetrics(admin_conn);
    printQueryCacheMetrics();

    // Expectation: extended/prepared queries are not cached -> no changes in cache metrics
    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    executeQueries(conn, { "DEALLOCATE ps_select_1" });
    // 3) Execute prepared statement again (same extended protocol). Still should not touch cache.
    if (!prepareAndExec(conn, "ps_select_1", "SELECT 1")) {
        // cleanup below
    }

    metrics.after = getQueryCacheMetrics(admin_conn);
    printQueryCacheMetrics();

    checkMetricDelta<>("Query_Cache_Memory_bytes", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_GET_OK", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_count_SET", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_IN", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_bytes_OUT", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Purged", 0, std::equal_to<int>());
    checkMetricDelta<>("Query_Cache_Entries", 0, std::equal_to<int>());

    metrics.swap();

    // 4) test that a simple (non-prepared) SELECT would create a cache entry here
    //    (verifies rule is active).
    
    if (!executeQueries(conn, { "SELECT 1" }))
        return;
    metrics.after = getQueryCacheMetrics(admin_conn);
    printQueryCacheMetrics();
    checkMetricDelta<>("Query_Cache_Entries", 1, std::equal_to<int>());
    metrics.swap();
    

    // 5) Cleanup: remove rules and restore variables as in basic test
    if (!executeQueries(admin_conn, {
        "DELETE FROM pgsql_query_rules",
        "LOAD PGSQL QUERY RULES TO RUNTIME",
        "UPDATE global_variables SET variable_value=256 WHERE variable_name='pgsql-query_cache_size_MB'",
        "LOAD PGSQL VARIABLES TO RUNTIME"
        })) {
        return;
    }

    // 6) Deallocate prepared statement on connection (best-effort; ignore failure)
    executeQueries(conn, { "DEALLOCATE ps_select_1" });
}

std::vector<std::pair<std::string, void (*)(PGconn*, PGconn*)>> tests = {
	{ "Basic Test", execute_basic_test },
	{ "Data Manipulation Test", execute_data_manipulation_test },
	{ "Multi Statement Test", execute_multi_statement_test },
	{ "Threshold Resultset Size Test", execute_threshold_resultset_size_test },
	{ "Multi Threaded Test", execute_multi_threaded_test },
	{ "Multi Threaded Purge Test", execute_multi_threaded_purge_test },
	{ "Transaction Status Test", execute_transaction_status_test },
    { "Query Cache Store Empty Result Test", execute_query_cache_store_empty_result_test },
    { "Query Cache Store Notice Result Test", execute_query_cache_notice_test },
	{ "Prepared Statement Test", execute_prepared_test },
};

void execute_tests(bool with_ssl, bool diff_conn) {

    if (diff_conn == false) {
        PGConnPtr admin_conn = createNewConnection(ConnType::ADMIN, with_ssl);
        PGConnPtr backend_conn = createNewConnection(ConnType::BACKEND, with_ssl);

        if (!admin_conn || !backend_conn) {
            fprintf(stderr, "Error: failed to connect to the database in file %s, line %d\n", __FILE__, __LINE__);
            return;
        }

        if (!executeQueries(admin_conn.get(), { "PROXYSQL FLUSH PGSQL QUERY CACHE" }))
            return;

		for (const auto& test : tests) {
			diag(">>>> Running %s - Shared Connection: %s <<<<", test.first.c_str(), !diff_conn ? "True" : "False");
			test.second(admin_conn.get(), backend_conn.get());
            diag(">>>> Done <<<<");
		}
    } else {

        PGConnPtr admin_conn = createNewConnection(ConnType::ADMIN, with_ssl);
        if (!executeQueries(admin_conn.get(), { "PROXYSQL FLUSH PGSQL QUERY CACHE" }))
            return;

        for (const auto& test : tests) {
            diag(">>>> Running %s - Shared Connection: %s <<<<", test.first.c_str(), diff_conn ? "False" : "True");

            PGConnPtr admin_conn = createNewConnection(ConnType::ADMIN, with_ssl);
            PGConnPtr backend_conn = createNewConnection(ConnType::BACKEND, with_ssl);

            if (!admin_conn || !backend_conn) {
                fprintf(stderr, "Error: failed to connect to the database in file %s, line %d\n", __FILE__, __LINE__);
                return;
            }

            test.second(admin_conn.get(), backend_conn.get());
            diag(">>>> Done <<<<");
        }
    }
}

int main(int argc, char** argv) {
    if (cl.getEnv())
        return exit_status();

	spawn_internal_noise(cl, internal_noise_mysql_traffic_v2, {{"num_connections", "100"}, {"reconnect_interval", "100"}, {"avg_delay_ms", "300"}});
	spawn_internal_noise(cl, internal_noise_prometheus_poller);
	spawn_internal_noise(cl, internal_noise_rest_prometheus_poller, {{"enable_rest_api", "true"}});

	if (cl.use_noise) {
		plan(206*2 + 3);
	} else {
		plan(206*2);
	}

    execute_tests(false, false);
	execute_tests(false, true);

    return exit_status();
}
