mirror of https://github.com/sysown/proxysql
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.
435 lines
15 KiB
435 lines
15 KiB
#include "PgSQL_Variables.h"
|
|
#include "proxysql.h"
|
|
|
|
#include "PgSQL_Session.h"
|
|
#include "PgSQL_Data_Stream.h"
|
|
#ifndef SPOOKYV2
|
|
#include "SpookyV2.h"
|
|
#define SPOOKYV2
|
|
#endif
|
|
|
|
#include <sstream>
|
|
|
|
|
|
/*static inline char is_digit(char c) {
|
|
if(c >= '0' && c <= '9')
|
|
return 1;
|
|
return 0;
|
|
}*/
|
|
|
|
pgsql_verify_var PgSQL_Variables::verifiers[PGSQL_NAME_LAST_HIGH_WM];
|
|
pgsql_update_var PgSQL_Variables::updaters[PGSQL_NAME_LAST_HIGH_WM];
|
|
|
|
|
|
PgSQL_Variables::PgSQL_Variables() {
|
|
// add here all the variables we want proxysql to recognize, but ignore
|
|
ignore_vars.push_back("application_name");
|
|
//ignore_vars.push_back("interactive_timeout");
|
|
//ignore_vars.push_back("wait_timeout");
|
|
//ignore_vars.push_back("net_read_timeout");
|
|
//ignore_vars.push_back("net_write_timeout");
|
|
//ignore_vars.push_back("net_buffer_length");
|
|
//ignore_vars.push_back("read_buffer_size");
|
|
//ignore_vars.push_back("read_rnd_buffer_size");
|
|
// NOTE: This variable has been temporarily ignored. Check issues #3442 and #3441.
|
|
//ignore_vars.push_back("session_track_schema");
|
|
variables_regexp = "";
|
|
for (auto i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
// we initialized all the internal_variable_name if set to NULL
|
|
if (pgsql_tracked_variables[i].internal_variable_name == NULL) {
|
|
pgsql_tracked_variables[i].internal_variable_name = pgsql_tracked_variables[i].set_variable_name;
|
|
}
|
|
}
|
|
/*
|
|
NOTE:
|
|
make special ATTENTION that the order in pgsql_variable_name
|
|
and pgsqll_tracked_variables[] is THE SAME
|
|
NOTE:
|
|
PgSQL_Variables::PgSQL_Variables() has a built-in check to make sure that the order is correct,
|
|
and that variables are in alphabetical order
|
|
*/
|
|
for (int i = PGSQL_NAME_LAST_LOW_WM; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
assert(i == pgsql_tracked_variables[i].idx);
|
|
if (i > PGSQL_NAME_LAST_LOW_WM+1) {
|
|
assert(strcmp(pgsql_tracked_variables[i].set_variable_name, pgsql_tracked_variables[i-1].set_variable_name) > 0);
|
|
}
|
|
}
|
|
for (auto i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
if (i == PGSQL_CLIENT_ENCODING) {
|
|
PgSQL_Variables::updaters[i] = NULL;
|
|
PgSQL_Variables::verifiers[i] = NULL;
|
|
} else {
|
|
PgSQL_Variables::verifiers[i] = verify_server_variable;
|
|
PgSQL_Variables::updaters[i] = update_server_variable;
|
|
}
|
|
if (pgsql_tracked_variables[i].status == SETTING_VARIABLE) {
|
|
variables_regexp += pgsql_tracked_variables[i].set_variable_name;
|
|
variables_regexp += "|";
|
|
|
|
int idx = 0;
|
|
while (pgsql_tracked_variables[i].alias[idx]) {
|
|
variables_regexp += pgsql_tracked_variables[i].alias[idx];
|
|
variables_regexp += "|";
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
for (std::vector<std::string>::iterator it=ignore_vars.begin(); it != ignore_vars.end(); it++) {
|
|
variables_regexp += *it;
|
|
variables_regexp += "|";
|
|
}
|
|
|
|
// Check if the last character is '|'
|
|
if (!variables_regexp.empty() && variables_regexp.back() == '|') {
|
|
variables_regexp.pop_back(); // Remove the last character
|
|
}
|
|
}
|
|
|
|
PgSQL_Variables::~PgSQL_Variables() {}
|
|
|
|
bool PgSQL_Variables::client_set_hash_and_value(PgSQL_Session* session, int idx, const std::string& value, uint32_t hash) {
|
|
if (!session || !session->client_myds || !session->client_myds->myconn) {
|
|
proxy_warning("Session validation failed\n");
|
|
return false;
|
|
}
|
|
|
|
session->client_myds->myconn->var_hash[idx] = hash;
|
|
if (session->client_myds->myconn->variables[idx].value) {
|
|
free(session->client_myds->myconn->variables[idx].value);
|
|
}
|
|
session->client_myds->myconn->variables[idx].value = strdup(value.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
void PgSQL_Variables::client_reset_value(PgSQL_Session* session, int idx) {
|
|
if (!session || !session->client_myds || !session->client_myds->myconn) {
|
|
proxy_warning("Session validation failed\n");
|
|
return;
|
|
}
|
|
|
|
PgSQL_Connection *client_conn = session->client_myds->myconn;
|
|
|
|
if (client_conn->var_hash[idx] != 0) {
|
|
client_conn->var_hash[idx] = 0;
|
|
if (client_conn->variables[idx].value) {
|
|
free(client_conn->variables[idx].value);
|
|
client_conn->variables[idx].value = NULL;
|
|
}
|
|
if (idx > PGSQL_NAME_LAST_LOW_WM) {
|
|
// we now regererate dynamic_variables_idx
|
|
client_conn->reorder_dynamic_variables_idx();
|
|
}
|
|
}
|
|
}
|
|
void PgSQL_Variables::server_set_hash_and_value(PgSQL_Session* session, int idx, const char* value, uint32_t hash) {
|
|
if (!session || !session->mybe || !session->mybe->server_myds || !session->mybe->server_myds->myconn || !value) {
|
|
proxy_warning("Session validation failed\n");
|
|
return;
|
|
}
|
|
|
|
session->mybe->server_myds->myconn->var_hash[idx] = hash;
|
|
if (session->mybe->server_myds->myconn->variables[idx].value) {
|
|
free(session->mybe->server_myds->myconn->variables[idx].value);
|
|
}
|
|
session->mybe->server_myds->myconn->variables[idx].value = strdup(value);
|
|
}
|
|
|
|
bool PgSQL_Variables::client_set_value(PgSQL_Session* session, int idx, const std::string& value) {
|
|
if (!session || !session->client_myds || !session->client_myds->myconn) {
|
|
proxy_warning("Session validation failed\n");
|
|
return false;
|
|
}
|
|
|
|
session->client_myds->myconn->var_hash[idx] = SpookyHash::Hash32(value.c_str(),strlen(value.c_str()),10);
|
|
if (session->client_myds->myconn->variables[idx].value) {
|
|
free(session->client_myds->myconn->variables[idx].value);
|
|
}
|
|
session->client_myds->myconn->variables[idx].value = strdup(value.c_str());
|
|
|
|
if (idx > PGSQL_NAME_LAST_LOW_WM) {
|
|
// we now regererate dynamic_variables_idx
|
|
session->client_myds->myconn->reorder_dynamic_variables_idx();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char* PgSQL_Variables::client_get_value(PgSQL_Session* session, int idx) const {
|
|
assert(session);
|
|
assert(session->client_myds);
|
|
assert(session->client_myds->myconn);
|
|
return session->client_myds->myconn->variables[idx].value;
|
|
}
|
|
|
|
uint32_t PgSQL_Variables::client_get_hash(PgSQL_Session* session, int idx) const {
|
|
assert(session);
|
|
assert(session->client_myds);
|
|
assert(session->client_myds->myconn);
|
|
return session->client_myds->myconn->var_hash[idx];
|
|
}
|
|
|
|
void PgSQL_Variables::server_set_value(PgSQL_Session* session, int idx, const char* value) {
|
|
assert(session);
|
|
assert(session->mybe);
|
|
assert(session->mybe->server_myds);
|
|
assert(session->mybe->server_myds->myconn);
|
|
if (!value) return; // FIXME: I am not sure about this implementation . If value == NULL , show the variable be reset?
|
|
session->mybe->server_myds->myconn->var_hash[idx] = SpookyHash::Hash32(value,strlen(value),10);
|
|
|
|
if (session->mybe->server_myds->myconn->variables[idx].value) {
|
|
free(session->mybe->server_myds->myconn->variables[idx].value);
|
|
}
|
|
session->mybe->server_myds->myconn->variables[idx].value = strdup(value);
|
|
|
|
if (idx > PGSQL_NAME_LAST_LOW_WM) {
|
|
// we now regererate dynamic_variables_idx
|
|
session->mybe->server_myds->myconn->reorder_dynamic_variables_idx();
|
|
}
|
|
}
|
|
|
|
void PgSQL_Variables::server_reset_value(PgSQL_Session* session, int idx) {
|
|
assert(session);
|
|
assert(session->mybe);
|
|
assert(session->mybe->server_myds);
|
|
assert(session->mybe->server_myds->myconn);
|
|
|
|
PgSQL_Connection *backend_conn = session->mybe->server_myds->myconn;
|
|
|
|
if (backend_conn->var_hash[idx] != 0) {
|
|
backend_conn->var_hash[idx] = 0;
|
|
if (backend_conn->variables[idx].value) {
|
|
free(backend_conn->variables[idx].value);
|
|
backend_conn->variables[idx].value = NULL;
|
|
}
|
|
if (idx > PGSQL_NAME_LAST_LOW_WM) {
|
|
// we now regererate dynamic_variables_idx
|
|
backend_conn->reorder_dynamic_variables_idx();
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* PgSQL_Variables::server_get_value(PgSQL_Session* session, int idx) const {
|
|
assert(session);
|
|
assert(session->mybe);
|
|
assert(session->mybe->server_myds);
|
|
assert(session->mybe->server_myds->myconn);
|
|
return session->mybe->server_myds->myconn->variables[idx].value;
|
|
}
|
|
|
|
uint32_t PgSQL_Variables::server_get_hash(PgSQL_Session* session, int idx) const {
|
|
assert(session);
|
|
assert(session->mybe);
|
|
assert(session->mybe->server_myds);
|
|
assert(session->mybe->server_myds->myconn);
|
|
return session->mybe->server_myds->myconn->var_hash[idx];
|
|
}
|
|
|
|
bool PgSQL_Variables::update_variable(PgSQL_Session* session, session_status status, int &_rc) {
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
if (session->status == SETTING_VARIABLE) {
|
|
// if status is SETTING_VARIABLE , what variable needs to be changed is defined in changing_variable_idx
|
|
idx = session->changing_variable_idx;
|
|
} else {
|
|
for (int i=0; i<PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
if (pgsql_tracked_variables[i].status == status) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert(idx != PGSQL_NAME_LAST_HIGH_WM);
|
|
return updaters[idx](session, idx, _rc);
|
|
}
|
|
|
|
bool PgSQL_Variables::verify_variable(PgSQL_Session* session, int idx) const {
|
|
auto ret = false;
|
|
if (likely(verifiers[idx])) {
|
|
auto client_hash = session->client_myds->myconn->var_hash[idx];
|
|
auto server_hash = session->mybe->server_myds->myconn->var_hash[idx];
|
|
if (client_hash && client_hash != server_hash) {
|
|
ret = verifiers[idx](session, idx, client_hash, server_hash);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool validate_charset(PgSQL_Session* session, int idx, int &_rc) {
|
|
/*if (idx == PGSQL_CLIENT_ENCODING || idx == PGSQL_SET_NAMES) {
|
|
PgSQL_Data_Stream *myds = session->mybe->server_myds;
|
|
PgSQL_Connection *myconn = myds->myconn;
|
|
char msg[128];
|
|
const MARIADB_CHARSET_INFO *ci = NULL;
|
|
const char* replace_collation = NULL;
|
|
const char* not_supported_collation = NULL;
|
|
unsigned int replace_collation_nr = 0;
|
|
std::stringstream ss;
|
|
int charset = atoi(pgsql_variables.client_get_value(session, idx));
|
|
if (charset >= 255 && myconn->pgsql->server_version[0] != '8') {
|
|
switch(pgsql_thread___handle_unknown_charset) {
|
|
case HANDLE_UNKNOWN_CHARSET__DISCONNECT_CLIENT:
|
|
snprintf(msg,sizeof(msg),"Can't initialize character set %s", pgsql_variables.client_get_value(session, idx));
|
|
proxy_error("Can't initialize character set on %s, %d: Error %d (%s). Closing client connection %s:%d.\n",
|
|
myconn->parent->address, myconn->parent->port, 2019, msg, session->client_myds->addr.addr, session->client_myds->addr.port);
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
myds->fd=0;
|
|
_rc=-1;
|
|
return false;
|
|
case HANDLE_UNKNOWN_CHARSET__REPLACE_WITH_DEFAULT_VERBOSE:
|
|
ci = proxysql_find_charset_nr(charset);
|
|
if (!ci) {
|
|
// LCOV_EXCL_START
|
|
proxy_error("Cannot find character set [%s]\n", pgsql_variables.client_get_value(session, idx));
|
|
assert(0);
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
not_supported_collation = ci->name;
|
|
|
|
if (idx == SQL_COLLATION_CONNECTION) {
|
|
ci = proxysql_find_charset_collate(pgsql_thread___default_variables[idx]);
|
|
} else {
|
|
if (pgsql_thread___default_variables[idx]) {
|
|
ci = proxysql_find_charset_name(pgsql_thread___default_variables[idx]);
|
|
} else {
|
|
ci = proxysql_find_charset_name(pgsql_thread___default_variables[SQL_CHARACTER_SET]);
|
|
}
|
|
}
|
|
|
|
if (!ci) {
|
|
// LCOV_EXCL_START
|
|
proxy_error("Cannot find character set [%s]\n", pgsql_thread___default_variables[idx]);
|
|
assert(0);
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
replace_collation = ci->name;
|
|
replace_collation_nr = ci->nr;
|
|
|
|
proxy_warning("Server doesn't support collation (%s) %s. Replacing it with the configured default (%d) %s. Client %s:%d\n",
|
|
pgsql_variables.client_get_value(session, idx), not_supported_collation,
|
|
replace_collation_nr, replace_collation, session->client_myds->addr.addr, session->client_myds->addr.port);
|
|
|
|
ss << replace_collation_nr;
|
|
pgsql_variables.client_set_value(session, idx, ss.str());
|
|
_rc=0;
|
|
return true;
|
|
case HANDLE_UNKNOWN_CHARSET__REPLACE_WITH_DEFAULT:
|
|
if (idx == SQL_COLLATION_CONNECTION) {
|
|
ci = proxysql_find_charset_collate(pgsql_thread___default_variables[idx]);
|
|
} else {
|
|
if (pgsql_thread___default_variables[idx]) {
|
|
ci = proxysql_find_charset_name(pgsql_thread___default_variables[idx]);
|
|
} else {
|
|
ci = proxysql_find_charset_name(pgsql_thread___default_variables[SQL_CHARACTER_SET]);
|
|
}
|
|
}
|
|
|
|
if (!ci) {
|
|
// LCOV_EXCL_START
|
|
proxy_error("Cannot filnd charset [%s]\n", pgsql_thread___default_variables[idx]);
|
|
assert(0);
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
replace_collation_nr = ci->nr;
|
|
|
|
ss << replace_collation_nr;
|
|
pgsql_variables.client_set_value(session, idx, ss.str());
|
|
_rc=0;
|
|
return true;
|
|
default:
|
|
proxy_error("Wrong configuration of the handle_unknown_charset\n");
|
|
_rc=-1;
|
|
return false;
|
|
}
|
|
}
|
|
}*/
|
|
_rc=0;
|
|
return true;
|
|
}
|
|
|
|
bool update_server_variable(PgSQL_Session* session, int idx, int &_rc) {
|
|
bool no_quote = true;
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_QUOTE(pgsql_tracked_variables[idx])) no_quote = false;
|
|
bool st = IS_PGTRACKED_VAR_OPTION_SET_SET_TRANSACTION(pgsql_tracked_variables[idx]);
|
|
const char *set_var_name = pgsql_tracked_variables[idx].set_variable_name;
|
|
bool ret = false;
|
|
|
|
if (!validate_charset(session, idx, _rc)) {
|
|
return false;
|
|
}
|
|
|
|
const char* value = pgsql_variables.client_get_value(session, idx);
|
|
pgsql_variables.server_set_value(session, idx, value);
|
|
ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, value, no_quote, st);
|
|
return ret;
|
|
}
|
|
|
|
bool verify_set_names(PgSQL_Session* session) {
|
|
uint32_t client_charset_hash = pgsql_variables.client_get_hash(session, PGSQL_CLIENT_ENCODING);
|
|
if (client_charset_hash == 0)
|
|
return false;
|
|
|
|
if (client_charset_hash != pgsql_variables.server_get_hash(session, PGSQL_CLIENT_ENCODING)) {
|
|
switch(session->status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility
|
|
case PROCESSING_QUERY:
|
|
session->previous_status.push(PROCESSING_QUERY);
|
|
break;
|
|
/*
|
|
case PROCESSING_STMT_PREPARE:
|
|
session->previous_status.push(PROCESSING_STMT_PREPARE);
|
|
break;
|
|
case PROCESSING_STMT_EXECUTE:
|
|
session->previous_status.push(PROCESSING_STMT_EXECUTE);
|
|
break;
|
|
*/
|
|
default:
|
|
// LCOV_EXCL_START
|
|
proxy_error("Wrong status %d\n", session->status);
|
|
assert(0);
|
|
break;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
session->set_status(SETTING_CHARSET);
|
|
const char* client_charset_value = pgsql_variables.client_get_value(session, PGSQL_CLIENT_ENCODING);
|
|
pgsql_variables.server_set_hash_and_value(session, PGSQL_CLIENT_ENCODING, client_charset_value, client_charset_hash);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool verify_server_variable(PgSQL_Session* session, int idx, uint32_t client_hash, uint32_t server_hash) {
|
|
if (client_hash && client_hash != server_hash) {
|
|
// Edge case for set charset command, because we do not know database character set
|
|
// for now we are setting connection and collation to empty
|
|
//if (idx == SQL_CHARACTER_SET_CONNECTION || idx == SQL_COLLATION_CONNECTION ) {
|
|
// if (pgsql_variables.client_get_hash(session, idx) == 0) {
|
|
// pgsql_variables.server_set_hash_and_value(session, idx, "", 0);
|
|
// return false;
|
|
// }
|
|
//}
|
|
// this variable is relevant only if status == SETTING_VARIABLE
|
|
session->changing_variable_idx = (enum pgsql_variable_name)idx;
|
|
switch(session->status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility
|
|
case PROCESSING_QUERY:
|
|
session->previous_status.push(PROCESSING_QUERY);
|
|
break;
|
|
/*
|
|
case PROCESSING_STMT_PREPARE:
|
|
session->previous_status.push(PROCESSING_STMT_PREPARE);
|
|
break;
|
|
case PROCESSING_STMT_EXECUTE:
|
|
session->previous_status.push(PROCESSING_STMT_EXECUTE);
|
|
break;
|
|
*/
|
|
default:
|
|
// LCOV_EXCL_START
|
|
proxy_error("Wrong status %d\n", session->status);
|
|
assert(0);
|
|
break;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
session->set_status(pgsql_tracked_variables[idx].status);
|
|
pgsql_variables.server_set_value(session, idx, pgsql_variables.client_get_value(session, idx));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|