You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/lib/Admin_FlushVariables.cpp

1491 lines
58 KiB

#include "../deps/json/json.hpp"
using json = nlohmann::json;
#define PROXYJSON
#include <iostream> // std::cout
#include <sstream> // std::stringstream
#include <fstream>
#include <algorithm> // std::sort
#include <memory>
#include <vector> // std::vector
#include <unordered_set>
#include "prometheus/exposer.h"
#include "prometheus/counter.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "Base_Thread.h"
#include "MySQL_HostGroups_Manager.h"
#include "PgSQL_HostGroups_Manager.h"
#include "mysql.h"
#include "proxysql_admin.h"
#include "re2/re2.h"
#include "re2/regexp.h"
#include "proxysql.h"
#include "proxysql_config.h"
#include "proxysql_restapi.h"
#include "MCP_Thread.h"
#include "MySQL_Tool_Handler.h"
#include "Query_Tool_Handler.h"
#include "Config_Tool_Handler.h"
#include "Admin_Tool_Handler.h"
#include "Cache_Tool_Handler.h"
#include "Observe_Tool_Handler.h"
#include "ProxySQL_MCP_Server.hpp"
#include "proxysql_utils.h"
#include "prometheus_helpers.h"
#include "cpp.h"
#include "MySQL_Data_Stream.h"
#include "PgSQL_Data_Stream.h"
#include "MySQL_Query_Processor.h"
#include "PgSQL_Query_Processor.h"
#include "ProxySQL_HTTP_Server.hpp" // HTTP server
#include "MySQL_Authentication.hpp"
#include "PgSQL_Authentication.h"
#include "MySQL_LDAP_Authentication.hpp"
#include "MySQL_PreparedStatement.h"
#include "ProxySQL_Cluster.hpp"
#include "ProxySQL_Statistics.hpp"
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#include "GenAI_Thread.h"
#include "SQLite3_Server.h"
#include "Web_Interface.hpp"
#include <dirent.h>
#include <search.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <pthread.h>
#ifndef SPOOKYV2
#include "SpookyV2.h"
#define SPOOKYV2
#endif
#include <fcntl.h>
#include <sys/utsname.h>
#include "platform.h"
#include "microhttpd.h"
#if (defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) || defined(__mips__)) && defined(__linux)
// currently only support x86-32, x86-64, ARM, and MIPS on Linux
#include "coredumper/coredumper.h"
#endif
#include <uuid/uuid.h>
#include "PgSQL_Protocol.h"
//#include "usual/time.h"
using std::string;
using std::unique_ptr;
#ifdef WITHGCOV
extern "C" void __gcov_dump();
extern "C" void __gcov_reset();
#endif
#ifdef DEBUG
//#define BENCHMARK_FASTROUTING_LOAD
#endif // DEBUG
//#define MYSQL_THREAD_IMPLEMENTATION
#define SELECT_VERSION_COMMENT "select @@version_comment limit 1"
#define SELECT_VERSION_COMMENT_LEN 32
#define SELECT_DB_USER "select DATABASE(), USER() limit 1"
#define SELECT_DB_USER_LEN 33
#define SELECT_CHARSET_VARIOUS "select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1"
#define SELECT_CHARSET_VARIOUS_LEN 115
#define READ_ONLY_OFF "\x01\x00\x00\x01\x02\x23\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x0d\x56\x61\x72\x69\x61\x62\x6c\x65\x5f\x6e\x61\x6d\x65\x00\x0c\x21\x00\x0f\x00\x00\x00\xfd\x01\x00\x1f\x00\x00\x1b\x00\x00\x03\x03\x64\x65\x66\x00\x00\x00\x05\x56\x61\x6c\x75\x65\x00\x0c\x21\x00\x0f\x00\x00\x00\xfd\x01\x00\x1f\x00\x00\x05\x00\x00\x04\xfe\x00\x00\x02\x00\x0e\x00\x00\x05\x09\x72\x65\x61\x64\x5f\x6f\x6e\x6c\x79\x03\x4f\x46\x46\x05\x00\x00\x06\xfe\x00\x00\x02\x00"
#define READ_ONLY_ON "\x01\x00\x00\x01\x02\x23\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x0d\x56\x61\x72\x69\x61\x62\x6c\x65\x5f\x6e\x61\x6d\x65\x00\x0c\x21\x00\x0f\x00\x00\x00\xfd\x01\x00\x1f\x00\x00\x1b\x00\x00\x03\x03\x64\x65\x66\x00\x00\x00\x05\x56\x61\x6c\x75\x65\x00\x0c\x21\x00\x0f\x00\x00\x00\xfd\x01\x00\x1f\x00\x00\x05\x00\x00\x04\xfe\x00\x00\x02\x00\x0d\x00\x00\x05\x09\x72\x65\x61\x64\x5f\x6f\x6e\x6c\x79\x02\x4f\x4e\x05\x00\x00\x06\xfe\x00\x00\x02\x00"
#define READ_ONLY_0 "\x01\x00\x00\x01\x01\x28\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x12\x40\x40\x67\x6c\x6f\x62\x61\x6c\x2e\x72\x65\x61\x64\x5f\x6f\x6e\x6c\x79\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x80\x00\x00\x00\x00\x05\x00\x00\x03\xfe\x00\x00\x02\x00\x02\x00\x00\x04\x01\x30\x05\x00\x00\x05\xfe\x00\x00\x02\x00"
#define READ_ONLY_1 "\x01\x00\x00\x01\x01\x28\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x12\x40\x40\x67\x6c\x6f\x62\x61\x6c\x2e\x72\x65\x61\x64\x5f\x6f\x6e\x6c\x79\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x80\x00\x00\x00\x00\x05\x00\x00\x03\xfe\x00\x00\x02\x00\x02\x00\x00\x04\x01\x31\x05\x00\x00\x05\xfe\x00\x00\x02\x00"
extern struct MHD_Daemon *Admin_HTTP_Server;
extern ProxySQL_Statistics *GloProxyStats;
template<enum SERVER_TYPE>
int ProxySQL_Test___PurgeDigestTable(bool async_purge, bool parallel, char **msg);
extern char *ssl_key_fp;
extern char *ssl_cert_fp;
extern char *ssl_ca_fp;
// ProxySQL_Admin shared variables
extern int admin___web_verbosity;
extern char * proxysql_version;
#include "proxysql_find_charset.h"
//extern MySQL_Query_Cache *GloMyQC;
extern MySQL_Authentication *GloMyAuth;
extern PgSQL_Authentication *GloPgAuth;
extern MySQL_LDAP_Authentication *GloMyLdapAuth;
extern ProxySQL_Admin *GloAdmin;
extern MySQL_Query_Processor* GloMyQPro;
extern PgSQL_Query_Processor* GloPgQPro;
extern MySQL_Threads_Handler *GloMTH;
extern MySQL_Logger *GloMyLogger;
extern PgSQL_Logger* GloPgSQL_Logger;
extern MySQL_STMT_Manager_v14 *GloMyStmt;
extern MySQL_Monitor *GloMyMon;
extern PgSQL_Threads_Handler* GloPTH;
#ifdef PROXYSQLGENAI
extern MCP_Threads_Handler* GloMCPH;
extern GenAI_Threads_Handler* GloGATH;
extern AI_Features_Manager *GloAI;
#endif /* PROXYSQLGENAI */
extern void (*flush_logs_function)();
extern Web_Interface *GloWebInterface;
extern ProxySQL_Cluster *GloProxyCluster;
#ifdef PROXYSQLCLICKHOUSE
extern ClickHouse_Authentication *GloClickHouseAuth;
extern ClickHouse_Server *GloClickHouseServer;
#endif /* PROXYSQLCLICKHOUSE */
extern SQLite3_Server *GloSQLite3Server;
extern char * binary_sha1;
extern int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg);
bool ProxySQL_Admin::flush_GENERIC_variables__retrieve__database_to_runtime(const std::string& modname, char* &error, int& cols, int& affected_rows, SQLite3_result* &resultset) {
string q = "SELECT substr(variable_name," + to_string(modname.length()+2) + ") vn, variable_value FROM global_variables WHERE variable_name LIKE '" + modname + "-%'";
admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset);
if (error) {
proxy_error("Error on %s : %s\n", q.c_str(), error);
free(error);
return false;
}
return true;
}
void ProxySQL_Admin::flush_GENERIC_variables__process__database_to_runtime(
const string& modname, SQLite3DB *db, SQLite3_result* resultset,
const bool& lock, const bool& replace,
const std::unordered_set<std::string>& variables_read_only,
const std::unordered_set<std::string>& variables_to_delete_silently,
const std::unordered_set<std::string>& variables_deprecated,
const std::unordered_set<std::string>& variables_special_values,
std::function<void(const std::string&, const char *, SQLite3DB *)> special_variable_action
) {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
bool rc = false;
if (modname == "admin") {
rc = set_variable(r->fields[0],r->fields[1], lock);
} else if (modname == "mysql") {
rc = GloMTH->set_variable(r->fields[0],r->fields[1]);
} else if (modname == "sqliteserver") {
rc = GloSQLite3Server->set_variable(r->fields[0],r->fields[1]);
#ifdef PROXYSQLCLICKHOUSE
} else if (modname == "clickhouse") {
rc = GloClickHouseServer->set_variable(r->fields[0],r->fields[1]);
#endif // PROXYSQLCLICKHOUSE
} else if (modname == "ldap") {
rc = GloMyLdapAuth->set_variable(r->fields[0],r->fields[1]);
}
const string v = string(r->fields[0]);
if (rc==false) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]);
if (replace) {
char *val = NULL;
if (modname == "admin") {
val = get_variable(r->fields[0]);
} else if (modname == "mysql") {
val = GloMTH->get_variable(r->fields[0]);
} else if (modname == "sqliteserver") {
val = GloSQLite3Server->get_variable(r->fields[0]);
#ifdef PROXYSQLCLICKHOUSE
} else if (modname == "clickhouse") {
val = GloClickHouseServer->get_variable(r->fields[0]);
#endif // PROXYSQLCLICKHOUSE
} else if (modname == "ldap") {
val = GloMyLdapAuth->get_variable(r->fields[0]);
}
char q[1000];
if (val) {
if (variables_read_only.count(v) > 0) {
proxy_warning("Impossible to set read-only variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val);
} else {
proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val);
}
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"%s-%s\",\"%s\")", modname.c_str(), r->fields[0],val);
db->execute(q);
free(val);
} else {
if (variables_to_delete_silently.count(v) > 0) {
sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]);
db->execute(q);
} else if (variables_deprecated.count(v) > 0) {
proxy_error("Global variable %s-%s is deprecated.\n", modname.c_str(), r->fields[0]);
sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]);
db->execute(q);
} else {
proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]);
}
sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]);
db->execute(q);
}
}
} else {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]);
if (variables_special_values.count(v) > 0) {
if (special_variable_action != nullptr) {
special_variable_action(v, r->fields[1], db);
}
}
}
}
}
void ProxySQL_Admin::flush_admin_variables___database_to_runtime(
SQLite3DB *db, bool replace, const string& checksum, const time_t epoch, bool lock
) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ADMIN variables. Replace:%d\n", replace);
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
if (flush_GENERIC_variables__retrieve__database_to_runtime("admin", error, cols, affected_rows, resultset) == true) {
wrlock();
flush_GENERIC_variables__process__database_to_runtime("admin", db, resultset, lock, replace, {"version"}, {"debug"}, {}, {});
//commit(); NOT IMPLEMENTED
// Checksums are always generated - 'admin-checksum_*' deprecated
{
// generate checksum for cluster
pthread_mutex_lock(&GloVars.checksum_mutex);
flush_admin_variables___runtime_to_database(admindb, false, false, false, true);
flush_GENERIC_variables__checksum__database_to_runtime("admin", checksum, epoch);
pthread_mutex_unlock(&GloVars.checksum_mutex);
}
wrunlock();
{
load_http_server();
load_restapi_server();
// Update the admin variable for 'web_verbosity'
admin___web_verbosity = variables.web_verbosity;
}
}
if (resultset) delete resultset;
}
void ProxySQL_Admin::flush_pgsql_variables___runtime_to_database(SQLite3DB* db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing PgSQL variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (onlyifempty) {
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'pgsql-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
int matching_rows = 0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
else {
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
matching_rows += atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has PgSQL variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting PgSQL variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'pgsql-%'");
}
static char* a;
static char* b;
if (replace) {
a = (char*)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
}
else {
a = (char*)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
}
int rc;
//sqlite3 *mydb3=db->get_db();
auto [rc1, statement1_unique] = db->prepare_v2(a);
ASSERT_SQLITE_OK(rc1, db);
sqlite3_stmt* statement1 = statement1_unique.get();
sqlite3_stmt* statement2 = NULL;
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'pgsql-%'");
b = (char*)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(?1, ?2)";
auto [rc2, statement2_unique] = db->prepare_v2(b);
ASSERT_SQLITE_OK(rc2, db);
statement2 = statement2_unique.get();
}
if (use_lock) {
GloPTH->wrlock();
db->execute("BEGIN");
}
char** varnames = GloPTH->get_variables_list();
for (int i = 0; varnames[i]; i++) {
char* val = GloPTH->get_variable(varnames[i]);
char* qualified_name = (char*)malloc(strlen(varnames[i]) + 12);
sprintf(qualified_name, "pgsql-%s", varnames[i]);
rc = (*proxy_sqlite3_bind_text)(statement1, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_bind_text)(statement1, 2, (val ? val : (char*)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement1);
rc = (*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, db);
if (runtime) {
rc = (*proxy_sqlite3_bind_text)(statement2, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_bind_text)(statement2, 2, (val ? val : (char*)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement2);
rc = (*proxy_sqlite3_clear_bindings)(statement2); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_reset)(statement2); ASSERT_SQLITE_OK(rc, db);
}
if (val)
free(val);
free(qualified_name);
}
if (use_lock) {
db->execute("COMMIT");
GloPTH->wrunlock();
}
for (int i = 0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
void ProxySQL_Admin::flush_GENERIC_variables__checksum__database_to_runtime(const string& modname, const string& checksum, const time_t epoch) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
std::string q;
q="SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE '" + modname + "-\%' ";
if (modname == "mysql") {
q += " AND variable_name NOT IN ('mysql-threads')";
if (GloVars.cluster_sync_interfaces == false) {
q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_MYSQL);
}
} else if (modname == "admin") {
if (GloVars.cluster_sync_interfaces == false) {
q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_ADMIN);
}
}
q += " ORDER BY variable_name";
admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset);
uint64_t hash1 = resultset->raw_checksum();
uint32_t d32[2];
char buf[20];
memcpy(&d32, &hash1, sizeof(hash1));
sprintf(buf,"0x%0X%0X", d32[0], d32[1]);
ProxySQL_Checksum_Value *checkvar = NULL;
if (modname == "admin") {
checkvar = &GloVars.checksums_values.admin_variables;
} else if (modname == "mysql") {
checkvar = &GloVars.checksums_values.mysql_variables;
} else if (modname == "ldap") {
checkvar = &GloVars.checksums_values.ldap_variables;
}
assert(checkvar != NULL);
checkvar->set_checksum(buf);
checkvar->version++;
time_t t = time(NULL);
if (epoch != 0 && checksum != "" && checkvar->checksum == checksum) {
checkvar->epoch = epoch;
} else {
checkvar->epoch = t;
}
GloVars.epoch_version = t;
GloVars.generate_global_checksum();
GloVars.checksums_values.updates_cnt++;
string modnameupper = modname;
for (char &c : modnameupper) { c = std::toupper(c); }
proxy_info(
"Computed checksum for 'LOAD %s VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n",
modnameupper.c_str(), checkvar->checksum, checkvar->epoch
);
delete resultset;
}
void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum, const time_t epoch) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MySQL variables. Replace:%d\n", replace);
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
if (flush_GENERIC_variables__retrieve__database_to_runtime("mysql", error, cols, affected_rows, resultset) == true) {
GloMTH->wrlock();
char * previous_default_charset = GloMTH->get_variable_string((char *)"default_charset");
char * previous_default_collation_connection = GloMTH->get_variable_string((char *)"default_collation_connection");
assert(previous_default_charset);
assert(previous_default_collation_connection);
flush_GENERIC_variables__process__database_to_runtime("mysql", db, resultset, false, replace, {}, {"session_debug"}, {"forward_autocommit"},
{
"default_collation_connection",
"default_charset",
"show_processlist_extended",
#ifdef IDLE_THREADS
"session_idle_show_processlist",
#endif // IDLE_THREADS
"processlist_max_query_length"
},
[](const std::string& varname, const char *varvalue, SQLite3DB* db) {
if (varname == "default_collation_connection" || varname == "default_charset") {
char *val=GloMTH->get_variable((char *)varname.c_str());
if (val) {
if (strcmp(val,varvalue)) {
char q[1000];
proxy_warning("Variable %s with value \"%s\" is being replaced with value \"%s\".\n", varname.c_str(), varvalue, val);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")", varname.c_str() ,val);
db->execute(q);
}
free(val);
}
} else if (varname == "show_processlist_extended") {
GloAdmin->variables.mysql_processlist.show_extended = atoi(varvalue);
#ifdef IDLE_THREADS
} else if (varname == "session_idle_show_processlist") {
GloAdmin->variables.mysql_processlist.show_idle_session = atoi(varvalue);
#endif // IDLE_THREADS
} else if (varname == "processlist_max_query_length") {
GloAdmin->variables.mysql_processlist.max_query_length = atoi(varvalue);
}
}
);
char q[1000];
char * default_charset = GloMTH->get_variable_string((char *)"default_charset");
char * default_collation_connection = GloMTH->get_variable_string((char *)"default_collation_connection");
assert(default_charset);
assert(default_collation_connection);
MARIADB_CHARSET_INFO * ci = NULL;
ci = proxysql_find_charset_name(default_charset);
if (ci == NULL) {
// invalid charset
proxy_error("Found an incorrect value for mysql-default_charset: %s\n", default_charset);
// let's try to get a charset from collation connection
ci = proxysql_find_charset_collate(default_collation_connection);
if (ci == NULL) {
proxy_error("Found an incorrect value for mysql-default_collation_connection: %s\n", default_collation_connection);
const char *p = mysql_tracked_variables[SQL_CHARACTER_SET].default_value;
ci = proxysql_find_charset_name(p);
assert(ci);
proxy_info("Resetting mysql-default_charset to hardcoded default value: %s\n", ci->csname);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_charset\",\"%s\")", ci->csname);
db->execute(q);
GloMTH->set_variable((char *)"default_charset",ci->csname);
proxy_info("Resetting mysql-default_collation_connection to hardcoded default value: %s\n", ci->name);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_collation_connection\",\"%s\")", ci->name);
db->execute(q);
GloMTH->set_variable((char *)"default_collation_connection",ci->name);
} else {
proxy_info("Changing mysql-default_charset to %s using configured mysql-default_collation_connection %s\n", ci->csname, ci->name);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_charset\",\"%s\")", ci->csname);
db->execute(q);
GloMTH->set_variable((char *)"default_charset",ci->csname);
}
} else {
MARIADB_CHARSET_INFO * cic = NULL;
cic = proxysql_find_charset_collate(default_collation_connection);
if (cic == NULL) {
proxy_error("Found an incorrect value for mysql-default_collation_connection: %s\n", default_collation_connection);
proxy_info("Changing mysql-default_collation_connection to %s using configured mysql-default_charset: %s\n", ci->name, ci->csname);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_collation_connection\",\"%s\")", ci->name);
db->execute(q);
GloMTH->set_variable((char *)"default_collation_connection",ci->name);
} else {
if (strcmp(cic->csname,ci->csname)==0) {
// mysql-default_collation_connection and mysql-default_charset are compatible
} else {
proxy_error("Found incompatible values for mysql-default_charset (%s) and mysql-default_collation_connection (%s)\n", default_charset, default_collation_connection);
bool use_collation = true;
if (strcmp(default_charset, previous_default_charset)) { // charset changed
if (strcmp(default_collation_connection, previous_default_collation_connection)==0) { // collation didn't change
// the user has changed the charset but not the collation
// we use charset as source of truth
use_collation = false;
}
}
if (use_collation) {
proxy_info("Changing mysql-default_charset to %s using configured mysql-default_collation_connection %s\n", cic->csname, cic->name);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_charset\",\"%s\")", cic->csname);
db->execute(q);
GloMTH->set_variable((char *)"default_charset",cic->csname);
} else {
proxy_info("Changing mysql-default_collation_connection to %s using configured mysql-default_charset: %s\n", ci->name, ci->csname);
sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_collation_connection\",\"%s\")", ci->name);
db->execute(q);
GloMTH->set_variable((char *)"default_collation_connection",ci->name);
}
}
}
}
free(default_charset);
free(default_collation_connection);
free(previous_default_charset);
free(previous_default_collation_connection);
GloMTH->commit();
GloMTH->wrunlock();
{
// NOTE: 'GloMTH->wrunlock()' should have been called before this point to avoid possible
// deadlocks. See issue #3847.
pthread_mutex_lock(&GloVars.checksum_mutex);
// generate checksum for cluster
flush_mysql_variables___runtime_to_database(admindb, false, false, false, true, true);
flush_GENERIC_variables__checksum__database_to_runtime("mysql", checksum, epoch);
pthread_mutex_unlock(&GloVars.checksum_mutex);
}
/**
* @brief Check and warn if TCP keepalive is disabled for MySQL connections.
*
* This safety check warns users when mysql-use_tcp_keepalive is set to false,
* which can cause connection instability in certain deployment scenarios.
*
* @warning Disabling TCP keepalive is unsafe when ProxySQL is deployed behind:
* - Network load balancers with idle connection timeouts
* - NAT firewalls with connection state timeout
* - Cloud environments with connection pooling
* - Any intermediate network device that drops idle connections
*
* @why_unsafe TCP keepalive sends periodic keep-alive packets on idle connections.
* When disabled:
* - Load balancers may drop connections from their connection pools
* - NAT devices may remove connection state from their tables
* - Cloud load balancers (AWS ELB, GCP Load Balancer, etc.) may terminate
* connections during idle periods
* - Results in sudden connection failures and "connection reset" errors
* - Can cause application downtime and poor user experience
*
* @recommendation Always set mysql-use_tcp_keepalive=true when deploying
* behind load balancers or in cloud environments.
*/
// Check for TCP keepalive setting and warn if disabled
int mysql_use_tcp_keepalive = GloMTH->get_variable_int((char *)"use_tcp_keepalive");
if (mysql_use_tcp_keepalive == 0) {
proxy_warning("mysql-use_tcp_keepalive is set to false. This may cause connection drops when ProxySQL is behind a network load balancer. Consider setting this to true.\n");
}
}
if (resultset) delete resultset;
}
void ProxySQL_Admin::flush_sqliteserver_variables___database_to_runtime(SQLite3DB *db, bool replace) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing SQLiteServer variables. Replace:%d\n", replace);
if (
(GloVars.global.sqlite3_server == false)
||
( GloSQLite3Server == NULL )
) {
return;
}
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
if (flush_GENERIC_variables__retrieve__database_to_runtime("sqliteserver", error, cols, affected_rows, resultset) == true) {
GloSQLite3Server->wrlock();
flush_GENERIC_variables__process__database_to_runtime("sqliteserver", db, resultset, false, replace, {}, {"session_debug"}, {}, {});
//GloClickHouse->commit();
GloSQLite3Server->wrunlock();
}
if (resultset) delete resultset;
}
void ProxySQL_Admin::flush_sqliteserver_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ClickHouse variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (GloVars.global.sqlite3_server == false) {
return;
}
if (onlyifempty) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
char *q=(char *)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'sqliteserver-%'";
db->execute_statement(q, &error , &cols , &affected_rows , &resultset);
int matching_rows=0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
} else {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
matching_rows+=atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has ClickHouse variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting ClickHouse variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'sqliteserver-%'");
}
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'sqliteserver-%'");
}
char *a;
char *b=(char *)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(\"sqliteserver-%s\",\"%s\")";
if (replace) {
a=(char *)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(\"sqliteserver-%s\",\"%s\")";
} else {
a=(char *)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(\"sqliteserver-%s\",\"%s\")";
}
int l=strlen(a)+200;
GloSQLite3Server->wrlock();
char **varnames=GloSQLite3Server->get_variables_list();
for (int i=0; varnames[i]; i++) {
char *val=GloSQLite3Server->get_variable(varnames[i]);
l+=( varnames[i] ? strlen(varnames[i]) : 6);
l+=( val ? strlen(val) : 6);
char *query=(char *)malloc(l);
sprintf(query, a, varnames[i], val);
if (runtime) {
db->execute(query);
sprintf(query, b, varnames[i], val);
}
db->execute(query);
if (val)
free(val);
free(query);
}
GloSQLite3Server->wrunlock();
for (int i=0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
#ifdef PROXYSQLCLICKHOUSE
void ProxySQL_Admin::flush_clickhouse_variables___database_to_runtime(SQLite3DB *db, bool replace) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ClickHouse variables. Replace:%d\n", replace);
if (
(GloVars.global.clickhouse_server == false)
||
( GloClickHouseServer == NULL )
) {
return;
}
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
if (flush_GENERIC_variables__retrieve__database_to_runtime("clickhouse", error, cols, affected_rows, resultset) == true) {
GloClickHouseServer->wrlock();
flush_GENERIC_variables__process__database_to_runtime("clickhouse", db, resultset, false, replace, {}, {"session_debug"}, {}, {});
//GloClickHouse->commit();
GloClickHouseServer->wrunlock();
}
if (resultset) delete resultset;
}
void ProxySQL_Admin::flush_clickhouse_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ClickHouse variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (
(GloVars.global.clickhouse_server == false)
||
( GloClickHouseServer == NULL )
) {
return;
}
if (onlyifempty) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
char *q=(char *)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'clickhouse-%'";
db->execute_statement(q, &error , &cols , &affected_rows , &resultset);
int matching_rows=0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
} else {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
matching_rows+=atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has ClickHouse variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting ClickHouse variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'clickhouse-%'");
}
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'clickhouse-%'");
}
char *a;
char *b=(char *)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(\"clickhouse-%s\",\"%s\")";
if (replace) {
a=(char *)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(\"clickhouse-%s\",\"%s\")";
} else {
a=(char *)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(\"clickhouse-%s\",\"%s\")";
}
int l=strlen(a)+200;
GloClickHouseServer->wrlock();
char **varnames=GloClickHouseServer->get_variables_list();
for (int i=0; varnames[i]; i++) {
char *val=GloClickHouseServer->get_variable(varnames[i]);
l+=( varnames[i] ? strlen(varnames[i]) : 6);
l+=( val ? strlen(val) : 6);
char *query=(char *)malloc(l);
sprintf(query, a, varnames[i], val);
if (runtime) {
db->execute(query);
sprintf(query, b, varnames[i], val);
}
db->execute(query);
if (val)
free(val);
free(query);
}
GloClickHouseServer->wrunlock();
for (int i=0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
#endif /* PROXYSQLCLICKHOUSE */
void ProxySQL_Admin::flush_pgsql_variables___database_to_runtime(SQLite3DB* db, bool replace, const std::string& checksum, const time_t epoch) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing PgSQL variables. Replace:%d\n", replace);
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT substr(variable_name,7) vn, variable_value FROM global_variables WHERE variable_name LIKE 'pgsql-%'";
admindb->execute_statement(q, &error, &cols, &affected_rows, &resultset);
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
else {
GloPTH->wrlock();
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
const char* value = r->fields[1];
bool rc = GloPTH->set_variable(r->fields[0], value);
if (rc == false) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0], value);
if (replace) {
char* val = GloPTH->get_variable(r->fields[0]);
char q[1000];
if (val) {
if (strcmp(val, value)) {
proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0], value, val);
sprintf(q, "INSERT OR REPLACE INTO global_variables VALUES(\"pgsql-%s\",\"%s\")", r->fields[0], val);
db->execute(q);
}
free(val);
}
else {
if (strcmp(r->fields[0], (char*)"session_debug") == 0) {
sprintf(q, "DELETE FROM disk.global_variables WHERE variable_name=\"pgsql-%s\"", r->fields[0]);
db->execute(q);
}
else {
if (strcmp(r->fields[0], (char*)"forward_autocommit") == 0) {
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
proxy_error("Global variable pgsql-forward_autocommit is deprecated. See issue #3253\n");
}
sprintf(q, "DELETE FROM disk.global_variables WHERE variable_name=\"pgsql-%s\"", r->fields[0]);
db->execute(q);
}
else {
proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0], r->fields[1]);
}
}
sprintf(q, "DELETE FROM global_variables WHERE variable_name=\"pgsql-%s\"", r->fields[0]);
db->execute(q);
}
}
}
else {
if (
(strcmp(r->fields[0], "default_collation_connection") == 0)
|| (strcmp(r->fields[0], "default_charset") == 0)
) {
char* val = GloPTH->get_variable(r->fields[0]);
char q[1000];
if (val) {
if (strcmp(val, value)) {
proxy_warning("Variable %s with value \"%s\" is being replaced with value \"%s\".\n", r->fields[0], value, val);
sprintf(q, "INSERT OR REPLACE INTO global_variables VALUES(\"pgsql-%s\",\"%s\")", r->fields[0], val);
db->execute(q);
}
free(val);
}
}
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0], value);
if (strcmp(r->fields[0], (char*)"show_processlist_extended") == 0) {
variables.pgsql_processlist.show_extended = atoi(value);
#ifdef IDLE_THREADS
} else if (strcmp(r->fields[0], (char*)"session_idle_show_processlist") == 0) {
variables.pgsql_processlist.show_idle_session = atoi(value);
#endif // IDLE_THREADS
} else if (strcmp(r->fields[0], (char*)"processlist_max_query_length") == 0) {
variables.pgsql_processlist.max_query_length = atoi(value);
}
}
// }
}
char q[1000];
char* default_client_encoding = GloPTH->get_variable_string((char*)"default_client_encoding");
assert(default_client_encoding);
int charset_encoding = PgSQL_Connection::char_to_encoding(default_client_encoding);
if (charset_encoding == -1) {
// invalid charset_encoding
proxy_error("Found an incorrect value for pgsql-default_client_encoding: %s\n", default_client_encoding);
const char* p = pgsql_tracked_variables[PGSQL_CLIENT_ENCODING].default_value;
charset_encoding = PgSQL_Connection::char_to_encoding(p);
assert(charset_encoding != -1);
proxy_info("Resetting pgsql-default_client_encoding to hardcoded default value: %s\n", p);
sprintf(q, "INSERT OR REPLACE INTO global_variables VALUES(\"pgsql-default_client_encoding\",\"%s\")", p);
db->execute(q);
GloPTH->set_variable((char*)"default_client_encoding", p);
}
free(default_client_encoding);
GloPTH->commit();
GloPTH->wrunlock();
/* Checksums are always generated - 'admin-checksum_*' deprecated
{
// NOTE: 'GloPTH->wrunlock()' should have been called before this point to avoid possible
// deadlocks. See issue #3847.
pthread_mutex_lock(&GloVars.checksum_mutex);
// generate checksum for cluster
flush_mysql_variables___runtime_to_database(admindb, false, false, false, true, true);
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
std::string q;
q = "SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE 'mysql-\%' AND variable_name NOT IN ('mysql-threads')";
if (GloVars.cluster_sync_interfaces == false) {
q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_MYSQL);
}
q += " ORDER BY variable_name";
admindb->execute_statement(q.c_str(), &error, &cols, &affected_rows, &resultset);
uint64_t hash1 = resultset->raw_checksum();
uint32_t d32[2];
char buf[20];
memcpy(&d32, &hash1, sizeof(hash1));
sprintf(buf, "0x%0X%0X", d32[0], d32[1]);
GloVars.checksums_values.mysql_variables.set_checksum(buf);
GloVars.checksums_values.mysql_variables.version++;
time_t t = time(NULL);
if (epoch != 0 && checksum != "" && GloVars.checksums_values.mysql_variables.checksum == checksum) {
GloVars.checksums_values.mysql_variables.epoch = epoch;
}
else {
GloVars.checksums_values.mysql_variables.epoch = t;
}
GloVars.epoch_version = t;
GloVars.generate_global_checksum();
GloVars.checksums_values.updates_cnt++;
pthread_mutex_unlock(&GloVars.checksum_mutex);
delete resultset;
}
proxy_info(
"Computed checksum for 'LOAD MYSQL VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n",
GloVars.checksums_values.mysql_variables.checksum, GloVars.checksums_values.mysql_variables.epoch
);
*/
/**
* @brief Check and warn if TCP keepalive is disabled for PostgreSQL connections.
*
* This safety check warns users when pgsql-use_tcp_keepalive is set to false,
* which can cause connection instability in certain deployment scenarios.
*
* @warning Disabling TCP keepalive is unsafe when ProxySQL is deployed behind:
* - Network load balancers with idle connection timeouts
* - NAT firewalls with connection state timeout
* - Cloud environments with connection pooling
* - Any intermediate network device that drops idle connections
*
* @why_unsafe TCP keepalive sends periodic keep-alive packets on idle connections.
* When disabled for PostgreSQL:
* - Load balancers may drop connections from their connection pools
* - NAT devices may remove connection state from their tables
* - Cloud load balancers (AWS ELB, GCP Load Balancer, etc.) may terminate
* connections during idle periods
* - PostgreSQL connections may appear "stale" to the database server
* - Results in sudden connection failures and "connection reset" errors
* - Can cause application downtime and poor user experience
*
* @note PostgreSQL connections are often long-lived and benefit greatly from
* TCP keepalive, especially in connection-pooled environments.
*
* @recommendation Always set pgsql-use_tcp_keepalive=true when deploying
* behind load balancers or in cloud environments.
*/
// Check for TCP keepalive setting and warn if disabled
int pgsql_use_tcp_keepalive = GloPTH->get_variable_int((char *)"use_tcp_keepalive");
if (pgsql_use_tcp_keepalive == 0) {
proxy_warning("pgsql-use_tcp_keepalive is set to false. This may cause connection drops when ProxySQL is behind a network load balancer. Consider setting this to true.\n");
}
}
if (resultset) delete resultset;
}
#ifdef PROXYSQLGENAI
// GenAI Variables Flush Functions
void ProxySQL_Admin::flush_genai_variables___runtime_to_database(SQLite3DB* db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing GenAI variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (onlyifempty) {
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'genai-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
int matching_rows = 0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
else {
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
matching_rows += atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has GenAI variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting GenAI variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'genai-%'");
}
static char* a;
static char* b;
if (replace) {
a = (char*)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
}
else {
a = (char*)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
}
int rc;
auto [rc1, statement1_unique] = db->prepare_v2(a);
ASSERT_SQLITE_OK(rc1, db);
sqlite3_stmt* statement1 = statement1_unique.get();
sqlite3_stmt* statement2 = NULL;
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'genai-%'");
b = (char*)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(?1, ?2)";
auto [rc2, statement2_unique] = db->prepare_v2(b);
ASSERT_SQLITE_OK(rc2, db);
statement2 = statement2_unique.get();
}
if (use_lock) {
GloGATH->wrlock();
db->execute("BEGIN");
}
char** varnames = GloGATH->get_variables_list();
for (int i = 0; varnames[i]; i++) {
char* val = GloGATH->get_variable(varnames[i]);
char* qualified_name = (char*)malloc(strlen(varnames[i]) + 10);
sprintf(qualified_name, "genai-%s", varnames[i]);
rc = (*proxy_sqlite3_bind_text)(statement1, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_bind_text)(statement1, 2, (val ? val : (char*)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement1);
rc = (*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, db);
if (runtime) {
rc = (*proxy_sqlite3_bind_text)(statement2, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_bind_text)(statement2, 2, (val ? val : (char*)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement2);
rc = (*proxy_sqlite3_clear_bindings)(statement2); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_reset)(statement2); ASSERT_SQLITE_OK(rc, db);
}
if (val)
free(val);
free(qualified_name);
}
if (use_lock) {
db->execute("COMMIT");
GloGATH->wrunlock();
}
for (int i = 0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
void ProxySQL_Admin::flush_genai_variables___database_to_runtime(SQLite3DB* db, bool replace, const std::string& checksum, const time_t epoch, bool lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing GenAI variables. Replace:%d\n", replace);
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT variable_name, variable_value FROM global_variables WHERE variable_name LIKE 'genai-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
if (resultset) {
if (lock) wrlock();
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
char* name = r->fields[0];
char* val = r->fields[1];
// Skip the 'genai-' prefix
char* var_name = name + 6;
GloGATH->set_variable(var_name, val);
}
// Populate runtime_global_variables
{
pthread_mutex_lock(&GloVars.checksum_mutex);
wrunlock(); // Release outer lock before calling runtime_to_database
flush_genai_variables___runtime_to_database(admindb, false, false, false, true, true);
wrlock(); // Re-acquire outer lock
pthread_mutex_unlock(&GloVars.checksum_mutex);
}
// Check if LLM bridge needs to be initialized
if (GloAI && GloGATH->variables.genai_llm_enabled && !GloAI->get_llm_bridge()) {
proxy_info("LLM bridge enabled but not initialized, initializing now\n");
if (GloAI->init_llm_bridge() != 0) {
proxy_error("Failed to initialize LLM bridge\n");
}
}
if (GloAI && GloGATH->variables.genai_enabled) {
GloAI->init();
}
if (lock) wrunlock();
}
if (resultset) delete resultset;
}
#endif /* PROXYSQLGENAI */
void ProxySQL_Admin::flush_mysql_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MySQL variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (onlyifempty) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
char *q=(char *)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'mysql-%'";
db->execute_statement(q, &error , &cols , &affected_rows , &resultset);
int matching_rows=0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
} else {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
matching_rows+=atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has MySQL variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting MySQL variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'mysql-%'");
}
static char *a;
static char *b;
if (replace) {
a=(char *)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
} else {
a=(char *)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)";
}
int rc;
auto [rc1, statement1_unique] = db->prepare_v2(a);
ASSERT_SQLITE_OK(rc1, db);
sqlite3_stmt *statement1 = statement1_unique.get();
sqlite3_stmt *statement2 = NULL;
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'mysql-%'");
b=(char *)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(?1, ?2)";
auto [rc2, statement2_unique] = db->prepare_v2(b);
ASSERT_SQLITE_OK(rc2, db);
statement2 = statement2_unique.get();
}
if (use_lock) {
GloMTH->wrlock();
db->execute("BEGIN");
}
char **varnames=GloMTH->get_variables_list();
for (int i=0; varnames[i]; i++) {
char *val=GloMTH->get_variable(varnames[i]);
char *qualified_name=(char *)malloc(strlen(varnames[i])+7);
sprintf(qualified_name, "mysql-%s", varnames[i]);
rc=(*proxy_sqlite3_bind_text)(statement1, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 2, (val ? val : (char *)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement1);
rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, db);
if (runtime) {
rc=(*proxy_sqlite3_bind_text)(statement2, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement2, 2, (val ? val : (char *)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement2);
rc=(*proxy_sqlite3_clear_bindings)(statement2); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_reset)(statement2); ASSERT_SQLITE_OK(rc, db);
}
if (val)
free(val);
free(qualified_name);
}
if (use_lock) {
db->execute("COMMIT");
GloMTH->wrunlock();
}
for (int i=0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
void ProxySQL_Admin::flush_ldap_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum, const time_t epoch) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing LDAP variables. Replace:%d\n", replace);
if (GloMyLdapAuth == NULL) {
return;
}
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
if (flush_GENERIC_variables__retrieve__database_to_runtime("ldap", error, cols, affected_rows, resultset) == true) {
GloMyLdapAuth->wrlock();
flush_GENERIC_variables__process__database_to_runtime("admin", db, resultset, false, replace, {}, {}, {}, {});
GloMyLdapAuth->wrunlock();
// Checksums are always generated - 'admin-checksum_*' deprecated
{
pthread_mutex_lock(&GloVars.checksum_mutex);
// generate checksum for cluster
flush_ldap_variables___runtime_to_database(admindb, false, false, false, true);
flush_GENERIC_variables__checksum__database_to_runtime("ldap", checksum, epoch);
pthread_mutex_unlock(&GloVars.checksum_mutex);
}
}
if (resultset) delete resultset;
}
void ProxySQL_Admin::flush_ldap_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing LDAP variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (GloMyLdapAuth == NULL) {
return;
}
if (onlyifempty) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
char *q=(char *)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'ldap-%'";
db->execute_statement(q, &error , &cols , &affected_rows , &resultset);
int matching_rows=0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
} else {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
matching_rows+=atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has LDAP variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting LDAP variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'ldap-%'");
}
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'ldap-%'");
}
char *a;
char *b=(char *)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(\"ldap-%s\",\"%s\")";
if (replace) {
a=(char *)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(\"ldap-%s\",\"%s\")";
} else {
a=(char *)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(\"ldap-%s\",\"%s\")";
}
int l=strlen(a)+200;
GloMyLdapAuth->wrlock();
char **varnames=GloMyLdapAuth->get_variables_list();
for (int i=0; varnames[i]; i++) {
char *val=GloMyLdapAuth->get_variable(varnames[i]);
l+=( varnames[i] ? strlen(varnames[i]) : 6);
l+=( val ? strlen(val) : 6);
char *query=(char *)malloc(l);
sprintf(query, a, varnames[i], val);
if (runtime) {
db->execute(query);
sprintf(query, b, varnames[i], val);
}
db->execute(query);
if (val)
free(val);
free(query);
}
GloMyLdapAuth->wrunlock();
for (int i=0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
void ProxySQL_Admin::flush_admin_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ADMIN variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (onlyifempty) {
char *error=NULL;
int cols=0;
int affected_rows=0;
SQLite3_result *resultset=NULL;
char *q=(char *)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'admin-%'";
db->execute_statement(q, &error , &cols , &affected_rows , &resultset);
int matching_rows=0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
} else {
for (std::vector<SQLite3_row *>::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
matching_rows+=atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has ADMIN variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting ADMIN variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'admin-%'");
}
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'admin-%'");
}
char *a;
char *b=(char *)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(\"admin-%s\",\"%s\")";
if (replace) {
a=(char *)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(\"admin-%s\",\"%s\")";
} else {
a=(char *)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(\"admin-%s\",\"%s\")";
}
int l=strlen(a)+200;
char **varnames=get_variables_list();
for (int i=0; varnames[i]; i++) {
char *val=get_variable(varnames[i]);
l+=( varnames[i] ? strlen(varnames[i]) : 6);
l+=( val ? strlen(val) : 6);
char *query=(char *)malloc(l);
sprintf(query, a, varnames[i], val);
db->execute(query);
if (runtime) {
sprintf(query, b, varnames[i], val);
db->execute(query);
}
if (val)
free(val);
free(query);
}
for (int i=0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
#ifdef PROXYSQLGENAI
// MCP (Model Context Protocol) VARIABLES
void ProxySQL_Admin::flush_mcp_variables___database_to_runtime(SQLite3DB* db, bool replace, const std::string& checksum, const time_t epoch, bool lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MCP variables. Replace:%d\n", replace);
if (GloMCPH == NULL) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "MCP handler not initialized, skipping MCP variables\n");
return;
}
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT variable_name, variable_value FROM global_variables WHERE variable_name LIKE 'mcp-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
if (resultset) {
if (lock) wrlock();
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
char* name = r->fields[0];
char* val = r->fields[1];
// Skip the 'mcp-' prefix
char* var_name = name + 4;
GloMCPH->set_variable(var_name, val);
}
// Populate runtime_global_variables
// Note: Checksum generation is skipped for MCP until the feature is complete
{
pthread_mutex_lock(&GloVars.checksum_mutex);
wrunlock(); // Release outer lock before calling runtime_to_database
flush_mcp_variables___runtime_to_database(admindb, false, false, false, true, true);
wrlock(); // Re-acquire outer lock
pthread_mutex_unlock(&GloVars.checksum_mutex);
}
// Manage MCP server state
load_mcp_server();
if (lock) wrunlock();
delete resultset;
}
}
void ProxySQL_Admin::flush_mcp_variables___runtime_to_database(SQLite3DB* db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
proxy_info("MCP: flush_mcp_variables___runtime_to_database called. runtime=%d, use_lock=%d\n", runtime, use_lock);
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MCP variables. Replace:%d, Delete:%d, Only_If_Empty:%d\n", replace, del, onlyifempty);
if (GloMCPH == NULL) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "MCP handler not initialized, skipping MCP variables\n");
return;
}
if (onlyifempty) {
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT COUNT(*) FROM global_variables WHERE variable_name LIKE 'mcp-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
int matching_rows = 0;
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
else {
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
matching_rows += atoi(r->fields[0]);
}
}
if (resultset) delete resultset;
if (matching_rows) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Table global_variables has MCP variables - skipping\n");
return;
}
}
if (del) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Deleting MCP variables from global_variables\n");
db->execute("DELETE FROM global_variables WHERE variable_name LIKE 'mcp-%'");
}
static char* a;
static char* b;
if (replace) {
a = (char*)"REPLACE INTO global_variables(variable_name, variable_value) VALUES(\"mcp-%s\",\"%s\")";
}
else {
a = (char*)"INSERT OR IGNORE INTO global_variables(variable_name, variable_value) VALUES(\"mcp-%s\",\"%s\")";
}
b = (char*)"INSERT INTO runtime_global_variables(variable_name, variable_value) VALUES(\"%s\",\"%s\")";
int rc;
auto [rc1, statement1_unique] = db->prepare_v2("REPLACE INTO global_variables(variable_name, variable_value) VALUES(?1, ?2)");
ASSERT_SQLITE_OK(rc1, db);
sqlite3_stmt* statement1 = statement1_unique.get();
if (use_lock) {
GloMCPH->wrlock();
}
if (runtime) {
db->execute("DELETE FROM runtime_global_variables WHERE variable_name LIKE 'mcp-%'");
}
char** varnames = GloMCPH->get_variables_list();
int var_count = 0;
for (int i = 0; varnames[i]; i++) {
var_count++;
}
proxy_info("MCP: Processing %d variables\n", var_count);
for (int i = 0; varnames[i]; i++) {
char val[256];
GloMCPH->get_variable(varnames[i], val);
char* qualified_name = (char*)malloc(strlen(varnames[i]) + 8);
sprintf(qualified_name, "mcp-%s", varnames[i]);
rc = (*proxy_sqlite3_bind_text)(statement1, 1, qualified_name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_bind_text)(statement1, 2, (val ? val : (char*)""), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement1);
rc = (*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db);
rc = (*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, db);
if (runtime) {
if (i < 3) {
proxy_info("MCP: Inserting variable %d: %s = %s\n", i, qualified_name, val);
}
// Use db->execute() for runtime_global_variables like admin version does
// qualified_name already contains the mcp- prefix, so we use %s without prefix
int l = strlen(qualified_name) + strlen(val) + 100;
char* query = (char*)malloc(l);
sprintf(query, b, qualified_name, val);
if (i < 3) {
proxy_info("MCP: Executing SQL: %s\n", query);
}
db->execute(query);
free(query);
}
free(qualified_name);
}
proxy_info("MCP: Finished processing %d variables\n", var_count);
if (use_lock) {
proxy_info("MCP: Releasing lock\n");
GloMCPH->wrunlock();
}
for (int i = 0; varnames[i]; i++) {
free(varnames[i]);
}
free(varnames);
}
#endif /* PROXYSQLGENAI */