|
|
#include "../deps/json/json.hpp"
|
|
|
using json = nlohmann::json;
|
|
|
#define PROXYJSON
|
|
|
|
|
|
#include "PgSQL_HostGroups_Manager.h"
|
|
|
#include "PgSQL_Thread.h"
|
|
|
#include "proxysql.h"
|
|
|
#include "cpp.h"
|
|
|
#include "proxysql_utils.h"
|
|
|
#include "re2/re2.h"
|
|
|
#include "re2/regexp.h"
|
|
|
#include "mysqld_error.h"
|
|
|
|
|
|
#include "PgSQL_Data_Stream.h"
|
|
|
#include "MySQL_Data_Stream.h"
|
|
|
#include "PgSQL_Query_Processor.h"
|
|
|
#include "MySQL_PreparedStatement.h"
|
|
|
#include "PgSQL_Logger.hpp"
|
|
|
#include "StatCounters.h"
|
|
|
#include "PgSQL_Authentication.h"
|
|
|
#include "MySQL_LDAP_Authentication.hpp"
|
|
|
#include "MySQL_Protocol.h"
|
|
|
#include "SQLite3_Server.h"
|
|
|
#include "PgSQL_Variables.h"
|
|
|
#include "ProxySQL_Cluster.hpp"
|
|
|
#include "PgSQL_Query_Cache.h"
|
|
|
#include "PgSQL_Variables_Validator.h"
|
|
|
|
|
|
#include "libinjection.h"
|
|
|
#include "libinjection_sqli.h"
|
|
|
|
|
|
#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_STATUS "select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1"
|
|
|
#define SELECT_CHARSET_STATUS_LEN 115
|
|
|
#define PROXYSQL_VERSION_COMMENT "\x01\x00\x00\x01\x01\x27\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x11\x40\x40\x76\x65\x72\x73\x69\x6f\x6e\x5f\x63\x6f\x6d\x6d\x65\x6e\x74\x00\x0c\x21\x00\x18\x00\x00\x00\xfd\x00\x00\x1f\x00\x00\x05\x00\x00\x03\xfe\x00\x00\x02\x00\x0b\x00\x00\x04\x0a(ProxySQL)\x05\x00\x00\x05\xfe\x00\x00\x02\x00"
|
|
|
#define PROXYSQL_VERSION_COMMENT_LEN 81
|
|
|
|
|
|
// PROXYSQL_VERSION_COMMENT_WITH_OK is sent instead of PROXYSQL_VERSION_COMMENT
|
|
|
// if Client supports CLIENT_DEPRECATE_EOF
|
|
|
#define PROXYSQL_VERSION_COMMENT_WITH_OK "\x01\x00\x00\x01\x01" \
|
|
|
"\x27\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x11\x40\x40\x76\x65\x72\x73\x69\x6f\x6e\x5f\x63\x6f\x6d\x6d\x65\x6e\x74\x00\x0c\x21\x00\x18\x00\x00\x00\xfd\x00\x00\x1f\x00\x00" \
|
|
|
"\x0b\x00\x00\x03\x0a(ProxySQL)" \
|
|
|
"\x07\x00\x00\x04\xfe\x00\x00\x02\x00\x00\x00"
|
|
|
#define PROXYSQL_VERSION_COMMENT_WITH_OK_LEN 74
|
|
|
|
|
|
#define SELECT_CONNECTION_ID "SELECT CONNECTION_ID()"
|
|
|
#define SELECT_CONNECTION_ID_LEN 22
|
|
|
#define SELECT_LAST_INSERT_ID "SELECT LAST_INSERT_ID()"
|
|
|
#define SELECT_LAST_INSERT_ID_LEN 23
|
|
|
#define SELECT_LAST_INSERT_ID_LIMIT1 "SELECT LAST_INSERT_ID() LIMIT 1"
|
|
|
#define SELECT_LAST_INSERT_ID_LIMIT1_LEN 31
|
|
|
#define SELECT_VARIABLE_IDENTITY "SELECT @@IDENTITY"
|
|
|
#define SELECT_VARIABLE_IDENTITY_LEN 17
|
|
|
#define SELECT_VARIABLE_IDENTITY_LIMIT1 "SELECT @@IDENTITY LIMIT 1"
|
|
|
#define SELECT_VARIABLE_IDENTITY_LIMIT1_LEN 25
|
|
|
|
|
|
#define EXPMARIA
|
|
|
|
|
|
using std::function;
|
|
|
using std::vector;
|
|
|
|
|
|
/*
|
|
|
static inline char is_digit(char c) {
|
|
|
if (c >= '0' && c <= '9')
|
|
|
return 1;
|
|
|
return 0;
|
|
|
}
|
|
|
static inline char is_normal_char(char c) {
|
|
|
if (c >= 'a' && c <= 'z')
|
|
|
return 1;
|
|
|
if (c >= 'A' && c <= 'Z')
|
|
|
return 1;
|
|
|
if (c >= '0' && c <= '9')
|
|
|
return 1;
|
|
|
if (c == '$' || c == '_')
|
|
|
return 1;
|
|
|
return 0;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
static const std::array<std::string,6> pgsql_critical_variables = {
|
|
|
"client_encoding",
|
|
|
"datestyle",
|
|
|
"intervalstyle",
|
|
|
"standard_conforming_strings",
|
|
|
"timezone",
|
|
|
"time zone"
|
|
|
};
|
|
|
|
|
|
static const std::set<std::string> pgsql_other_variables = {
|
|
|
"allow_in_place_tablespaces",
|
|
|
"bytea_output",
|
|
|
"client_min_messages",
|
|
|
"enable_bitmapscan",
|
|
|
"enable_hashjoin",
|
|
|
"enable_indexscan",
|
|
|
"enable_nestloop",
|
|
|
"enable_seqscan",
|
|
|
"enable_sort",
|
|
|
"escape_string_warning",
|
|
|
"extra_float_digits",
|
|
|
"maintenance_work_mem",
|
|
|
"synchronous_commit"
|
|
|
};
|
|
|
|
|
|
#include "proxysql_find_charset.h"
|
|
|
|
|
|
extern PgSQL_Authentication* GloPgAuth;
|
|
|
extern MySQL_LDAP_Authentication* GloMyLdapAuth;
|
|
|
extern ProxySQL_Admin* GloAdmin;
|
|
|
extern PgSQL_Logger* GloPgSQL_Logger;
|
|
|
extern MySQL_STMT_Manager_v14* GloMyStmt;
|
|
|
|
|
|
extern SQLite3_Server* GloSQLite3Server;
|
|
|
|
|
|
#ifdef PROXYSQLCLICKHOUSE
|
|
|
extern ClickHouse_Authentication* GloClickHouseAuth;
|
|
|
extern ClickHouse_Server* GloClickHouseServer;
|
|
|
#endif /* PROXYSQLCLICKHOUSE */
|
|
|
|
|
|
/*
|
|
|
std::string proxysql_session_type_str(enum proxysql_session_type session_type) {
|
|
|
if (session_type == PROXYSQL_SESSION_MYSQL) {
|
|
|
return "PROXYSQL_SESSION_MYSQL";d:
|
|
|
|
|
|
} else if (session_type == PROXYSQL_SESSION_ADMIN) {
|
|
|
return "PROXYSQL_SESSION_ADMIN";
|
|
|
} else if (session_type == PROXYSQL_SESSION_STATS) {
|
|
|
return "PROXYSQL_SESSION_STATS";
|
|
|
} else if (session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
return "PROXYSQL_SESSION_SQLITE";
|
|
|
} else if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
|
|
|
return "PROXYSQL_SESSION_CLICKHOUSE";
|
|
|
} else if (session_type == PROXYSQL_SESSION_MYSQL_EMU) {
|
|
|
return "PROXYSQL_SESSION_MYSQL_EMU";
|
|
|
} else {
|
|
|
return "PROXYSQL_SESSION_NONE";
|
|
|
}
|
|
|
};*/
|
|
|
|
|
|
/*
|
|
|
Session_Regex::Session_Regex(char *p) {
|
|
|
s=strdup(p);
|
|
|
re2::RE2::Options *opt2=new re2::RE2::Options(RE2::Quiet);
|
|
|
opt2->set_case_sensitive(false);
|
|
|
opt=(void *)opt2;
|
|
|
re=(RE2 *)new RE2(s, *opt2);
|
|
|
}
|
|
|
|
|
|
PgSQL_Session_Regex::~PgSQL_Session_Regex() {
|
|
|
free(s);
|
|
|
delete (RE2 *)re;
|
|
|
delete (re2::RE2::Options *)opt;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session_Regex::match(char *m) {
|
|
|
bool rc=false;
|
|
|
rc=RE2::PartialMatch(m,*(RE2 *)re);
|
|
|
return rc;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
PgSQL_KillArgs::PgSQL_KillArgs(char* u, char* p, char* h, unsigned int P, unsigned int _hid, int i, int kt, int _use_ssl, PgSQL_Thread* _mt) :
|
|
|
PgSQL_KillArgs(u, p, h, P, _hid, i, kt, _use_ssl, _mt, NULL) {
|
|
|
// resolving DNS if available in Cache
|
|
|
if (h && P) {
|
|
|
const std::string& ip = MySQL_Monitor::dns_lookup(h, false);
|
|
|
|
|
|
if (ip.empty() == false) {
|
|
|
ip_addr = strdup(ip.c_str());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
PgSQL_KillArgs::PgSQL_KillArgs(char* u, char* p, char* h, unsigned int P, unsigned int _hid, int i, int kt, int _use_ssl, PgSQL_Thread* _mt, char* ip) {
|
|
|
username = strdup(u);
|
|
|
password = strdup(p);
|
|
|
hostname = strdup(h);
|
|
|
ip_addr = NULL;
|
|
|
if (ip)
|
|
|
ip_addr = strdup(ip);
|
|
|
port = P;
|
|
|
hid = _hid;
|
|
|
id = i;
|
|
|
kill_type = kt;
|
|
|
use_ssl = _use_ssl;
|
|
|
mt = _mt;
|
|
|
}
|
|
|
|
|
|
PgSQL_KillArgs::~PgSQL_KillArgs() {
|
|
|
free(username);
|
|
|
free(password);
|
|
|
free(hostname);
|
|
|
if (ip_addr)
|
|
|
free(ip_addr);
|
|
|
}
|
|
|
|
|
|
const char* PgSQL_KillArgs::get_host_address() const {
|
|
|
const char* host_address = hostname;
|
|
|
|
|
|
if (ip_addr)
|
|
|
host_address = ip_addr;
|
|
|
|
|
|
return host_address;
|
|
|
}
|
|
|
|
|
|
void* PgSQL_kill_query_thread(void* arg) {
|
|
|
PgSQL_KillArgs* ka = (PgSQL_KillArgs*)arg;
|
|
|
std::unique_ptr<MySQL_Thread> mysql_thr(new MySQL_Thread());
|
|
|
mysql_thr->curtime = monotonic_time();
|
|
|
mysql_thr->refresh_variables();
|
|
|
MYSQL* pgsql = mysql_init(NULL);
|
|
|
mysql_options4(pgsql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "proxysql_killer");
|
|
|
mysql_options4(pgsql, MYSQL_OPT_CONNECT_ATTR_ADD, "_server_host", ka->hostname);
|
|
|
|
|
|
if (ka->use_ssl && ka->port) {
|
|
|
mysql_ssl_set(pgsql,
|
|
|
pgsql_thread___ssl_p2s_key,
|
|
|
pgsql_thread___ssl_p2s_cert,
|
|
|
pgsql_thread___ssl_p2s_ca,
|
|
|
pgsql_thread___ssl_p2s_capath,
|
|
|
pgsql_thread___ssl_p2s_cipher);
|
|
|
mysql_options(pgsql, MYSQL_OPT_SSL_CRL, pgsql_thread___ssl_p2s_crl);
|
|
|
mysql_options(pgsql, MYSQL_OPT_SSL_CRLPATH, pgsql_thread___ssl_p2s_crlpath);
|
|
|
mysql_options(pgsql, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback);
|
|
|
}
|
|
|
|
|
|
if (!pgsql) {
|
|
|
goto __exit_kill_query_thread;
|
|
|
}
|
|
|
MYSQL* ret;
|
|
|
if (ka->port) {
|
|
|
switch (ka->kill_type) {
|
|
|
case KILL_QUERY:
|
|
|
proxy_warning("KILL QUERY %d on %s:%d\n", ka->id, ka->hostname, ka->port);
|
|
|
if (ka->mt) {
|
|
|
ka->mt->status_variables.stvar[st_var_killed_queries]++;
|
|
|
}
|
|
|
break;
|
|
|
case KILL_CONNECTION:
|
|
|
proxy_warning("KILL CONNECTION %d on %s:%d\n", ka->id, ka->hostname, ka->port);
|
|
|
if (ka->mt) {
|
|
|
ka->mt->status_variables.stvar[st_var_killed_connections]++;
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
ret = mysql_real_connect(pgsql, ka->get_host_address(), ka->username, ka->password, NULL, ka->port, NULL, 0);
|
|
|
}
|
|
|
else {
|
|
|
switch (ka->kill_type) {
|
|
|
case KILL_QUERY:
|
|
|
proxy_warning("KILL QUERY %d on localhost\n", ka->id);
|
|
|
break;
|
|
|
case KILL_CONNECTION:
|
|
|
proxy_warning("KILL CONNECTION %d on localhost\n", ka->id);
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
ret = mysql_real_connect(pgsql, "localhost", ka->username, ka->password, NULL, 0, ka->hostname, 0);
|
|
|
}
|
|
|
if (!ret) {
|
|
|
proxy_error("Failed to connect to server %s:%d to run KILL %s %d: Error: %s\n", ka->hostname, ka->port, (ka->kill_type == KILL_QUERY ? "QUERY" : "CONNECTION"), ka->id, mysql_error(pgsql));
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::pgsql, ka->hid, ka->hostname, ka->port, mysql_errno(pgsql));
|
|
|
goto __exit_kill_query_thread;
|
|
|
}
|
|
|
|
|
|
MySQL_Monitor::update_dns_cache_from_mysql_conn(pgsql);
|
|
|
|
|
|
char buf[100];
|
|
|
switch (ka->kill_type) {
|
|
|
case KILL_QUERY:
|
|
|
sprintf(buf, "KILL QUERY %d", ka->id);
|
|
|
break;
|
|
|
case KILL_CONNECTION:
|
|
|
sprintf(buf, "KILL CONNECTION %d", ka->id);
|
|
|
break;
|
|
|
default:
|
|
|
sprintf(buf, "KILL %d", ka->id);
|
|
|
break;
|
|
|
}
|
|
|
// FIXME: these 2 calls are blocking, fortunately on their own thread
|
|
|
mysql_query(pgsql, buf);
|
|
|
__exit_kill_query_thread:
|
|
|
if (pgsql)
|
|
|
mysql_close(pgsql);
|
|
|
delete ka;
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
extern PgSQL_Query_Processor* GloPgQPro;
|
|
|
extern PgSQL_Query_Cache *GloPgQC;
|
|
|
extern ProxySQL_Admin* GloAdmin;
|
|
|
extern PgSQL_Threads_Handler* GloPTH;
|
|
|
|
|
|
PgSQL_Query_Info::PgSQL_Query_Info() {
|
|
|
PgQueryCmd=PGSQL_QUERY___NONE;
|
|
|
QueryPointer=NULL;
|
|
|
QueryLength=0;
|
|
|
QueryParserArgs.digest_text=NULL;
|
|
|
QueryParserArgs.first_comment=NULL;
|
|
|
stmt_info=NULL;
|
|
|
bool_is_select_NOT_for_update=false;
|
|
|
bool_is_select_NOT_for_update_computed=false;
|
|
|
have_affected_rows=false; // if affected rows is set, last_insert_id is set too
|
|
|
waiting_since = 0;
|
|
|
affected_rows=0;
|
|
|
last_insert_id = 0;
|
|
|
rows_sent=0;
|
|
|
start_time=0;
|
|
|
end_time=0;
|
|
|
stmt_client_id=0;
|
|
|
}
|
|
|
|
|
|
PgSQL_Query_Info::~PgSQL_Query_Info() {
|
|
|
GloPgQPro->query_parser_free(&QueryParserArgs);
|
|
|
if (stmt_info) {
|
|
|
stmt_info=NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Query_Info::begin(unsigned char *_p, int len, bool header) {
|
|
|
PgQueryCmd=PGSQL_QUERY___NONE;
|
|
|
QueryPointer=NULL;
|
|
|
QueryLength=0;
|
|
|
mysql_stmt=NULL;
|
|
|
stmt_meta=NULL;
|
|
|
QueryParserArgs.digest_text=NULL;
|
|
|
QueryParserArgs.first_comment=NULL;
|
|
|
start_time=sess->thread->curtime;
|
|
|
init(_p, len, header);
|
|
|
if (pgsql_thread___commands_stats || pgsql_thread___query_digests) {
|
|
|
query_parser_init();
|
|
|
if (pgsql_thread___commands_stats)
|
|
|
query_parser_command_type();
|
|
|
}
|
|
|
bool_is_select_NOT_for_update=false;
|
|
|
bool_is_select_NOT_for_update_computed=false;
|
|
|
have_affected_rows=false; // if affected rows is set, last_insert_id is set too
|
|
|
waiting_since = 0;
|
|
|
affected_rows=0;
|
|
|
last_insert_id = 0;
|
|
|
rows_sent=0;
|
|
|
stmt_client_id=0;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Query_Info::end() {
|
|
|
query_parser_update_counters();
|
|
|
query_parser_free();
|
|
|
if ((end_time-start_time) > (unsigned int)pgsql_thread___long_query_time *1000) {
|
|
|
__sync_add_and_fetch(&sess->thread->status_variables.stvar[st_var_queries_slow],1);
|
|
|
}
|
|
|
assert(mysql_stmt==NULL);
|
|
|
if (stmt_info) {
|
|
|
stmt_info=NULL;
|
|
|
}
|
|
|
if (stmt_meta) { // fix bug #796: memory is not freed in case of error during STMT_EXECUTE
|
|
|
if (stmt_meta->pkt) {
|
|
|
uint32_t stmt_global_id=0;
|
|
|
memcpy(&stmt_global_id,(char *)(stmt_meta->pkt)+5,sizeof(uint32_t));
|
|
|
sess->SLDH->reset(stmt_global_id);
|
|
|
free(stmt_meta->pkt);
|
|
|
stmt_meta->pkt=NULL;
|
|
|
}
|
|
|
stmt_meta = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Query_Info::init(unsigned char *_p, int len, bool header) {
|
|
|
QueryLength=(header ? len-5 : len);
|
|
|
QueryPointer=(header ? _p+5 : _p);
|
|
|
PgQueryCmd = PGSQL_QUERY__UNINITIALIZED;
|
|
|
bool_is_select_NOT_for_update=false;
|
|
|
bool_is_select_NOT_for_update_computed=false;
|
|
|
have_affected_rows=false; // if affected rows is set, last_insert_id is set too
|
|
|
waiting_since = 0;
|
|
|
affected_rows=0;
|
|
|
last_insert_id = 0;
|
|
|
rows_sent=0;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Query_Info::query_parser_init() {
|
|
|
GloPgQPro->query_parser_init(&QueryParserArgs,(char *)QueryPointer,QueryLength,0);
|
|
|
}
|
|
|
|
|
|
enum PGSQL_QUERY_command PgSQL_Query_Info::query_parser_command_type() {
|
|
|
PgQueryCmd = GloPgQPro->query_parser_command_type(&QueryParserArgs);
|
|
|
return PgQueryCmd;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Query_Info::query_parser_free() {
|
|
|
GloPgQPro->query_parser_free(&QueryParserArgs);
|
|
|
}
|
|
|
|
|
|
unsigned long long PgSQL_Query_Info::query_parser_update_counters() {
|
|
|
if (stmt_info) {
|
|
|
//PgQueryCmd=stmt_info->MyComQueryCmd;
|
|
|
}
|
|
|
if (PgQueryCmd==PGSQL_QUERY___NONE) return 0; // this means that it was never initialized
|
|
|
if (PgQueryCmd==PGSQL_QUERY__UNINITIALIZED) return 0; // this means that it was never initialized
|
|
|
unsigned long long ret=GloPgQPro->query_parser_update_counters(sess, PgQueryCmd, &QueryParserArgs, end_time-start_time);
|
|
|
PgQueryCmd=PGSQL_QUERY___NONE;
|
|
|
QueryPointer=NULL;
|
|
|
QueryLength=0;
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
char * PgSQL_Query_Info::get_digest_text() {
|
|
|
return GloPgQPro->get_digest_text(&QueryParserArgs);
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Query_Info::is_select_NOT_for_update() {
|
|
|
if (stmt_info) { // we are processing a prepared statement. We already have the information
|
|
|
return stmt_info->is_select_NOT_for_update;
|
|
|
}
|
|
|
if (QueryPointer==NULL) {
|
|
|
return false;
|
|
|
}
|
|
|
if (bool_is_select_NOT_for_update_computed) {
|
|
|
return bool_is_select_NOT_for_update;
|
|
|
}
|
|
|
bool_is_select_NOT_for_update_computed=true;
|
|
|
if (QueryLength<7) {
|
|
|
return false;
|
|
|
}
|
|
|
char *QP = (char *)QueryPointer;
|
|
|
size_t ql = QueryLength;
|
|
|
// we try to use the digest, if avaiable
|
|
|
if (QueryParserArgs.digest_text) {
|
|
|
QP = QueryParserArgs.digest_text;
|
|
|
ql = strlen(QP);
|
|
|
}
|
|
|
if (strncasecmp(QP,(char *)"SELECT ",7)) {
|
|
|
return false;
|
|
|
}
|
|
|
// if we arrive till here, it is a SELECT
|
|
|
if (ql>=17) {
|
|
|
char *p=QP;
|
|
|
p+=ql-11;
|
|
|
if (strncasecmp(p," FOR UPDATE",11)==0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
p=QP;
|
|
|
p+=ql-10;
|
|
|
if (strncasecmp(p," FOR SHARE",10)==0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
if (ql>=25) {
|
|
|
char *p=QP;
|
|
|
p+=ql-19;
|
|
|
if (strncasecmp(p," LOCK IN SHARE MODE",19)==0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
p=QP;
|
|
|
p+=ql-7;
|
|
|
if (strncasecmp(p," NOWAIT",7)==0) {
|
|
|
// let simplify. If NOWAIT is used, we assume FOR UPDATE|SHARE is used
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
p=QP;
|
|
|
p+=ql-12;
|
|
|
if (strncasecmp(p," SKIP LOCKED",12)==0) {
|
|
|
// let simplify. If SKIP LOCKED is used, we assume FOR UPDATE|SHARE is used
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
p=QP;
|
|
|
char buf[129];
|
|
|
if (ql>=128) { // for long query, just check the last 128 bytes
|
|
|
p+=ql-128;
|
|
|
memcpy(buf,p,128);
|
|
|
buf[128]=0;
|
|
|
} else {
|
|
|
memcpy(buf,p,ql);
|
|
|
buf[ql]=0;
|
|
|
}
|
|
|
if (strcasestr(buf," FOR ")) {
|
|
|
if (strcasestr(buf," FOR UPDATE ")) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
if (strcasestr(buf," FOR SHARE ")) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.select_for_update_or_equivalent, 1);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
bool_is_select_NOT_for_update=true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::set_status(enum session_status e) {
|
|
|
if (e == session_status___NONE) {
|
|
|
if (mybe) {
|
|
|
if (mybe->server_myds) {
|
|
|
assert(mybe->server_myds->myconn == 0);
|
|
|
if (mybe->server_myds->myconn) {
|
|
|
assert(mybe->server_myds->myconn->async_state_machine == ASYNC_IDLE);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
status = e;
|
|
|
}
|
|
|
|
|
|
|
|
|
PgSQL_Session::PgSQL_Session() {
|
|
|
thread_session_id = 0;
|
|
|
//handler_ret = 0;
|
|
|
pause_until = 0;
|
|
|
qpo = new PgSQL_Query_Processor_Output();
|
|
|
qpo->init();
|
|
|
start_time = 0;
|
|
|
command_counters = new StatCounters(15, 10);
|
|
|
healthy = 1;
|
|
|
autocommit = true;
|
|
|
autocommit_handled = false;
|
|
|
sending_set_autocommit = false;
|
|
|
autocommit_on_hostgroup = -1;
|
|
|
killed = false;
|
|
|
session_type = PROXYSQL_SESSION_PGSQL;
|
|
|
//admin=false;
|
|
|
connections_handler = false;
|
|
|
max_connections_reached = false;
|
|
|
//stats=false;
|
|
|
client_authenticated = false;
|
|
|
default_schema = NULL;
|
|
|
user_attributes = NULL;
|
|
|
schema_locked = false;
|
|
|
session_fast_forward = SESSION_FORWARD_TYPE_NONE;
|
|
|
//started_sending_data_to_client = false;
|
|
|
handler_function = NULL;
|
|
|
client_myds = NULL;
|
|
|
to_process = 0;
|
|
|
mybe = NULL;
|
|
|
mirror = false;
|
|
|
mirrorPkt.ptr = NULL;
|
|
|
mirrorPkt.size = 0;
|
|
|
set_status(session_status___NONE);
|
|
|
warning_in_hg = -1;
|
|
|
|
|
|
idle_since = 0;
|
|
|
transaction_started_at = 0;
|
|
|
|
|
|
CurrentQuery.sess = this;
|
|
|
CurrentQuery.mysql_stmt = NULL;
|
|
|
CurrentQuery.stmt_meta = NULL;
|
|
|
CurrentQuery.stmt_global_id = 0;
|
|
|
CurrentQuery.stmt_client_id = 0;
|
|
|
CurrentQuery.stmt_info = NULL;
|
|
|
|
|
|
current_hostgroup = -1;
|
|
|
default_hostgroup = -1;
|
|
|
locked_on_hostgroup = -1;
|
|
|
locked_on_hostgroup_and_all_variables_set = false;
|
|
|
next_query_flagIN = -1;
|
|
|
mirror_hostgroup = -1;
|
|
|
mirror_flagOUT = -1;
|
|
|
active_transactions = 0;
|
|
|
|
|
|
use_ssl = false;
|
|
|
change_user_auth_switch = false;
|
|
|
|
|
|
match_regexes = NULL;
|
|
|
copy_cmd_matcher = NULL;
|
|
|
init(); // we moved this out to allow CHANGE_USER
|
|
|
|
|
|
last_insert_id = 0; // #1093
|
|
|
|
|
|
last_HG_affected_rows = -1; // #1421 : advanced support for LAST_INSERT_ID()
|
|
|
proxysql_node_address = NULL;
|
|
|
use_ldap_auth = false;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::reset() {
|
|
|
autocommit = true;
|
|
|
autocommit_handled = false;
|
|
|
sending_set_autocommit = false;
|
|
|
autocommit_on_hostgroup = -1;
|
|
|
warning_in_hg = -1;
|
|
|
current_hostgroup = -1;
|
|
|
default_hostgroup = -1;
|
|
|
locked_on_hostgroup = -1;
|
|
|
locked_on_hostgroup_and_all_variables_set = false;
|
|
|
if (sess_STMTs_meta) {
|
|
|
delete sess_STMTs_meta;
|
|
|
sess_STMTs_meta = NULL;
|
|
|
}
|
|
|
if (SLDH) {
|
|
|
delete SLDH;
|
|
|
SLDH = NULL;
|
|
|
}
|
|
|
if (mybes) {
|
|
|
reset_all_backends();
|
|
|
delete mybes;
|
|
|
mybes = NULL;
|
|
|
}
|
|
|
mybe = NULL;
|
|
|
|
|
|
if (session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
SQLite3_Session* sqlite_sess = (SQLite3_Session*)thread->gen_args;
|
|
|
if (sqlite_sess && sqlite_sess->sessdb) {
|
|
|
sqlite3* db = sqlite_sess->sessdb->get_db();
|
|
|
if ((*proxy_sqlite3_get_autocommit)(db) == 0) {
|
|
|
sqlite_sess->sessdb->execute((char*)"COMMIT");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (client_myds) {
|
|
|
if (client_myds->myconn) {
|
|
|
client_myds->myconn->reset();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
PgSQL_Session::~PgSQL_Session() {
|
|
|
|
|
|
reset(); // we moved this out to allow CHANGE_USER
|
|
|
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked]--;
|
|
|
}
|
|
|
|
|
|
if (client_myds) {
|
|
|
if (client_authenticated) {
|
|
|
switch (session_type) {
|
|
|
#ifdef PROXYSQLCLICKHOUSE
|
|
|
case PROXYSQL_SESSION_CLICKHOUSE:
|
|
|
GloClickHouseAuth->decrease_frontend_user_connections(client_myds->myconn->userinfo->username);
|
|
|
break;
|
|
|
#endif /* PROXYSQLCLICKHOUSE */
|
|
|
default:
|
|
|
if (use_ldap_auth == false) {
|
|
|
GloPgAuth->decrease_frontend_user_connections(client_myds->myconn->userinfo->username);
|
|
|
}
|
|
|
else {
|
|
|
GloMyLdapAuth->decrease_frontend_user_connections(client_myds->myconn->userinfo->fe_username);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
delete client_myds;
|
|
|
}
|
|
|
if (default_schema) {
|
|
|
free(default_schema);
|
|
|
}
|
|
|
if (user_attributes) {
|
|
|
free(user_attributes);
|
|
|
user_attributes = NULL;
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_NET, 1, "Thread=%p, Session=%p -- Shutdown Session %p\n", this->thread, this, this);
|
|
|
delete command_counters;
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL && connections_handler == false && mirror == false) {
|
|
|
__sync_fetch_and_sub(&PgHGM->status.client_connections, 1);
|
|
|
}
|
|
|
assert(qpo);
|
|
|
delete qpo;
|
|
|
match_regexes = NULL;
|
|
|
copy_cmd_matcher = NULL;
|
|
|
if (mirror) {
|
|
|
__sync_sub_and_fetch(&GloPTH->status_variables.mirror_sessions_current, 1);
|
|
|
//GloPTH->status_variables.p_gauge_array[p_th_gauge::mirror_concurrency]->Decrement();
|
|
|
}
|
|
|
if (proxysql_node_address) {
|
|
|
delete proxysql_node_address;
|
|
|
proxysql_node_address = NULL;
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
reset_default_session_variable((enum pgsql_variable_name)i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_CommitRollback(PtrSize_t* pkt) {
|
|
|
if (pkt->size <= 5) { return false; }
|
|
|
char c = ((char*)pkt->ptr)[5];
|
|
|
bool ret = false;
|
|
|
if (c == 'c' || c == 'C') {
|
|
|
if (pkt->size >= sizeof("commit") + 5) {
|
|
|
if (strncasecmp((char*)"commit", (char*)pkt->ptr + 5, 6) == 0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.commit_cnt, 1);
|
|
|
ret = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (c == 'r' || c == 'R') {
|
|
|
if (pkt->size >= sizeof("rollback") + 5) {
|
|
|
if (strncasecmp((char*)"rollback", (char*)pkt->ptr + 5, 8) == 0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.rollback_cnt, 1);
|
|
|
ret = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (ret == false) {
|
|
|
return false; // quick exit
|
|
|
}
|
|
|
// in this part of the code (as at release 2.4.3) where we call
|
|
|
// NumActiveTransactions() with the check_savepoint flag .
|
|
|
// This to try to handle MySQL bug https://bugs.pgsql.com/bug.php?id=107875
|
|
|
//
|
|
|
// Since we are limited to forwarding just one 'COMMIT|ROLLBACK', we work under the assumption that we
|
|
|
// only have one active transaction. Under this premise, we should execute this command under that
|
|
|
// specific connection, for that, we update 'current_hostgroup' with the first active transaction we are
|
|
|
// able to find. If more transactions are simultaneously open for the session, more 'COMMIT|ROLLBACK'
|
|
|
// commands are required to be issued by the client to continue ending transactions.
|
|
|
int hg = FindOneActiveTransaction(true);
|
|
|
if (hg != -1) {
|
|
|
// there is an active transaction, we must forward the request
|
|
|
current_hostgroup = hg;
|
|
|
return false;
|
|
|
}
|
|
|
else {
|
|
|
// there is no active transaction, we will just reply OK
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
//uint16_t setStatus = 0;
|
|
|
//if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
//client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, (const char*)pkt->ptr + 5);
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
} else {
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
if (c == 'c' || c == 'C') {
|
|
|
__sync_fetch_and_add(&PgHGM->status.commit_cnt_filtered, 1);
|
|
|
} else {
|
|
|
__sync_fetch_and_add(&PgHGM->status.rollback_cnt_filtered, 1);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
void PgSQL_Session::generate_proxysql_internal_session_json(json& j) {
|
|
|
char buff[32];
|
|
|
sprintf(buff, "%p", this);
|
|
|
j["address"] = buff;
|
|
|
if (thread) {
|
|
|
sprintf(buff, "%p", thread);
|
|
|
j["thread"] = buff;
|
|
|
}
|
|
|
const uint64_t age_ms = (thread->curtime - start_time) / 1000;
|
|
|
j["age_ms"] = age_ms;
|
|
|
j["status"] = status;
|
|
|
j["thread_session_id"] = thread_session_id;
|
|
|
j["current_hostgroup"] = current_hostgroup;
|
|
|
j["default_hostgroup"] = default_hostgroup;
|
|
|
j["locked_on_hostgroup"] = locked_on_hostgroup;
|
|
|
j["active_transactions"] = active_transactions;
|
|
|
j["transaction_time_ms"] = thread->curtime - transaction_started_at;
|
|
|
j["qpo"]["create_new_connection"] = qpo->create_new_conn;
|
|
|
j["qpo"]["reconnect"] = qpo->reconnect;
|
|
|
j["qpo"]["sticky_conn"] = qpo->sticky_conn;
|
|
|
j["qpo"]["cache_timeout"] = qpo->cache_timeout;
|
|
|
j["qpo"]["cache_ttl"] = qpo->cache_ttl;
|
|
|
j["qpo"]["delay"] = qpo->delay;
|
|
|
j["qpo"]["destination_hostgroup"] = qpo->destination_hostgroup;
|
|
|
j["qpo"]["firewall_whitelist_mode"] = qpo->firewall_whitelist_mode;
|
|
|
j["qpo"]["multiplex"] = qpo->multiplex;
|
|
|
j["qpo"]["timeout"] = qpo->timeout;
|
|
|
j["qpo"]["retries"] = qpo->retries;
|
|
|
j["qpo"]["max_lag_ms"] = qpo->max_lag_ms;
|
|
|
j["user_attributes"] = (user_attributes ? user_attributes : "");
|
|
|
j["transaction_persistent"] = transaction_persistent;
|
|
|
if (client_myds != NULL) { // only if client_myds is defined
|
|
|
j["client"]["stream"]["pkts_recv"] = client_myds->pkts_recv;
|
|
|
j["client"]["stream"]["pkts_sent"] = client_myds->pkts_sent;
|
|
|
j["client"]["stream"]["bytes_recv"] = client_myds->bytes_info.bytes_recv;
|
|
|
j["client"]["stream"]["bytes_sent"] = client_myds->bytes_info.bytes_sent;
|
|
|
j["client"]["client_addr"]["address"] = (client_myds->addr.addr ? client_myds->addr.addr : "");
|
|
|
j["client"]["client_addr"]["port"] = client_myds->addr.port;
|
|
|
j["client"]["proxy_addr"]["address"] = (client_myds->proxy_addr.addr ? client_myds->proxy_addr.addr : "");
|
|
|
j["client"]["proxy_addr"]["port"] = client_myds->proxy_addr.port;
|
|
|
j["client"]["encrypted"] = client_myds->encrypted;
|
|
|
if (client_myds->encrypted) {
|
|
|
const SSL_CIPHER* cipher = SSL_get_current_cipher(client_myds->ssl);
|
|
|
if (cipher) {
|
|
|
const char* name = SSL_CIPHER_get_name(cipher);
|
|
|
if (name) {
|
|
|
j["client"]["ssl_cipher"] = name;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
j["client"]["DSS"] = client_myds->DSS;
|
|
|
j["client"]["auth_method"] = AUTHENTICATION_METHOD_STR[(int)client_myds->auth_method];
|
|
|
if (client_myds->myconn != NULL) { // only if myconn is defined
|
|
|
if (client_myds->myconn->userinfo != NULL) { // only if userinfo is defined
|
|
|
j["client"]["userinfo"]["username"] = (client_myds->myconn->userinfo->username ? client_myds->myconn->userinfo->username : "");
|
|
|
j["client"]["userinfo"]["dbname"] = (client_myds->myconn->userinfo->dbname ? client_myds->myconn->userinfo->dbname : "");
|
|
|
#ifdef DEBUG
|
|
|
j["client"]["userinfo"]["password"] = (client_myds->myconn->userinfo->password ? client_myds->myconn->userinfo->password : "");
|
|
|
#endif
|
|
|
}
|
|
|
for (auto idx = 0; idx < PGSQL_NAME_LAST_LOW_WM; idx++) {
|
|
|
client_myds->myconn->variables[idx].fill_client_internal_session(j["client"], idx);
|
|
|
}
|
|
|
|
|
|
PgSQL_Connection* client_conn = client_myds->myconn;
|
|
|
for (std::vector<uint32_t>::const_iterator it_c = client_conn->dynamic_variables_idx.begin();
|
|
|
it_c != client_conn->dynamic_variables_idx.end(); it_c++) {
|
|
|
client_conn->variables[*it_c].fill_client_internal_session(j["client"], *it_c);
|
|
|
}
|
|
|
//j["conn"]["autocommit"] = (client_myds->myconn->options.autocommit ? "ON" : "OFF");
|
|
|
//j["conn"]["client_flag"]["value"] = client_myds->myconn->options.client_flag;
|
|
|
//j["conn"]["client_flag"]["client_found_rows"] = (client_myds->myconn->options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0);
|
|
|
//j["conn"]["client_flag"]["client_multi_statements"] = (client_myds->myconn->options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0);
|
|
|
//j["conn"]["client_flag"]["client_multi_results"] = (client_myds->myconn->options.client_flag & CLIENT_MULTI_RESULTS ? 1 : 0);
|
|
|
//j["conn"]["client_flag"]["client_deprecate_eof"] = (client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0);
|
|
|
//j["conn"]["no_backslash_escapes"] = client_myds->myconn->options.no_backslash_escapes;
|
|
|
//j["conn"]["status"]["compression"] = client_myds->myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION);
|
|
|
//j["conn"]["ps"]["client_stmt_to_global_ids"] = client_myds->myconn->local_stmts->client_stmt_to_global_ids;
|
|
|
|
|
|
const PgSQL_Conn_Param& conn_params = client_myds->myconn->conn_params;
|
|
|
|
|
|
for (const auto& [key, val] : conn_params.connection_parameters) {
|
|
|
j["client"]["conn"]["connection_options"][key.c_str()] = val.c_str();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
for (unsigned int i = 0; i < mybes->len; i++) {
|
|
|
PgSQL_Backend* _mybe = NULL;
|
|
|
_mybe = (PgSQL_Backend*)mybes->index(i);
|
|
|
j["backends"][i]["hostgroup_id"] = _mybe->hostgroup_id;
|
|
|
if (_mybe->server_myds) {
|
|
|
PgSQL_Data_Stream* _myds = _mybe->server_myds;
|
|
|
sprintf(buff, "%p", _myds);
|
|
|
j["backends"][i]["stream"]["address"] = buff;
|
|
|
j["backends"][i]["stream"]["questions"] = _myds->statuses.questions;
|
|
|
j["backends"][i]["stream"]["pgconnpoll_get"] = _myds->statuses.pgconnpoll_get;
|
|
|
j["backends"][i]["stream"]["pgconnpoll_put"] = _myds->statuses.pgconnpoll_put;
|
|
|
/* when fast_forward is not used, these metrics are always 0. Explicitly disabled
|
|
|
j["backend"][i]["stream"]["pkts_recv"] = _myds->pkts_recv;
|
|
|
j["backend"][i]["stream"]["pkts_sent"] = _myds->pkts_sent;
|
|
|
*/
|
|
|
j["backends"][i]["stream"]["bytes_recv"] = _myds->bytes_info.bytes_recv;
|
|
|
j["backends"][i]["stream"]["bytes_sent"] = _myds->bytes_info.bytes_sent;
|
|
|
j["backends"][i]["stream"]["DSS"] = _myds->DSS;
|
|
|
if (_myds->myconn) {
|
|
|
PgSQL_Connection* _myconn = _myds->myconn;
|
|
|
for (auto idx = 0; idx < PGSQL_NAME_LAST_LOW_WM; idx++) {
|
|
|
_myconn->variables[idx].fill_server_internal_session(j["backends"], i, idx);
|
|
|
}
|
|
|
for (std::vector<uint32_t>::const_iterator it_c = _myconn->dynamic_variables_idx.begin(); it_c != _myconn->dynamic_variables_idx.end(); it_c++) {
|
|
|
_myconn->variables[*it_c].fill_server_internal_session(j["backends"], i, *it_c);
|
|
|
}
|
|
|
sprintf(buff, "%p", _myconn);
|
|
|
j["backends"][i]["conn"]["address"] = buff;
|
|
|
j["backends"][i]["conn"]["auto_increment_delay_token"] = _myconn->auto_increment_delay_token;
|
|
|
j["backends"][i]["conn"]["bytes_recv"] = _myconn->bytes_info.bytes_recv;
|
|
|
j["backends"][i]["conn"]["bytes_sent"] = _myconn->bytes_info.bytes_sent;
|
|
|
j["backends"][i]["conn"]["questions"] = _myconn->statuses.questions;
|
|
|
j["backends"][i]["conn"]["pgconnpoll_get"] = _myconn->statuses.pgconnpoll_get;
|
|
|
j["backends"][i]["conn"]["pgconnpoll_put"] = _myconn->statuses.pgconnpoll_put;
|
|
|
//j["backend"][i]["conn"]["charset"] = _myds->myconn->options.charset; // not used for backend
|
|
|
//j["backends"][i]["conn"]["session_track_gtids"] = (_myconn->options.session_track_gtids ? _myconn->options.session_track_gtids : "");
|
|
|
j["backends"][i]["conn"]["init_connect"] = (_myconn->options.init_connect ? _myconn->options.init_connect : "");
|
|
|
j["backends"][i]["conn"]["init_connect_sent"] = _myds->myconn->options.init_connect_sent;
|
|
|
//j["backends"][i]["conn"]["standard_conforming_strings"] = _myconn->options.no_backslash_escapes;
|
|
|
//j["backends"][i]["conn"]["status"]["get_lock"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_GET_LOCK);
|
|
|
//j["backends"][i]["conn"]["status"]["lock_tables"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES);
|
|
|
j["backends"][i]["conn"]["status"]["has_savepoint"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT);
|
|
|
j["backends"][i]["conn"]["status"]["temporary_table"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE);
|
|
|
j["backends"][i]["conn"]["status"]["user_variable"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_USER_VARIABLE);
|
|
|
//j["backends"][i]["conn"]["status"]["found_rows"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_FOUND_ROWS);
|
|
|
j["backends"][i]["conn"]["status"]["no_multiplex"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX);
|
|
|
j["backends"][i]["conn"]["status"]["no_multiplex_HG"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG);
|
|
|
//j["backends"][i]["conn"]["status"]["compression"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION);
|
|
|
//j["backends"][i]["conn"]["status"]["prepared_statement"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT);
|
|
|
{
|
|
|
// MultiplexDisabled : status returned by PgSQL_Connection::MultiplexDisabled();
|
|
|
// MultiplexDisabled_ext : status returned by PgSQL_Connection::MultiplexDisabled() || PgSQL_Connection::isActiveTransaction()
|
|
|
bool multiplex_disabled = _myconn->MultiplexDisabled();
|
|
|
j["backends"][i]["conn"]["MultiplexDisabled"] = multiplex_disabled;
|
|
|
if (multiplex_disabled == false) {
|
|
|
if (_myconn->IsActiveTransaction() == true) {
|
|
|
multiplex_disabled = true;
|
|
|
}
|
|
|
}
|
|
|
j["backends"][i]["conn"]["MultiplexDisabled_ext"] = multiplex_disabled;
|
|
|
}
|
|
|
//j["backends"][i]["conn"]["ps"]["backend_stmt_to_global_ids"] = _myconn->local_stmts->backend_stmt_to_global_ids;
|
|
|
//j["backends"][i]["conn"]["ps"]["global_stmt_to_backend_ids"] = _myconn->local_stmts->global_stmt_to_backend_ids;
|
|
|
//j["backends"][i]["conn"]["client_flag"]["value"] = _myconn->options.client_flag;
|
|
|
//j["backends"][i]["conn"]["client_flag"]["client_found_rows"] = (_myconn->options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0);
|
|
|
//j["backends"][i]["conn"]["client_flag"]["client_multi_statements"] = (_myconn->options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0);
|
|
|
//j["backends"][i]["conn"]["client_flag"]["client_deprecate_eof"] = (_myconn->options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0);
|
|
|
if (_myconn->is_connected()) {
|
|
|
sprintf(buff, "%p", _myconn->get_pg_connection());
|
|
|
j["backends"][i]["conn"]["pgsql"]["address"] = buff;
|
|
|
j["backends"][i]["conn"]["pgsql"]["host"] = _myconn->get_pg_host();
|
|
|
j["backends"][i]["conn"]["pgsql"]["host_addr"] = _myconn->get_pg_hostaddr();
|
|
|
j["backends"][i]["conn"]["pgsql"]["port"] = _myconn->get_pg_port();
|
|
|
j["backends"][i]["conn"]["pgsql"]["user"] = _myconn->get_pg_user();
|
|
|
#ifdef DEBUG
|
|
|
j["backends"][i]["conn"]["pgsql"]["password"] = _myconn->get_pg_password();
|
|
|
#endif
|
|
|
j["backends"][i]["conn"]["pgsql"]["database"] = _myconn->get_pg_dbname();
|
|
|
j["backends"][i]["conn"]["pgsql"]["backend_pid"] = _myconn->get_pg_backend_pid();
|
|
|
j["backends"][i]["conn"]["pgsql"]["using_ssl"] = _myconn->get_pg_ssl_in_use() ? "YES" : "NO";
|
|
|
j["backends"][i]["conn"]["pgsql"]["error_msg"] = _myconn->get_pg_error_message();
|
|
|
j["backends"][i]["conn"]["pgsql"]["options"] = _myconn->get_pg_options();
|
|
|
j["backends"][i]["conn"]["pgsql"]["fd"] = _myconn->get_pg_socket_fd();
|
|
|
j["backends"][i]["conn"]["pgsql"]["protocol_version"] = _myconn->get_pg_protocol_version();
|
|
|
j["backends"][i]["conn"]["pgsql"]["server_version"] = _myconn->get_pg_server_version_str(buff, sizeof(buff));
|
|
|
j["backends"][i]["conn"]["pgsql"]["transaction_status"] = _myconn->get_pg_transaction_status_str();
|
|
|
j["backends"][i]["conn"]["pgsql"]["connection_status"] = _myconn->get_pg_connection_status_str();
|
|
|
j["backends"][i]["conn"]["pgsql"]["client_encoding"] = _myconn->get_pg_client_encoding();
|
|
|
j["backends"][i]["conn"]["pgsql"]["is_nonblocking"] = _myconn->get_pg_is_nonblocking() ? "YES" : "NO";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_special_queries(PtrSize_t* pkt, bool* lock_hostgroup) {
|
|
|
// Unsupported Features:
|
|
|
// COPY
|
|
|
/*if (pkt->size > (5 + 5) && strncasecmp((char*)"COPY ", (char*)pkt->ptr + 5, 5) == 0) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Feature not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
|
false, true);
|
|
|
//client_myds->DSS = STATE_SLEEP;
|
|
|
//status = WAITING_CLIENT_DATA;
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
} else {
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}*/
|
|
|
//
|
|
|
if (pkt->size > (5 + 18) && strncasecmp((char*)"PROXYSQL INTERNAL ", (char*)pkt->ptr + 5, 18) == 0) {
|
|
|
return_proxysql_internal(pkt);
|
|
|
return true;
|
|
|
}
|
|
|
if (locked_on_hostgroup == -1) {
|
|
|
//if (handler_SetAutocommit(pkt) == true) {
|
|
|
// return true;
|
|
|
//}
|
|
|
if (handler_CommitRollback(pkt) == true) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
/*
|
|
|
//handle 2564
|
|
|
if (pkt->size == SELECT_VERSION_COMMENT_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncmp((char*)SELECT_VERSION_COMMENT, (char*)pkt->ptr + 5, pkt->size - 5) == 0) {
|
|
|
// FIXME: this doesn't return AUTOCOMMIT or IN_TRANS
|
|
|
PtrSize_t pkt_2;
|
|
|
if (deprecate_eof_active) {
|
|
|
pkt_2.size = PROXYSQL_VERSION_COMMENT_WITH_OK_LEN;
|
|
|
pkt_2.ptr = l_alloc(pkt_2.size);
|
|
|
memcpy(pkt_2.ptr, PROXYSQL_VERSION_COMMENT_WITH_OK, pkt_2.size);
|
|
|
}
|
|
|
else {
|
|
|
pkt_2.size = PROXYSQL_VERSION_COMMENT_LEN;
|
|
|
pkt_2.ptr = l_alloc(pkt_2.size);
|
|
|
memcpy(pkt_2.ptr, PROXYSQL_VERSION_COMMENT, pkt_2.size);
|
|
|
}
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
client_myds->PSarrayOUT->add(pkt_2.ptr, pkt_2.size);
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
if (pkt->size == strlen((char*)"select USER()") + 5 && strncmp((char*)"select USER()", (char*)pkt->ptr + 5, pkt->size - 5) == 0) {
|
|
|
// FIXME: this doesn't return AUTOCOMMIT or IN_TRANS
|
|
|
char* query1 = (char*)"SELECT \"%s\" AS 'USER()'";
|
|
|
char* query2 = (char*)malloc(strlen(query1) + strlen(client_myds->myconn->userinfo->username) + 10);
|
|
|
sprintf(query2, query1, client_myds->myconn->userinfo->username);
|
|
|
char* error;
|
|
|
int cols;
|
|
|
int affected_rows;
|
|
|
SQLite3_result* resultset;
|
|
|
GloAdmin->admindb->execute_statement(query2, &error, &cols, &affected_rows, &resultset);
|
|
|
SQLite3_to_MySQL(resultset, error, affected_rows, &client_myds->myprot, false, deprecate_eof_active);
|
|
|
delete resultset;
|
|
|
free(query2);
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
// MySQL client check command for dollars quote support, starting at version '8.1.0'. See #4300.
|
|
|
if ((pkt->size == strlen("SELECT $$") + 5) && strncasecmp("SELECT $$", (char*)pkt->ptr + 5, pkt->size - 5) == 0) {
|
|
|
pair<int, const char*> err_info{ get_dollar_quote_error(pgsql_thread___server_version) };
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, err_info.first, (char*)"HY000", err_info.second, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
|
|
|
return true;
|
|
|
}*/
|
|
|
if (locked_on_hostgroup >= 0 &&
|
|
|
(strncasecmp((char*)"SET ", (char*)pkt->ptr + 5, 4) == 0 ||
|
|
|
strncasecmp((char*)"RESET ", (char*)pkt->ptr + 5, 6) == 0)) {
|
|
|
// this is a circuit breaker, we will send everything to the backend
|
|
|
//
|
|
|
// also note that in the current implementation we stop tracking variables:
|
|
|
// this becomes a problem if pgsql-set_query_lock_on_hostgroup is
|
|
|
// disabled while a session is already locked
|
|
|
return false;
|
|
|
}
|
|
|
/*
|
|
|
if (pkt->size > (5 + 6) && strncasecmp((char*)"RESET ", (char*)pkt->ptr + 5, 6) == 0) {
|
|
|
if (locked_on_hostgroup >= 0)
|
|
|
return false;
|
|
|
|
|
|
std::vector<char> param_name;
|
|
|
const char* ptr = (char*)pkt->ptr;
|
|
|
|
|
|
for (size_t idx = (5 + 6); idx < pkt->size; idx++) {
|
|
|
if (ptr[idx] == '\0' || ptr[idx] == ';')
|
|
|
break;
|
|
|
|
|
|
if (ptr[idx] == ' ')
|
|
|
continue;
|
|
|
|
|
|
param_name.push_back(ptr[idx]);
|
|
|
}
|
|
|
|
|
|
param_name.push_back('\0');
|
|
|
|
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
|
for (int i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
|
|
|
if (idx == PGSQL_NAME_LAST_LOW_WM) continue;
|
|
|
|
|
|
if (variable_name_exists(pgsql_tracked_variables[i], param_name.data()) == true) {
|
|
|
idx = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> param_status = {};
|
|
|
|
|
|
if (idx != PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
const char* name = pgsql_tracked_variables[idx].set_variable_name;
|
|
|
const char* value = (idx < PGSQL_NAME_LAST_LOW_WM) ? pgsql_thread___default_variables[idx] : pgsql_tracked_variables[idx].default_value;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection %s to %s\n", name, value);
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(value, strlen(value), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, pgsql_tracked_variables[idx].idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, pgsql_tracked_variables[idx].idx, value)) {
|
|
|
return false;
|
|
|
}
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx])) {
|
|
|
param_status.push_back(std::make_pair(name, value));
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, (const char*)pkt->ptr + 5, trx_state, NULL, param_status);
|
|
|
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
} else {
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
// 'LOAD DATA LOCAL INFILE' is unsupported. We report an specific error to inform clients about this fact. For more context see #833.
|
|
|
if ((pkt->size >= 22 + 5) && (strncasecmp((char*)"LOAD DATA LOCAL INFILE", (char*)pkt->ptr + 5, 22) == 0)) {
|
|
|
if (pgsql_thread___enable_load_data_local_infile == false) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Unsupported 'LOAD DATA LOCAL INFILE' command",
|
|
|
PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, false, true);
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
else {
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
else {
|
|
|
if (pgsql_thread___verbose_query_error) {
|
|
|
proxy_warning(
|
|
|
"Command '%.*s' refers to file in ProxySQL instance, NOT on client side!\n",
|
|
|
static_cast<int>(pkt->size - sizeof(mysql_hdr) - 1),
|
|
|
static_cast<char*>(pkt->ptr) + 5
|
|
|
);
|
|
|
}
|
|
|
else {
|
|
|
proxy_warning(
|
|
|
"Command 'LOAD DATA LOCAL INFILE' refers to file in ProxySQL instance, NOT on client side!\n"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___create_mirror_session() {
|
|
|
if (pkt.size < 15 * 1024 * 1024 && (qpo->mirror_hostgroup >= 0 || qpo->mirror_flagOUT >= 0)) {
|
|
|
// check if there are too many mirror sessions in queue
|
|
|
if (thread->mirror_queue_mysql_sessions->len >= (unsigned int)pgsql_thread___mirror_max_queue_length) {
|
|
|
return;
|
|
|
}
|
|
|
// at this point, we will create the new session
|
|
|
// we will later decide if queue it or sent it immediately
|
|
|
|
|
|
// int i=0;
|
|
|
// for (i=0;i<100;i++) {
|
|
|
PgSQL_Session* newsess = NULL;
|
|
|
if (thread->mirror_queue_mysql_sessions_cache->len == 0) {
|
|
|
newsess = new PgSQL_Session();
|
|
|
newsess->client_myds = new PgSQL_Data_Stream();
|
|
|
newsess->client_myds->DSS = STATE_SLEEP;
|
|
|
newsess->client_myds->sess = newsess;
|
|
|
newsess->client_myds->fd = 0;
|
|
|
newsess->client_myds->myds_type = MYDS_FRONTEND;
|
|
|
newsess->client_myds->PSarrayOUT = new PtrSizeArray();
|
|
|
newsess->thread_session_id = __sync_fetch_and_add(&glovars.thread_id, 1);
|
|
|
if (newsess->thread_session_id == 0) {
|
|
|
newsess->thread_session_id = __sync_fetch_and_add(&glovars.thread_id, 1);
|
|
|
}
|
|
|
newsess->status = WAITING_CLIENT_DATA;
|
|
|
PgSQL_Connection* myconn = new PgSQL_Connection;
|
|
|
newsess->client_myds->attach_connection(myconn);
|
|
|
newsess->client_myds->myprot.init(&newsess->client_myds, newsess->client_myds->myconn->userinfo, newsess);
|
|
|
newsess->mirror = true;
|
|
|
newsess->client_myds->destroy_queues();
|
|
|
}
|
|
|
else {
|
|
|
newsess = (PgSQL_Session*)thread->mirror_queue_mysql_sessions_cache->remove_index_fast(0);
|
|
|
}
|
|
|
newsess->client_myds->myconn->userinfo->set(client_myds->myconn->userinfo);
|
|
|
newsess->to_process = 1;
|
|
|
newsess->default_hostgroup = default_hostgroup;
|
|
|
if (qpo->mirror_hostgroup >= 0) {
|
|
|
newsess->mirror_hostgroup = qpo->mirror_hostgroup; // in the new session we copy the mirror hostgroup
|
|
|
}
|
|
|
else {
|
|
|
newsess->mirror_hostgroup = default_hostgroup; // copy the default
|
|
|
}
|
|
|
newsess->mirror_flagOUT = qpo->mirror_flagOUT; // in the new session we copy the mirror flagOUT
|
|
|
if (newsess->default_schema == NULL) {
|
|
|
newsess->default_schema = strdup(default_schema);
|
|
|
}
|
|
|
else {
|
|
|
if (strcmp(newsess->default_schema, default_schema)) {
|
|
|
free(newsess->default_schema);
|
|
|
newsess->default_schema = strdup(default_schema);
|
|
|
}
|
|
|
}
|
|
|
newsess->mirrorPkt.size = pkt.size;
|
|
|
newsess->mirrorPkt.ptr = l_alloc(newsess->mirrorPkt.size);
|
|
|
memcpy(newsess->mirrorPkt.ptr, pkt.ptr, pkt.size);
|
|
|
|
|
|
if (thread->mirror_queue_mysql_sessions->len == 0) {
|
|
|
// there are no sessions in the queue, we try to execute immediately
|
|
|
// Only pgsql_thread___mirror_max_concurrency mirror session can run in parallel
|
|
|
if (__sync_add_and_fetch(&GloPTH->status_variables.mirror_sessions_current, 1) > (unsigned int)pgsql_thread___mirror_max_concurrency) {
|
|
|
// if the limit is reached, we queue it instead
|
|
|
__sync_sub_and_fetch(&GloPTH->status_variables.mirror_sessions_current, 1);
|
|
|
thread->mirror_queue_mysql_sessions->add(newsess);
|
|
|
}
|
|
|
else {
|
|
|
//GloPTH->status_variables.p_gauge_array[p_th_gauge::mirror_concurrency]->Increment();
|
|
|
thread->register_session(thread,newsess);
|
|
|
newsess->handler(); // execute immediately
|
|
|
//newsess->to_process=0;
|
|
|
if (newsess->status == WAITING_CLIENT_DATA) { // the mirror session has completed
|
|
|
thread->unregister_session(thread->mysql_sessions->len - 1);
|
|
|
unsigned int l = (unsigned int)pgsql_thread___mirror_max_concurrency;
|
|
|
if (thread->mirror_queue_mysql_sessions->len * 0.3 > l) l = thread->mirror_queue_mysql_sessions->len * 0.3;
|
|
|
if (thread->mirror_queue_mysql_sessions_cache->len <= l) {
|
|
|
bool to_cache = true;
|
|
|
if (newsess->mybe) {
|
|
|
if (newsess->mybe->server_myds) {
|
|
|
to_cache = false;
|
|
|
}
|
|
|
}
|
|
|
if (to_cache) {
|
|
|
__sync_sub_and_fetch(&GloPTH->status_variables.mirror_sessions_current, 1);
|
|
|
//GloPTH->status_variables.p_gauge_array[p_th_gauge::mirror_concurrency]->Decrement();
|
|
|
thread->mirror_queue_mysql_sessions_cache->add(newsess);
|
|
|
}
|
|
|
else {
|
|
|
delete newsess;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
delete newsess;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
thread->mirror_queue_mysql_sessions->add(newsess);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int PgSQL_Session::handler_again___status_PINGING_SERVER() {
|
|
|
assert(mybe->server_myds->myconn);
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
int rc = myconn->async_ping(myds->revents);
|
|
|
if (rc == 0) {
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myconn->compute_unknown_transaction_status();
|
|
|
//if (pgsql_thread___multiplexing && (myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) {
|
|
|
// due to issue #2096 we disable the global check on pgsql_thread___multiplexing
|
|
|
if ((myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
myds->return_MySQL_Connection_To_Pool();
|
|
|
} else {
|
|
|
myds->destroy_MySQL_Connection_From_Pool(true);
|
|
|
}
|
|
|
delete mybe->server_myds;
|
|
|
mybe->server_myds = NULL;
|
|
|
set_status(session_status___NONE);
|
|
|
return -1;
|
|
|
}
|
|
|
else {
|
|
|
if (rc == -1 || rc == -2) {
|
|
|
if (rc == -2) {
|
|
|
unsigned long long us = pgsql_thread___ping_timeout_server * 1000;
|
|
|
us += thread->curtime;
|
|
|
us -= myds->wait_until;
|
|
|
proxy_error("Ping timeout during ping on %s:%d after %lluus (timeout %dms)\n", myconn->parent->address, myconn->parent->port, us, pgsql_thread___ping_timeout_server);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_PING_TIMEOUT);
|
|
|
}
|
|
|
else { // rc==-1
|
|
|
int myerr = 0; // TODO: fix this mysql_errno(myconn->pgsql);
|
|
|
detected_broken_connection(__FILE__, __LINE__, __func__, "during ping", myconn, true);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::pgsql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myerr);
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
delete mybe->server_myds;
|
|
|
mybe->server_myds = NULL;
|
|
|
return -1;
|
|
|
}
|
|
|
else {
|
|
|
// rc==1 , nothing to do for now
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, myds->fd, myds, thread->curtime);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
int PgSQL_Session::handler_again___status_RESETTING_CONNECTION() {
|
|
|
assert(mybe->server_myds->myconn);
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, myds->fd, myds, thread->curtime);
|
|
|
}
|
|
|
myds->DSS = STATE_MARIADB_QUERY;
|
|
|
|
|
|
int rc = myconn->async_reset_session(myds->revents);
|
|
|
if (rc == 0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.backend_reset_connection, 1);
|
|
|
myds->myconn->reset();
|
|
|
PgHGM->increase_reset_counter();
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myds->return_MySQL_Connection_To_Pool();
|
|
|
delete mybe->server_myds;
|
|
|
mybe->server_myds = NULL;
|
|
|
set_status(session_status___NONE);
|
|
|
return -1;
|
|
|
} else {
|
|
|
if (rc == -1 || rc == -2) {
|
|
|
if (rc == -2) {
|
|
|
proxy_error("Resetting Connection timeout during Reset Session on %s , %d\n", myconn->parent->address, myconn->parent->port);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::pgsql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_CHANGE_USER_TIMEOUT);
|
|
|
} else { // rc==-1
|
|
|
const bool error_present = myconn->is_error_present();
|
|
|
PgHGM->p_update_pgsql_error_counter(
|
|
|
p_pgsql_error_type::pgsql,
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port,
|
|
|
(error_present ? 9999 : ER_PROXYSQL_OFFLINE_SRV) // TOFIX: 9999 is a placeholder for the actual error code
|
|
|
);
|
|
|
if (error_present) {
|
|
|
proxy_error("Detected an error during Reset Session on (%d,%s,%d) , FD (Conn:%d , MyDS:%d) : %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myds->fd, myds->myconn->fd, myconn->get_error_code_with_message().c_str());
|
|
|
} else {
|
|
|
proxy_error(
|
|
|
"Detected an error during Reset Session on (%d,%s,%d) , FD (Conn:%d , MyDS:%d) : %d, %s\n",
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port,
|
|
|
myds->fd,
|
|
|
myds->myconn->fd,
|
|
|
ER_PROXYSQL_OFFLINE_SRV,
|
|
|
"Detected offline server prior to statement execution"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
RequestEnd(myds); //fix bug #682
|
|
|
return -1;
|
|
|
} else {
|
|
|
// rc==1 , nothing to do for now
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, myds->fd, myds, thread->curtime);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
void PgSQL_Session::handler_again___new_thread_to_kill_connection() {
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
if (myds->myconn && false /*myds->myconn->pgsql*/) { // TODO: fix this
|
|
|
if (myds->killed_at == 0) {
|
|
|
myds->wait_until = 0;
|
|
|
myds->killed_at = thread->curtime;
|
|
|
//fprintf(stderr,"Expired: %llu, %llu\n", mybe->server_myds->wait_until, thread->curtime);
|
|
|
PgSQL_Connection_userinfo* ui = client_myds->myconn->userinfo;
|
|
|
char* auth_password = NULL;
|
|
|
if (ui->password) {
|
|
|
if (ui->password[0] == '*') { // we don't have the real password, let's pass sha1
|
|
|
auth_password = ui->sha1_pass;
|
|
|
}
|
|
|
else {
|
|
|
auth_password = ui->password;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
PgSQL_KillArgs* ka = new PgSQL_KillArgs(ui->username, auth_password, myds->myconn->parent->address, myds->myconn->parent->port, myds->myconn->parent->myhgc->hid, myds->myconn->get_backend_pid(), KILL_QUERY, myds->myconn->parent->use_ssl, thread, myds->myconn->connected_host_details.ip);
|
|
|
pthread_attr_t attr;
|
|
|
pthread_attr_init(&attr);
|
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
|
pthread_attr_setstacksize(&attr, 256 * 1024);
|
|
|
pthread_t pt;
|
|
|
if (pthread_create(&pt, &attr, &PgSQL_kill_query_thread, ka) != 0) {
|
|
|
// LCOV_EXCL_START
|
|
|
proxy_error("Thread creation\n");
|
|
|
assert(0);
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// NEXT_IMMEDIATE is a legacy macro used inside handler() to immediately jump
|
|
|
// to handler_again
|
|
|
#define NEXT_IMMEDIATE(new_st) do { set_status(new_st); goto handler_again; } while (0)
|
|
|
// NEXT_IMMEDIATE_NEW is a new macro to use *outside* handler().
|
|
|
// handler() should check the return code of the function it calls, and if
|
|
|
// true should jump to handler_again
|
|
|
#define NEXT_IMMEDIATE_NEW(new_st) do { set_status(new_st); return true; } while (0)
|
|
|
|
|
|
bool PgSQL_Session::handler_again___verify_init_connect() {
|
|
|
if (mybe->server_myds->myconn->options.init_connect_sent == false) {
|
|
|
// we needs to set it to true
|
|
|
mybe->server_myds->myconn->options.init_connect_sent = true;
|
|
|
char* tmp_init_connect = mysql_thread___init_connect;
|
|
|
char* init_connect_hg = mybe->server_myds->myconn->parent->myhgc->attributes.init_connect;
|
|
|
if (init_connect_hg != NULL && strlen(init_connect_hg) != 0) {
|
|
|
// mysql_hostgroup_attributes takes priority
|
|
|
tmp_init_connect = init_connect_hg;
|
|
|
}
|
|
|
if (tmp_init_connect) {
|
|
|
// we send init connect queries only if set
|
|
|
mybe->server_myds->myconn->options.init_connect = strdup(tmp_init_connect);
|
|
|
// Sets the previous status of the PgSQL session according to the current status.
|
|
|
set_previous_status_mode3();
|
|
|
NEXT_IMMEDIATE_NEW(SETTING_INIT_CONNECT);
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_again___verify_backend_user_db() {
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session %p , client: %s , backend: %s\n", this, client_myds->myconn->userinfo->username, mybe->server_myds->myconn->userinfo->username);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session %p , client: %s , backend: %s\n", this, client_myds->myconn->userinfo->dbname, mybe->server_myds->myconn->userinfo->dbname);
|
|
|
if (client_myds->myconn->userinfo->hash != mybe->server_myds->myconn->userinfo->hash) {
|
|
|
assert(strcmp(client_myds->myconn->userinfo->username, myds->myconn->userinfo->username) == 0);
|
|
|
assert(strcmp(client_myds->myconn->userinfo->dbname, myds->myconn->userinfo->dbname) == 0);
|
|
|
}
|
|
|
// if we reach here, the username is the same
|
|
|
if (myds->myconn->requires_RESETTING_CONNECTION(client_myds->myconn)) {
|
|
|
// if we reach here, even if the username is the same,
|
|
|
// the backend connection has some session variable set
|
|
|
// that the client never asked for
|
|
|
// because we can't unset variables, we will reset the connection
|
|
|
//
|
|
|
// Sets the previous status of the PgSQL session according to the current status.
|
|
|
set_previous_status_mode3();
|
|
|
mybe->server_myds->wait_until = thread->curtime + pgsql_thread___connect_timeout_server * 1000; // max_timeout
|
|
|
NEXT_IMMEDIATE_NEW(RESETTING_CONNECTION_V2);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_again___status_SETTING_INIT_CONNECT(int* _rc) {
|
|
|
bool ret = false;
|
|
|
assert(mybe->server_myds->myconn);
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
myds->DSS = STATE_MARIADB_QUERY;
|
|
|
enum session_status st = status;
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime);
|
|
|
}
|
|
|
int rc = myconn->async_send_simple_command(myds->revents, myconn->options.init_connect, strlen(myconn->options.init_connect));
|
|
|
if (rc == 0) {
|
|
|
myds->revents |= POLLOUT; // we also set again POLLOUT to send a query immediately!
|
|
|
//myds->free_mysql_real_query();
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
}
|
|
|
else {
|
|
|
if (rc == -1 || rc == -2) {
|
|
|
// the command failed
|
|
|
int myerr = 0; // TODO: fix this mysql_errno(myconn->pgsql);
|
|
|
PgHGM->p_update_pgsql_error_counter(
|
|
|
p_pgsql_error_type::pgsql,
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port,
|
|
|
(myerr ? myerr : ER_PROXYSQL_OFFLINE_SRV)
|
|
|
);
|
|
|
if (myerr >= 2000 || myerr == 0) {
|
|
|
bool retry_conn = false;
|
|
|
// client error, serious
|
|
|
detected_broken_connection(__FILE__, __LINE__, __func__, "while setting INIT CONNECT", myconn);
|
|
|
//if ((myds->myconn->reusable==true) && ((myds->myprot.prot_status & SERVER_STATUS_IN_TRANS)==0)) {
|
|
|
if (rc != -2) { // see PMC-10003
|
|
|
if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
retry_conn = true;
|
|
|
}
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (rc == -2) {
|
|
|
// Here we handle PMC-10003
|
|
|
// and we terminate the session
|
|
|
retry_conn = false;
|
|
|
}
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
//previous_status.push(PROCESSING_QUERY);
|
|
|
NEXT_IMMEDIATE_NEW(CONNECTING_SERVER);
|
|
|
}
|
|
|
*_rc = -1; // an error happened, we should destroy the Session
|
|
|
return ret;
|
|
|
}
|
|
|
else {
|
|
|
proxy_warning("Error while setting INIT CONNECT on %s:%d hg %d : %d, %d\n", myconn->parent->address, myconn->parent->port, current_hostgroup, myerr, 9999);
|
|
|
// we won't go back to PROCESSING_QUERY
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
char sqlstate[10];
|
|
|
sprintf(sqlstate, "%s", ""/* TODO: fix this mysql_sqlstate(myconn->pgsql)*/);
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 9999 /* TODO: fix this mysql_errno(myconn->pgsql)*/, sqlstate, "" /* TODO: fix this mysql_error(myconn->pgsql)*/);
|
|
|
myds->destroy_MySQL_Connection_From_Pool(true);
|
|
|
myds->fd = 0;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
// rc==1 , nothing to do for now
|
|
|
}
|
|
|
}
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_again___status_SETTING_GENERIC_VARIABLE(int* _rc, const char* var_name, const char* var_value,
|
|
|
bool no_quote, bool set_transaction) {
|
|
|
bool ret = false;
|
|
|
assert(mybe->server_myds->myconn);
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
myds->DSS = STATE_MARIADB_QUERY;
|
|
|
enum session_status st = status;
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime);
|
|
|
}
|
|
|
char* query = NULL;
|
|
|
unsigned long query_length = 0;
|
|
|
if (myconn->async_state_machine == ASYNC_IDLE) {
|
|
|
char* q = NULL;
|
|
|
if (set_transaction == false) {
|
|
|
if (no_quote) {
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
}
|
|
|
else {
|
|
|
q = (char*)"SET %s TO '%s'"; // default
|
|
|
if (var_value[0] && var_value[0] == '@') {
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
}
|
|
|
if (strncasecmp(var_value, (char*)"CONCAT", 6) == 0)
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
if (strncasecmp(var_value, (char*)"IFNULL", 6) == 0)
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
if (strncasecmp(var_value, (char*)"REPLACE", 7) == 0)
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
if (var_value[0] && var_value[0] == '(') { // the value is a subquery
|
|
|
q = (char*)"SET %s TO %s";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
// NOTE: for now, only SET SESSION is supported
|
|
|
// the calling function is already passing "SESSION TRANSACTION"
|
|
|
q = (char*)"SET %s %s";
|
|
|
}
|
|
|
query = (char*)malloc(strlen(q) + strlen(var_name) + strlen(var_value));
|
|
|
sprintf(query, q, var_name, var_value);
|
|
|
query_length = strlen(query);
|
|
|
}
|
|
|
int rc = myconn->async_send_simple_command(myds->revents, query, query_length);
|
|
|
if (query) {
|
|
|
free(query);
|
|
|
query = NULL;
|
|
|
}
|
|
|
if (rc == 0) {
|
|
|
if (strncasecmp(var_name, "client_encoding", sizeof("client_encoding")-1) == 0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.backend_set_client_encoding, 1);
|
|
|
}
|
|
|
myds->revents |= POLLOUT; // we also set again POLLOUT to send a query immediately!
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
|
|
|
/*if (strcasecmp("transaction isolation level", var_name) == 0) {
|
|
|
pgsql_variables.server_reset_value(this, SQL_NEXT_ISOLATION_LEVEL);
|
|
|
pgsql_variables.client_reset_value(this, SQL_NEXT_ISOLATION_LEVEL);
|
|
|
} else if (strcasecmp("transaction read", var_name) == 0) {
|
|
|
pgsql_variables.server_reset_value(this, SQL_NEXT_TRANSACTION_READ);
|
|
|
pgsql_variables.client_reset_value(this, SQL_NEXT_TRANSACTION_READ);
|
|
|
}*/
|
|
|
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
} else {
|
|
|
if (rc == -1) {
|
|
|
// the command failed
|
|
|
bool error_present = myconn->is_error_present();
|
|
|
PgHGM->p_update_pgsql_error_counter(
|
|
|
p_pgsql_error_type::pgsql,
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port,
|
|
|
(error_present ? 9999 : ER_PROXYSQL_OFFLINE_SRV) // TOFIX: 9999 is a placeholder for the actual error code
|
|
|
);
|
|
|
if (error_present == false || (error_present == true && myconn->is_connection_in_reusable_state() == false)) {
|
|
|
bool retry_conn = false;
|
|
|
// client error, serious
|
|
|
detected_broken_connection(__FILE__, __LINE__, __func__, "while setting ", myconn);
|
|
|
if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
retry_conn = true;
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
NEXT_IMMEDIATE_NEW(CONNECTING_SERVER);
|
|
|
}
|
|
|
*_rc = -1;
|
|
|
return false;
|
|
|
} else {
|
|
|
proxy_warning("Error while setting %s to \"%s\" on %s:%d hg %d: %s\n", var_name, var_value, myconn->parent->address, myconn->parent->port, current_hostgroup, myconn->get_error_code_with_message().c_str());
|
|
|
|
|
|
if (myconn->get_error_code() == PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR ||
|
|
|
myconn->get_error_code() == PGSQL_ERROR_CODES::ERRCODE_UNDEFINED_PARAMETER ||
|
|
|
myconn->get_error_code() == PGSQL_ERROR_CODES::ERRCODE_UNDEFINED_OBJECT) {
|
|
|
|
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
|
for (int i = PGSQL_NAME_LAST_LOW_WM + 1; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
if (variable_name_exists(pgsql_tracked_variables[i], var_name) == true) {
|
|
|
idx = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (idx != PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
myconn->var_absent[idx] = true;
|
|
|
|
|
|
myds->myconn->async_free_result();
|
|
|
myconn->compute_unknown_transaction_status();
|
|
|
|
|
|
myds->revents |= POLLOUT; // we also set again POLLOUT to send a query immediately!
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// we won't go back to PROCESSING_QUERY
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
client_myds->myprot.generate_error_packet(true, true, myconn->get_error_message().c_str(), myconn->get_error_code(), false);
|
|
|
myds->destroy_MySQL_Connection_From_Pool(true);
|
|
|
myds->fd = 0;
|
|
|
RequestEnd(myds); //fix bug #682
|
|
|
ret = true;
|
|
|
}
|
|
|
} else {
|
|
|
// rc==1 , nothing to do for now
|
|
|
}
|
|
|
}
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) {
|
|
|
//fprintf(stderr,"CONNECTING_SERVER\n");
|
|
|
unsigned long long curtime = monotonic_time();
|
|
|
thread->atomic_curtime = curtime;
|
|
|
if (mirror) {
|
|
|
mybe->server_myds->connect_retries_on_failure = 0; // no try for mirror
|
|
|
mybe->server_myds->wait_until = thread->curtime + pgsql_thread___connect_timeout_server * 1000;
|
|
|
pause_until = 0;
|
|
|
}
|
|
|
if (mybe->server_myds->max_connect_time ) {
|
|
|
if (thread->curtime >= mybe->server_myds->max_connect_time) {
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
|
|
|
string errmsg{};
|
|
|
const string session_info{ session_fast_forward ? "for 'fast_forward' session " : "" };
|
|
|
const uint64_t query_time = (thread->curtime - CurrentQuery.start_time) / 1000;
|
|
|
|
|
|
string_format(
|
|
|
"Max connect timeout reached while reaching hostgroup %d %safter %llums",
|
|
|
errmsg, current_hostgroup, session_info.c_str(), query_time
|
|
|
);
|
|
|
|
|
|
if (thread) {
|
|
|
thread->status_variables.stvar[st_var_max_connect_timeout_err]++;
|
|
|
}
|
|
|
client_myds->myprot.generate_error_packet(true, true, errmsg.c_str(), PGSQL_ERROR_CODES::ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
|
|
|
false, true);
|
|
|
RequestEnd(mybe->server_myds);
|
|
|
|
|
|
string hg_status{};
|
|
|
generate_status_one_hostgroup(current_hostgroup, hg_status);
|
|
|
proxy_error("%s . HG status: %s\n", errmsg.c_str(), hg_status.c_str());
|
|
|
|
|
|
while (previous_status.size()) {
|
|
|
previous_status.pop();
|
|
|
}
|
|
|
if (mybe->server_myds->myconn) {
|
|
|
// NOTE-3404: Created connection never reached 'connect_cont' phase, due to that internal
|
|
|
// structures of 'pgsql->net' are not fully initialized. This induces a leak of the 'fd'
|
|
|
// associated with the socket opened by the library. To prevent this, we need to call
|
|
|
// `mysql_real_connect_cont` through `connect_cont`. This way we ensure a proper cleanup of
|
|
|
// all the resources when 'mysql_close' is later called. For more context see issue #3404.
|
|
|
mybe->server_myds->myconn->connect_cont(PG_EVENT_NONE);
|
|
|
mybe->server_myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA);
|
|
|
}
|
|
|
}
|
|
|
mybe->server_myds->max_connect_time = 0;
|
|
|
NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA);
|
|
|
}
|
|
|
}
|
|
|
if (mybe->server_myds->myconn == NULL) {
|
|
|
handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED__get_connection();
|
|
|
}
|
|
|
if (mybe->server_myds->myconn == NULL) {
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// NOTE-connect_retries_delay: This check alone is not enough for imposing
|
|
|
// 'pgsql_thread___connect_retries_delay'. In case of 'async_connect' failing, 'pause_until' should also
|
|
|
// be set to 'pgsql_thread___connect_retries_delay'. Complementary NOTE below.
|
|
|
if (mybe->server_myds->myconn == NULL) {
|
|
|
pause_until = thread->curtime + pgsql_thread___connect_retries_delay * 1000;
|
|
|
*_rc = 1;
|
|
|
return false;
|
|
|
}
|
|
|
else {
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
int rc;
|
|
|
if (default_hostgroup < 0) {
|
|
|
// we are connected to a Admin module backend
|
|
|
// we pretend to set a user variable to disable multiplexing
|
|
|
myconn->set_status(true, STATUS_MYSQL_CONNECTION_USER_VARIABLE);
|
|
|
}
|
|
|
enum session_status st = status;
|
|
|
if (mybe->server_myds->myconn->async_state_machine == ASYNC_IDLE) {
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
}
|
|
|
assert(st == status);
|
|
|
unsigned long long curtime = monotonic_time();
|
|
|
|
|
|
assert(myconn->async_state_machine != ASYNC_IDLE);
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
rc = myconn->async_connect(myds->revents);
|
|
|
if (myds->mypolls == NULL) {
|
|
|
// connection yet not in mypolls
|
|
|
myds->assign_fd_from_mysql_conn();
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, curtime);
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
}
|
|
|
switch (rc) {
|
|
|
case 0:
|
|
|
myds->myds_type = MYDS_BACKEND;
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
myds->wait_until = 0;
|
|
|
if (session_fast_forward) {
|
|
|
// we have a successful connection and session_fast_forward enabled
|
|
|
// set DSS=STATE_SLEEP or it will believe it have to use MARIADB client library
|
|
|
myds->DSS = STATE_SLEEP;
|
|
|
myds->myconn->send_quit = false;
|
|
|
myds->myconn->reusable = false;
|
|
|
// In a 'fast_forward' session after we disable compression for the fronted connection
|
|
|
// after we have adquired a backend connection, this is, the 'FAST_FORWARD' session status
|
|
|
// is reached, and the 1-1 connection relationship is established. We can safely do this
|
|
|
// due two main reasons:
|
|
|
// 1. The client and backend have to agree on compression, i.e. if the client connected without
|
|
|
// compression using fast-forward to a backend connections expected to have compression, it results
|
|
|
// in a fallback to a connection without compression, as it's expected by protocol. In this case we do
|
|
|
// not require to compress the data received from the backend.
|
|
|
// 2. The client and backend have agreed in using compression, in this case, the data received from
|
|
|
// the backend is already compressed, so we are only required to forward the data to the client.
|
|
|
// In both cases, we do not require to perform any specials actions for the received data,
|
|
|
// so we completely disable the compression flag for the client connection.
|
|
|
client_myds->myconn->set_status(false, STATUS_MYSQL_CONNECTION_COMPRESSION);
|
|
|
}
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
break;
|
|
|
case -1:
|
|
|
case -2:
|
|
|
PgHGM->p_update_pgsql_error_counter(
|
|
|
p_pgsql_error_type::pgsql,
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port, 9999 /* TODO: fix this mysql_errno(myconn->pgsql)*/);
|
|
|
|
|
|
if (myds->connect_retries_on_failure > 0) {
|
|
|
myds->connect_retries_on_failure--;
|
|
|
|
|
|
if (myconn->is_error_present() &&
|
|
|
myconn->get_error_code() == PGSQL_ERROR_CODES::ERRCODE_TOO_MANY_CONNECTIONS) {
|
|
|
goto __exit_handler_again___status_CONNECTING_SERVER_with_err;
|
|
|
}
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
// NOTE-connect_retries_delay: In case of failure to connect, if
|
|
|
// 'pgsql_thread___connect_retries_delay' is set, we impose a delay in the session
|
|
|
// processing via 'pause_until'. Complementary NOTE above.
|
|
|
if (pgsql_thread___connect_retries_delay) {
|
|
|
pause_until = thread->curtime + pgsql_thread___connect_retries_delay * 1000;
|
|
|
set_status(CONNECTING_SERVER);
|
|
|
return false;
|
|
|
}
|
|
|
NEXT_IMMEDIATE_NEW(CONNECTING_SERVER);
|
|
|
}
|
|
|
else {
|
|
|
__exit_handler_again___status_CONNECTING_SERVER_with_err:
|
|
|
bool is_error_present = myconn->is_error_present();
|
|
|
if (is_error_present) {
|
|
|
client_myds->myprot.generate_error_packet(true, true, myconn->error_info.message.c_str(),
|
|
|
myconn->error_info.code, false, true);
|
|
|
} else {
|
|
|
char buf[256];
|
|
|
sprintf(buf, "Max connect failure while reaching hostgroup %d", current_hostgroup);
|
|
|
client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
|
|
|
false, true);
|
|
|
if (thread) {
|
|
|
thread->status_variables.stvar[st_var_max_connect_timeout_err]++;
|
|
|
}
|
|
|
}
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
// see bug #979
|
|
|
RequestEnd(myds);
|
|
|
}
|
|
|
while (previous_status.size()) {
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
}
|
|
|
if (mirror) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(is_error_present);
|
|
|
myds->max_connect_time = 0;
|
|
|
NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA);
|
|
|
}
|
|
|
break;
|
|
|
case 1: // continue on next loop
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler_again___status_RESETTING_CONNECTION(int* _rc) {
|
|
|
assert(mybe->server_myds->myconn);
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
myds->DSS = STATE_MARIADB_QUERY;
|
|
|
enum session_status st = status;
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime);
|
|
|
}
|
|
|
|
|
|
if (pgsql_thread___connect_timeout_server_max) {
|
|
|
if (mybe->server_myds->max_connect_time == 0) {
|
|
|
mybe->server_myds->max_connect_time = thread->curtime + pgsql_thread___connect_timeout_server_max * 1000;
|
|
|
}
|
|
|
}
|
|
|
int rc = myconn->async_reset_session(myds->revents);
|
|
|
if (rc == 0) {
|
|
|
__sync_fetch_and_add(&PgHGM->status.backend_reset_connection, 1);
|
|
|
//myds->myconn->userinfo->set(client_myds->myconn->userinfo);
|
|
|
myds->myconn->reset();
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
NEXT_IMMEDIATE_NEW(st);
|
|
|
} else {
|
|
|
if (rc == -1) {
|
|
|
// the command failed
|
|
|
const bool error_present = myconn->is_error_present();
|
|
|
PgHGM->p_update_pgsql_error_counter(
|
|
|
p_pgsql_error_type::pgsql,
|
|
|
myconn->parent->myhgc->hid,
|
|
|
myconn->parent->address,
|
|
|
myconn->parent->port,
|
|
|
(error_present ? 9999 : ER_PROXYSQL_OFFLINE_SRV) // TOFIX: 9999 is a placeholder for the actual error code
|
|
|
);
|
|
|
if (error_present == false || (error_present == true && myconn->is_connection_in_reusable_state() == false)) {
|
|
|
bool retry_conn = false;
|
|
|
// client error, serious
|
|
|
detected_broken_connection(__FILE__, __LINE__, __func__, "during Resetting Connection", myconn);
|
|
|
if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
retry_conn = true;
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
NEXT_IMMEDIATE_NEW(CONNECTING_SERVER);
|
|
|
}
|
|
|
*_rc = -1;
|
|
|
return false;
|
|
|
} else {
|
|
|
proxy_warning("Error during Resetting Connection: %s\n", myconn->get_error_code_with_message().c_str());
|
|
|
// we won't go back to PROCESSING_QUERY
|
|
|
st = previous_status.top();
|
|
|
previous_status.pop();
|
|
|
client_myds->myprot.generate_error_packet(true, true, myconn->get_error_message().c_str(), myconn->get_error_code(), false);
|
|
|
myds->destroy_MySQL_Connection_From_Pool(true);
|
|
|
myds->fd = 0;
|
|
|
RequestEnd(myds); //fix bug #682
|
|
|
}
|
|
|
} else {
|
|
|
if (rc == -2) {
|
|
|
bool retry_conn = false;
|
|
|
proxy_error("Timeout during Resetting Connection on %s , %d\n", myconn->parent->address, myconn->parent->port);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::pgsql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_CHANGE_USER_TIMEOUT);
|
|
|
if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
retry_conn = true;
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
NEXT_IMMEDIATE_NEW(CONNECTING_SERVER);
|
|
|
}
|
|
|
*_rc = -1;
|
|
|
return false;
|
|
|
} else {
|
|
|
// rc==1 , nothing to do for now
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// ClickHouse doesn't support COM_INIT_DB , so we replace it
|
|
|
// with a COM_QUERY running USE
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB_replace_CLICKHOUSE(PtrSize_t& pkt) {
|
|
|
PtrSize_t _new_pkt;
|
|
|
_new_pkt.ptr = malloc(pkt.size + 4); // USE + space
|
|
|
memcpy(_new_pkt.ptr, pkt.ptr, 4);
|
|
|
unsigned char* _c = (unsigned char*)_new_pkt.ptr;
|
|
|
_c += 4; *_c = 0x03;
|
|
|
_c += 1; *_c = 'U';
|
|
|
_c += 1; *_c = 'S';
|
|
|
_c += 1; *_c = 'E';
|
|
|
_c += 1; *_c = ' ';
|
|
|
memcpy((char*)_new_pkt.ptr + 9, (char*)pkt.ptr + 5, pkt.size - 5);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
pkt.size += 4;
|
|
|
pkt.ptr = _new_pkt.ptr;
|
|
|
}
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = WAITING_CLIENT_DATA
|
|
|
// client_myds->DSS = STATE_SLEEP
|
|
|
// enum_mysql_command = _MYSQL_COM_QUERY
|
|
|
// it processes the session not MYSQL_SESSION
|
|
|
// Make sure that handler_function() doesn't free the packet
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___not_mysql(PtrSize_t& pkt) {
|
|
|
switch (session_type) {
|
|
|
case PROXYSQL_SESSION_ADMIN:
|
|
|
case PROXYSQL_SESSION_STATS:
|
|
|
// this is processed by the admin module
|
|
|
handler_function(this, (void*)GloAdmin, &pkt);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
break;
|
|
|
case PROXYSQL_SESSION_SQLITE:
|
|
|
handler_function(this, (void*)GloSQLite3Server, &pkt);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
break;
|
|
|
#ifdef PROXYSQLCLICKHOUSE
|
|
|
case PROXYSQL_SESSION_CLICKHOUSE:
|
|
|
handler_function(this, (void*)GloClickHouseServer, &pkt);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
break;
|
|
|
#endif /* PROXYSQLCLICKHOUSE */
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = WAITING_CLIENT_DATA
|
|
|
// client_myds->DSS = STATE_SLEEP
|
|
|
// enum_mysql_command = _MYSQL_COM_QUERY
|
|
|
// it searches for SQL injection
|
|
|
// it returns true if it detected an SQL injection
|
|
|
bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_detect_SQLi() {
|
|
|
if (client_myds->com_field_list == false) {
|
|
|
if (qpo->firewall_whitelist_mode != WUS_OFF) {
|
|
|
struct libinjection_sqli_state state;
|
|
|
int issqli;
|
|
|
const char* input = (char*)CurrentQuery.QueryPointer;
|
|
|
size_t slen = CurrentQuery.QueryLength;
|
|
|
libinjection_sqli_init(&state, input, slen, FLAG_SQL_MYSQL);
|
|
|
issqli = libinjection_is_sqli(&state);
|
|
|
if (issqli) {
|
|
|
bool allow_sqli = false;
|
|
|
allow_sqli = GloPgQPro->whitelisted_sqli_fingerprint(state.fingerprint);
|
|
|
if (allow_sqli) {
|
|
|
thread->status_variables.stvar[st_var_mysql_whitelisted_sqli_fingerprint]++;
|
|
|
}
|
|
|
else {
|
|
|
thread->status_variables.stvar[st_var_automatic_detected_sqli]++;
|
|
|
char* username = client_myds->myconn->userinfo->username;
|
|
|
char* client_address = client_myds->addr.addr;
|
|
|
proxy_error("SQLinjection detected with fingerprint of '%s' from client %s@%s . Query listed below:\n", state.fingerprint, username, client_address);
|
|
|
fwrite(CurrentQuery.QueryPointer, CurrentQuery.QueryLength, 1, stderr);
|
|
|
fprintf(stderr, "\n");
|
|
|
RequestEnd(NULL);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = WAITING_CLIENT_DATA
|
|
|
// client_myds->DSS = STATE_SLEEP_MULTI_PACKET
|
|
|
//
|
|
|
// replacing the single goto with return true
|
|
|
bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP_MULTI_PACKET(PtrSize_t& pkt) {
|
|
|
if (client_myds->multi_pkt.ptr == NULL) {
|
|
|
// not initialized yet
|
|
|
client_myds->multi_pkt.ptr = pkt.ptr;
|
|
|
client_myds->multi_pkt.size = pkt.size;
|
|
|
}
|
|
|
else {
|
|
|
PtrSize_t tmp_pkt;
|
|
|
tmp_pkt.ptr = client_myds->multi_pkt.ptr;
|
|
|
tmp_pkt.size = client_myds->multi_pkt.size;
|
|
|
client_myds->multi_pkt.size = pkt.size + tmp_pkt.size - sizeof(mysql_hdr);
|
|
|
client_myds->multi_pkt.ptr = l_alloc(client_myds->multi_pkt.size);
|
|
|
memcpy(client_myds->multi_pkt.ptr, tmp_pkt.ptr, tmp_pkt.size);
|
|
|
memcpy((char*)client_myds->multi_pkt.ptr + tmp_pkt.size, (char*)pkt.ptr + sizeof(mysql_hdr), pkt.size - sizeof(mysql_hdr)); // the header is not copied
|
|
|
l_free(tmp_pkt.size, tmp_pkt.ptr);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
}
|
|
|
if (pkt.size == (0xFFFFFF + sizeof(mysql_hdr))) { // there are more packets
|
|
|
//goto __get_pkts_from_client;
|
|
|
return true;
|
|
|
}
|
|
|
else {
|
|
|
// no more packets, move everything back to pkt and proceed
|
|
|
pkt.ptr = client_myds->multi_pkt.ptr;
|
|
|
pkt.size = client_myds->multi_pkt.size;
|
|
|
client_myds->multi_pkt.size = 0;
|
|
|
client_myds->multi_pkt.ptr = NULL;
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = WAITING_CLIENT_DATA
|
|
|
// client_myds->DSS = STATE_SLEEP
|
|
|
// enum_mysql_command in a large list of possible values
|
|
|
// the most common values for enum_mysql_command are handled from the calling function
|
|
|
// here we only process the not so common ones
|
|
|
// we return false if the enum_mysql_command is not found
|
|
|
bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM__various(PtrSize_t* pkt, bool* wrong_pass) {
|
|
|
unsigned char c;
|
|
|
c = *((unsigned char*)pkt->ptr + sizeof(mysql_hdr));
|
|
|
switch ((enum_mysql_command)c) {
|
|
|
case _MYSQL_COM_CHANGE_USER:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_CHANGE_USER(pkt, wrong_pass);
|
|
|
break;
|
|
|
case _MYSQL_COM_PING:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PING(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_SET_OPTION:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_SET_OPTION(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_STATISTICS:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STATISTICS(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_INIT_DB:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_FIELD_LIST:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_FIELD_LIST(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_PROCESS_KILL:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PROCESS_KILL(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_RESET_CONNECTION:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_RESET_CONNECTION(pkt);
|
|
|
break;
|
|
|
default:
|
|
|
return false;
|
|
|
break;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = NONE or default
|
|
|
//
|
|
|
// this is triggered when proxysql receives a packet when doesn't expect any
|
|
|
// for example while it is supposed to be sending resultset to client
|
|
|
void PgSQL_Session::handler___status_NONE_or_default(PtrSize_t& pkt) {
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
switch (client_myds->client_addr->sa_family) {
|
|
|
case AF_INET: {
|
|
|
struct sockaddr_in* ipv4 = (struct sockaddr_in*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN);
|
|
|
break;
|
|
|
}
|
|
|
case AF_INET6: {
|
|
|
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
sprintf(buf, "localhost");
|
|
|
break;
|
|
|
}
|
|
|
if (pkt.size == 5) {
|
|
|
unsigned char c = *((unsigned char*)pkt.ptr + sizeof(mysql_hdr));
|
|
|
if (c == _MYSQL_COM_QUIT) {
|
|
|
proxy_error("Unexpected COM_QUIT from client %s . Session_status: %d , client_status: %d Disconnecting it\n", buf, status, client_myds->status);
|
|
|
if (GloPgSQL_Logger) { GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); }
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUIT packet\n");
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
if (thread) {
|
|
|
thread->status_variables.stvar[st_var_unexpected_com_quit]++;
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
proxy_error2(10001, "Unexpected packet from client %s . Session_status: %d , client_status: %d Disconnecting it\n", buf, status, client_myds->status);
|
|
|
if (thread) {
|
|
|
thread->status_variables.stvar[st_var_unexpected_packet]++;
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// this function was inline inside PgSQL_Session::get_pkts_from_client
|
|
|
// where:
|
|
|
// status = WAITING_CLIENT_DATA
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___default() {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Statuses: WAITING_CLIENT_DATA - STATE_UNKNOWN\n");
|
|
|
if (mirror == false) {
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
switch (client_myds->client_addr->sa_family) {
|
|
|
case AF_INET: {
|
|
|
struct sockaddr_in* ipv4 = (struct sockaddr_in*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN);
|
|
|
break;
|
|
|
}
|
|
|
case AF_INET6: {
|
|
|
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
sprintf(buf, "localhost");
|
|
|
break;
|
|
|
}
|
|
|
// PMC-10001: A unexpected packet has been received from client. This error has two potential causes:
|
|
|
// * Bug: ProxySQL state machine wasn't in the correct state when a legitimate client packet was received.
|
|
|
// * Client error: The client incorrectly sent a packet breaking MySQL protocol.
|
|
|
proxy_error2(10001, "Unexpected packet from client %s . Session_status: %d , client_status: %d Disconnecting it\n", buf, status, client_myds->status);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int PgSQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) {
|
|
|
int handler_ret = 0;
|
|
|
unsigned char c;
|
|
|
|
|
|
__get_pkts_from_client:
|
|
|
|
|
|
// implement a more complex logic to run even in case of mirror
|
|
|
// if client_myds , this is a regular client
|
|
|
// if client_myds == NULL , it is a mirror
|
|
|
// process mirror only status==WAITING_CLIENT_DATA
|
|
|
for (unsigned int j = 0; j < (client_myds->PSarrayIN ? client_myds->PSarrayIN->len : 0) || (mirror == true && status == WAITING_CLIENT_DATA);) {
|
|
|
if (mirror == false) {
|
|
|
client_myds->PSarrayIN->remove_index(0, &pkt);
|
|
|
}
|
|
|
switch (status) {
|
|
|
|
|
|
case CONNECTING_CLIENT:
|
|
|
switch (client_myds->DSS) {
|
|
|
case STATE_SSL_INIT:
|
|
|
case STATE_SERVER_HANDSHAKE:
|
|
|
handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(&pkt, &wrong_pass);
|
|
|
break;
|
|
|
default:
|
|
|
proxy_error("Detected not valid state client state: %d\n", client_myds->DSS);
|
|
|
handler_ret = -1; //close connection
|
|
|
return handler_ret;
|
|
|
break;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case WAITING_CLIENT_DATA:
|
|
|
// this is handled only for real traffic, not mirror
|
|
|
if (pkt.size == (0xFFFFFF + sizeof(mysql_hdr))) {
|
|
|
// we are handling a multi-packet
|
|
|
switch (client_myds->DSS) { // real traffic only
|
|
|
case STATE_SLEEP:
|
|
|
client_myds->DSS = STATE_SLEEP_MULTI_PACKET;
|
|
|
break;
|
|
|
case STATE_SLEEP_MULTI_PACKET:
|
|
|
break;
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
switch (client_myds->DSS) {
|
|
|
case STATE_SLEEP_MULTI_PACKET:
|
|
|
if (handler___status_WAITING_CLIENT_DATA___STATE_SLEEP_MULTI_PACKET(pkt)) {
|
|
|
// if handler___status_WAITING_CLIENT_DATA___STATE_SLEEP_MULTI_PACKET
|
|
|
// returns true it meansa we need to reiterate
|
|
|
goto __get_pkts_from_client;
|
|
|
}
|
|
|
// Note: the above function can change DSS to STATE_SLEEP
|
|
|
// in that case we don't break from the witch but continue
|
|
|
if (client_myds->DSS != STATE_SLEEP) // if DSS==STATE_SLEEP , we continue
|
|
|
break;
|
|
|
case STATE_SLEEP: // only this section can be executed ALSO by mirror
|
|
|
command_counters->incr(thread->curtime / 1000000);
|
|
|
if (transaction_persistent_hostgroup == -1) {
|
|
|
if (pgsql_thread___set_query_lock_on_hostgroup == 0) { // behavior before 2.0.6
|
|
|
current_hostgroup = default_hostgroup;
|
|
|
}
|
|
|
else {
|
|
|
if (locked_on_hostgroup == -1) {
|
|
|
current_hostgroup = default_hostgroup;
|
|
|
}
|
|
|
else {
|
|
|
current_hostgroup = locked_on_hostgroup;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , client_myds=%p . Statuses: WAITING_CLIENT_DATA - STATE_SLEEP\n", this, client_myds);
|
|
|
if (session_fast_forward) { // if it is fast forward
|
|
|
// If this is a 'fast_forward' session that hasn't yet received a backend connection, we don't
|
|
|
// forward 'QUIT' packets, since this will make the act of obtaining a connection pointless.
|
|
|
// Instead, we intercept the 'QUIT' packet and end the 'PgSQL_Session'.
|
|
|
unsigned char command = *(static_cast<unsigned char*>(pkt.ptr));
|
|
|
if (command == 'X') {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got QUIT packet\n");
|
|
|
if (GloPgSQL_Logger) { GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); }
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
|
|
|
mybe = find_or_create_backend(current_hostgroup); // set a backend
|
|
|
mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active
|
|
|
mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); // move the first packet
|
|
|
previous_status.push(FAST_FORWARD); // next status will be FAST_FORWARD . Now we need a connection
|
|
|
|
|
|
// If this is a 'fast_forward' session, we impose the 'connect_timeout' prior to actually getting the
|
|
|
// connection from the 'connection_pool'. This is used to ensure that we kill the session if
|
|
|
// 'CONNECTING_SERVER' isn't completed before this timeout expiring. For example, if 'max_connections'
|
|
|
// is reached for the target hostgroup.
|
|
|
if (mybe->server_myds->max_connect_time == 0) {
|
|
|
uint64_t connect_timeout =
|
|
|
pgsql_thread___connect_timeout_server < pgsql_thread___connect_timeout_server_max ?
|
|
|
pgsql_thread___connect_timeout_server_max : pgsql_thread___connect_timeout_server;
|
|
|
mybe->server_myds->max_connect_time = thread->curtime + connect_timeout * 1000;
|
|
|
}
|
|
|
// Impose the same connection retrying policy as done for regular connections during
|
|
|
// 'MYSQL_CON_QUERY'.
|
|
|
mybe->server_myds->connect_retries_on_failure = pgsql_thread___connect_retries_on_failure;
|
|
|
// 'CurrentQuery' isn't used for 'FAST_FORWARD' but we update it for using it as a session
|
|
|
// startup time for when a fast_forward session has attempted to obtain a connection.
|
|
|
CurrentQuery.start_time = thread->curtime;
|
|
|
|
|
|
{
|
|
|
//NEXT_IMMEDIATE(CONNECTING_SERVER); // we create a connection . next status will be FAST_FORWARD
|
|
|
// we can't use NEXT_IMMEDIATE() inside get_pkts_from_client()
|
|
|
// instead we set status to CONNECTING_SERVER and return 0
|
|
|
// when we exit from get_pkts_from_client() we expect the label "handler_again"
|
|
|
set_status(CONNECTING_SERVER);
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
c = *((unsigned char*)pkt.ptr);
|
|
|
if (client_myds != NULL) {
|
|
|
if (session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
|
|
|
c = *((unsigned char*)pkt.ptr);
|
|
|
if (c == 'Q') {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___not_mysql(pkt);
|
|
|
} else if (c == 'X') {
|
|
|
//proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUIT packet\n");
|
|
|
//if (GloPgSQL_Logger) { GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); }
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
} else if (c == 'P' || c == 'B' || c == 'D' || c == 'E') {
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
continue;
|
|
|
} else {
|
|
|
proxy_error("Not implemented yet. Message type:'%c'\n", c);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Feature not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
|
false, true);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
//handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
char command = c = *((unsigned char*)pkt.ptr);
|
|
|
switch (command) {
|
|
|
case 'Q':
|
|
|
{
|
|
|
__sync_add_and_fetch(&thread->status_variables.stvar[st_var_queries], 1);
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
bool rc_break = false;
|
|
|
bool lock_hostgroup = false;
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
// Note: CurrentQuery sees the query as sent by the client.
|
|
|
// shortly after, the packets it used to contain the query will be deallocated
|
|
|
CurrentQuery.begin((unsigned char*)pkt.ptr, pkt.size, true);
|
|
|
}
|
|
|
rc_break = handler_special_queries(&pkt,&lock_hostgroup);
|
|
|
if (rc_break == true) {
|
|
|
if (mirror == false) {
|
|
|
// track also special queries
|
|
|
//RequestEnd(NULL);
|
|
|
// we moved this inside handler_special_queries()
|
|
|
// because a pointer was becoming invalid
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
timespec begint;
|
|
|
timespec endt;
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begint);
|
|
|
}
|
|
|
qpo = GloPgQPro->process_query(this, pkt.ptr, pkt.size, &CurrentQuery);
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &endt);
|
|
|
thread->status_variables.stvar[st_var_query_processor_time] = thread->status_variables.stvar[st_var_query_processor_time] +
|
|
|
(endt.tv_sec * 1000000000 + endt.tv_nsec) -
|
|
|
(begint.tv_sec * 1000000000 + begint.tv_nsec);
|
|
|
}
|
|
|
assert(qpo); // GloPgQPro->process_mysql_query() should always return a qpo
|
|
|
#if 0
|
|
|
// This block was moved from 'handler_special_queries' to support
|
|
|
// handling of 'USE' statements which are preceded by a comment.
|
|
|
// For more context check issue: #3493.
|
|
|
// ===================================================
|
|
|
if (session_type != PROXYSQL_SESSION_CLICKHOUSE) {
|
|
|
const char* qd = CurrentQuery.get_digest_text();
|
|
|
bool use_db_query = false;
|
|
|
|
|
|
if (qd != NULL) {
|
|
|
if (
|
|
|
(strncasecmp((char*)"USE", qd, 3) == 0)
|
|
|
&&
|
|
|
(
|
|
|
(strncasecmp((char*)"USE ", qd, 4) == 0)
|
|
|
||
|
|
|
(strncasecmp((char*)"USE`", qd, 4) == 0)
|
|
|
)
|
|
|
) {
|
|
|
use_db_query = true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (pkt.size > (5 + 4) && strncasecmp((char*)"USE ", (char*)pkt.ptr + 5, 4) == 0) {
|
|
|
use_db_query = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (use_db_query) {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(&pkt);
|
|
|
|
|
|
if (mirror == false) {
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
// ===================================================
|
|
|
if (qpo->max_lag_ms >= 0) {
|
|
|
thread->status_variables.stvar[st_var_queries_with_max_lag_ms]++;
|
|
|
}
|
|
|
rc_break = handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup);
|
|
|
if (mirror == false && rc_break == false) {
|
|
|
if (pgsql_thread___automatic_detect_sqli) {
|
|
|
if (handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_detect_SQLi()) {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (rc_break == true) {
|
|
|
if (mirror == false) {
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
if (mirror == false) {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___create_mirror_session();
|
|
|
}
|
|
|
|
|
|
if (autocommit_on_hostgroup >= 0) {
|
|
|
}
|
|
|
if (pgsql_thread___set_query_lock_on_hostgroup == 1) { // algorithm introduced in 2.0.6
|
|
|
if (locked_on_hostgroup < 0) {
|
|
|
if (lock_hostgroup) {
|
|
|
// we are locking on hostgroup now
|
|
|
if (qpo->destination_hostgroup >= 0) {
|
|
|
if (transaction_persistent_hostgroup == -1) {
|
|
|
current_hostgroup = qpo->destination_hostgroup;
|
|
|
}
|
|
|
}
|
|
|
locked_on_hostgroup = current_hostgroup;
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked]++;
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked_set_cmds]++;
|
|
|
}
|
|
|
}
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
if (current_hostgroup != locked_on_hostgroup) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
int l = CurrentQuery.QueryLength;
|
|
|
char* end = (char*)"";
|
|
|
if (l > 256) {
|
|
|
l = 253;
|
|
|
end = (char*)"...";
|
|
|
}
|
|
|
string nqn = string((char*)CurrentQuery.QueryPointer, l);
|
|
|
char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s";
|
|
|
char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64);
|
|
|
sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end);
|
|
|
client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION,
|
|
|
false, true);
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked_queries]++;
|
|
|
RequestEnd(NULL);
|
|
|
free(buf);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
mybe = find_or_create_backend(current_hostgroup);
|
|
|
status = PROCESSING_QUERY;
|
|
|
// set query retries
|
|
|
mybe->server_myds->query_retries_on_failure = pgsql_thread___query_retries_on_failure;
|
|
|
// if a number of retries is set in mysql_query_rules, that takes priority
|
|
|
if (qpo) {
|
|
|
if (qpo->retries >= 0) {
|
|
|
mybe->server_myds->query_retries_on_failure = qpo->retries;
|
|
|
}
|
|
|
}
|
|
|
mybe->server_myds->connect_retries_on_failure = pgsql_thread___connect_retries_on_failure;
|
|
|
mybe->server_myds->wait_until = 0;
|
|
|
pause_until = 0;
|
|
|
if (pgsql_thread___default_query_delay) {
|
|
|
pause_until = thread->curtime + pgsql_thread___default_query_delay * 1000;
|
|
|
}
|
|
|
if (qpo) {
|
|
|
if (qpo->delay > 0) {
|
|
|
if (pause_until == 0)
|
|
|
pause_until = thread->curtime;
|
|
|
pause_until += qpo->delay * 1000;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Received query to be processed...\n");
|
|
|
mybe->server_myds->killed_at = 0;
|
|
|
mybe->server_myds->kill_type = 0;
|
|
|
mybe->server_myds->mysql_real_query.init(&pkt);
|
|
|
mybe->server_myds->statuses.questions++;
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
case 'X':
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got QUIT packet\n");
|
|
|
if (GloPgSQL_Logger) { GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); }
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
break;
|
|
|
case 'P':
|
|
|
case 'B':
|
|
|
case 'D':
|
|
|
case 'E':
|
|
|
//ignore
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
continue;
|
|
|
case 'S':
|
|
|
default:
|
|
|
proxy_error("Not implemented yet. Message type:'%c'\n", c);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Feature not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
|
false, true);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
|
|
|
if ((enum_mysql_command)c == _MYSQL_COM_INIT_DB) {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB_replace_CLICKHOUSE(pkt);
|
|
|
c = *((unsigned char*)pkt.ptr + sizeof(mysql_hdr));
|
|
|
}
|
|
|
}
|
|
|
client_myds->com_field_list = false; // default
|
|
|
if (c == _MYSQL_COM_FIELD_LIST) {
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
MySQL_Protocol* myprot = &client_myds->myprot;
|
|
|
bool rcp = myprot->generate_COM_QUERY_from_COM_FIELD_LIST(&pkt);
|
|
|
if (rcp) {
|
|
|
// all went well
|
|
|
c = *((unsigned char*)pkt.ptr + sizeof(mysql_hdr));
|
|
|
client_myds->com_field_list = true;
|
|
|
}
|
|
|
else {
|
|
|
// parsing failed, proxysql will return not suppported command
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
switch ((enum_mysql_command)c) {
|
|
|
case _MYSQL_COM_QUERY:
|
|
|
__sync_add_and_fetch(&thread->status_variables.stvar[st_var_queries], 1);
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
bool rc_break = false;
|
|
|
bool lock_hostgroup = false;
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
// Note: CurrentQuery sees the query as sent by the client.
|
|
|
// shortly after, the packets it used to contain the query will be deallocated
|
|
|
CurrentQuery.begin((unsigned char*)pkt.ptr, pkt.size, true);
|
|
|
}
|
|
|
rc_break = handler_special_queries(&pkt, &lock_hostgroup);
|
|
|
if (rc_break == true) {
|
|
|
if (mirror == false) {
|
|
|
// track also special queries
|
|
|
//RequestEnd(NULL);
|
|
|
// we moved this inside handler_special_queries()
|
|
|
// because a pointer was becoming invalid
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
timespec begint;
|
|
|
timespec endt;
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begint);
|
|
|
}
|
|
|
qpo = GloPgQPro->process_query(this, pkt.ptr, pkt.size, &CurrentQuery);
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &endt);
|
|
|
thread->status_variables.stvar[st_var_query_processor_time] = thread->status_variables.stvar[st_var_query_processor_time] +
|
|
|
(endt.tv_sec * 1000000000 + endt.tv_nsec) -
|
|
|
(begint.tv_sec * 1000000000 + begint.tv_nsec);
|
|
|
}
|
|
|
assert(qpo); // GloPgQPro->process_mysql_query() should always return a qpo
|
|
|
#if 0
|
|
|
// This block was moved from 'handler_special_queries' to support
|
|
|
// handling of 'USE' statements which are preceded by a comment.
|
|
|
// For more context check issue: #3493.
|
|
|
// ===================================================
|
|
|
if (session_type != PROXYSQL_SESSION_CLICKHOUSE) {
|
|
|
const char* qd = CurrentQuery.get_digest_text();
|
|
|
bool use_db_query = false;
|
|
|
|
|
|
if (qd != NULL) {
|
|
|
if (
|
|
|
(strncasecmp((char*)"USE", qd, 3) == 0)
|
|
|
&&
|
|
|
(
|
|
|
(strncasecmp((char*)"USE ", qd, 4) == 0)
|
|
|
||
|
|
|
(strncasecmp((char*)"USE`", qd, 4) == 0)
|
|
|
)
|
|
|
) {
|
|
|
use_db_query = true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (pkt.size > (5 + 4) && strncasecmp((char*)"USE ", (char*)pkt.ptr + 5, 4) == 0) {
|
|
|
use_db_query = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (use_db_query) {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(&pkt);
|
|
|
|
|
|
if (mirror == false) {
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
// ===================================================
|
|
|
if (qpo->max_lag_ms >= 0) {
|
|
|
thread->status_variables.stvar[st_var_queries_with_max_lag_ms]++;
|
|
|
}
|
|
|
rc_break = handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup);
|
|
|
if (mirror == false && rc_break == false) {
|
|
|
if (pgsql_thread___automatic_detect_sqli) {
|
|
|
if (handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_detect_SQLi()) {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (rc_break == true) {
|
|
|
if (mirror == false) {
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
if (mirror == false) {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___create_mirror_session();
|
|
|
}
|
|
|
|
|
|
if (autocommit_on_hostgroup >= 0) {
|
|
|
}
|
|
|
if (pgsql_thread___set_query_lock_on_hostgroup == 1) { // algorithm introduced in 2.0.6
|
|
|
if (locked_on_hostgroup < 0) {
|
|
|
if (lock_hostgroup) {
|
|
|
// we are locking on hostgroup now
|
|
|
if (qpo->destination_hostgroup >= 0) {
|
|
|
if (transaction_persistent_hostgroup == -1) {
|
|
|
current_hostgroup = qpo->destination_hostgroup;
|
|
|
}
|
|
|
}
|
|
|
locked_on_hostgroup = current_hostgroup;
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked]++;
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked_set_cmds]++;
|
|
|
}
|
|
|
}
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
if (current_hostgroup != locked_on_hostgroup) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
int l = CurrentQuery.QueryLength;
|
|
|
char* end = (char*)"";
|
|
|
if (l > 256) {
|
|
|
l = 253;
|
|
|
end = (char*)"...";
|
|
|
}
|
|
|
string nqn = string((char*)CurrentQuery.QueryPointer, l);
|
|
|
char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s";
|
|
|
char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64);
|
|
|
sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end);
|
|
|
client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION,
|
|
|
false, true);
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked_queries]++;
|
|
|
RequestEnd(NULL);
|
|
|
free(buf);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
mybe = find_or_create_backend(current_hostgroup);
|
|
|
status = PROCESSING_QUERY;
|
|
|
// set query retries
|
|
|
mybe->server_myds->query_retries_on_failure = pgsql_thread___query_retries_on_failure;
|
|
|
// if a number of retries is set in mysql_query_rules, that takes priority
|
|
|
if (qpo) {
|
|
|
if (qpo->retries >= 0) {
|
|
|
mybe->server_myds->query_retries_on_failure = qpo->retries;
|
|
|
}
|
|
|
}
|
|
|
mybe->server_myds->connect_retries_on_failure = pgsql_thread___connect_retries_on_failure;
|
|
|
mybe->server_myds->wait_until = 0;
|
|
|
pause_until = 0;
|
|
|
if (pgsql_thread___default_query_delay) {
|
|
|
pause_until = thread->curtime + pgsql_thread___default_query_delay * 1000;
|
|
|
}
|
|
|
if (qpo) {
|
|
|
if (qpo->delay > 0) {
|
|
|
if (pause_until == 0)
|
|
|
pause_until = thread->curtime;
|
|
|
pause_until += qpo->delay * 1000;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Received query to be processed with MariaDB Client library\n");
|
|
|
mybe->server_myds->killed_at = 0;
|
|
|
mybe->server_myds->kill_type = 0;
|
|
|
mybe->server_myds->mysql_real_query.init(&pkt);
|
|
|
mybe->server_myds->statuses.questions++;
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
}
|
|
|
else {
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___not_mysql(pkt);
|
|
|
}
|
|
|
break;
|
|
|
/*case _MYSQL_COM_STMT_PREPARE:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_PREPARE(pkt);
|
|
|
break;
|
|
|
case _MYSQL_COM_STMT_EXECUTE:
|
|
|
handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_EXECUTE(pkt);
|
|
|
break;
|
|
|
*/
|
|
|
default:
|
|
|
// in this switch we only handle the most common commands.
|
|
|
// The not common commands are handled by "default" , that
|
|
|
// calls the following function
|
|
|
// handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM__various
|
|
|
//if (handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM__various(&pkt, &wrong_pass) == false) {
|
|
|
// If even this cannot find the command, we return an error to the client
|
|
|
proxy_error("RECEIVED AN UNKNOWN COMMAND: %d -- PLEASE REPORT A BUG\n", c);
|
|
|
l_free(pkt.size, pkt.ptr);
|
|
|
handler_ret = -1; // immediately drop the connection
|
|
|
return handler_ret;
|
|
|
//}
|
|
|
break;
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
handler___status_WAITING_CLIENT_DATA___default();
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
break;
|
|
|
}
|
|
|
break;
|
|
|
case FAST_FORWARD:
|
|
|
mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size);
|
|
|
break;
|
|
|
// This state is required because it covers the following situation:
|
|
|
// 1. A new connection is created by a client and the 'FAST_FORWARD' mode is enabled.
|
|
|
// 2. The first packet received for this connection isn't a whole packet, i.e, it's either
|
|
|
// split into multiple packets, or it doesn't fit 'queueIN' size (typically
|
|
|
// QUEUE_T_DEFAULT_SIZE).
|
|
|
// 3. Session is still in 'CONNECTING_SERVER' state, BUT further packets remain to be received
|
|
|
// from the initial split packet.
|
|
|
//
|
|
|
// Because of this, packets received during 'CONNECTING_SERVER' when the previous state is
|
|
|
// 'FAST_FORWARD' should be pushed to 'PSarrayOUT'.
|
|
|
case CONNECTING_SERVER:
|
|
|
if (previous_status.empty() == false && previous_status.top() == FAST_FORWARD) {
|
|
|
mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size);
|
|
|
break;
|
|
|
}
|
|
|
case session_status___NONE:
|
|
|
default:
|
|
|
handler___status_NONE_or_default(pkt);
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return handler_ret;
|
|
|
}
|
|
|
// end of PgSQL_Session::get_pkts_from_client()
|
|
|
|
|
|
|
|
|
// this function returns:
|
|
|
// 0 : no action
|
|
|
// -1 : the calling function will return
|
|
|
// 1 : call to NEXT_IMMEDIATE
|
|
|
int PgSQL_Session::handler_ProcessingQueryError_CheckBackendConnectionStatus(PgSQL_Data_Stream* myds) {
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
// the query failed
|
|
|
if (myconn->IsServerOffline()) {
|
|
|
// Set maximum connect time if connect timeout is configured
|
|
|
if (pgsql_thread___connect_timeout_server_max) {
|
|
|
myds->max_connect_time = thread->curtime + pgsql_thread___connect_timeout_server_max * 1000;
|
|
|
}
|
|
|
|
|
|
// Variables to track retry and error conditions
|
|
|
bool retry_conn = false;
|
|
|
if (myconn->server_status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) {
|
|
|
thread->status_variables.stvar[st_var_backend_lagging_during_query]++;
|
|
|
proxy_error("Detected a lagging server during query: %s, %d\n", myconn->parent->address, myconn->parent->port);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_LAGGING_SRV);
|
|
|
} else {
|
|
|
thread->status_variables.stvar[st_var_backend_offline_during_query]++;
|
|
|
proxy_error("Detected an offline server during query: %s, %d\n", myconn->parent->address, myconn->parent->port);
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_OFFLINE_SRV);
|
|
|
}
|
|
|
|
|
|
// Retry the query if retries are allowed and conditions permit
|
|
|
if (myds->query_retries_on_failure > 0) {
|
|
|
myds->query_retries_on_failure--;
|
|
|
if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) {
|
|
|
if (myds->myconn->query_result && myds->myconn->query_result->is_transfer_started()) {
|
|
|
// transfer to frontend has started, we cannot retry
|
|
|
} else {
|
|
|
retry_conn = true;
|
|
|
proxy_warning("Retrying query.\n");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
// Sets the previous status of the PgSQL session according to the current status.
|
|
|
set_previous_status_mode3();
|
|
|
return 1;
|
|
|
}
|
|
|
return -1;
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::SetQueryTimeout() {
|
|
|
mybe->server_myds->wait_until = 0;
|
|
|
if (qpo) {
|
|
|
if (qpo->timeout > 0) {
|
|
|
unsigned long long qr_timeout = qpo->timeout;
|
|
|
mybe->server_myds->wait_until = thread->curtime;
|
|
|
mybe->server_myds->wait_until += qr_timeout * 1000;
|
|
|
}
|
|
|
}
|
|
|
if (pgsql_thread___default_query_timeout) {
|
|
|
if (mybe->server_myds->wait_until == 0) {
|
|
|
mybe->server_myds->wait_until = thread->curtime;
|
|
|
unsigned long long def_query_timeout = pgsql_thread___default_query_timeout;
|
|
|
mybe->server_myds->wait_until += def_query_timeout * 1000;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// this function used to be inline.
|
|
|
// now it returns:
|
|
|
// true: NEXT_IMMEDIATE(CONNECTING_SERVER) needs to be called
|
|
|
// false: continue
|
|
|
bool PgSQL_Session::handler_minus1_ClientLibraryError(PgSQL_Data_Stream* myds) {
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
bool retry_conn = false;
|
|
|
// client error, serious
|
|
|
detected_broken_connection(__FILE__, __LINE__, __func__, "running query", myconn, true);
|
|
|
if (myds->query_retries_on_failure > 0) {
|
|
|
myds->query_retries_on_failure--;
|
|
|
if ((myconn->reusable == true) && myconn->IsActiveTransaction() == false && myconn->MultiplexDisabled() == false) {
|
|
|
if (myconn->query_result && myconn->query_result->is_transfer_started()) {
|
|
|
// transfer to frontend has started, we cannot retry
|
|
|
} else {
|
|
|
// This should never occur.
|
|
|
if (myconn->processing_multi_statement == true) {
|
|
|
// we are in the process of retriving results from a multi-statement query
|
|
|
proxy_warning("Disabling query retry because we were in middle of processing results\n");
|
|
|
} else {
|
|
|
retry_conn = true;
|
|
|
proxy_warning("Retrying query.\n");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
// Sets the previous status of the PgSQL session according to the current status.
|
|
|
set_previous_status_mode3();
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
// this function was inline
|
|
|
void PgSQL_Session::handler_minus1_LogErrorDuringQuery(PgSQL_Connection* myconn) {
|
|
|
if (pgsql_thread___verbose_query_error) {
|
|
|
proxy_warning("Error during query on (%d,%s,%d,%d) , user \"%s@%s\" , dbname \"%s\" , %s . digest_text = \"%s\"\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_backend_pid(), client_myds->myconn->userinfo->username, (client_myds->addr.addr ? client_myds->addr.addr : (char*)"unknown"), client_myds->myconn->userinfo->dbname, myconn->get_error_code_with_message().c_str(), CurrentQuery.QueryParserArgs.digest_text);
|
|
|
} else {
|
|
|
proxy_warning("Error during query on (%d,%s,%d,%d): %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_backend_pid(), myconn->get_error_code_with_message().c_str());
|
|
|
}
|
|
|
PgHGM->add_pgsql_errors(myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, client_myds->myconn->userinfo->username,
|
|
|
(client_myds->addr.addr ? client_myds->addr.addr : "unknown"), client_myds->myconn->userinfo->dbname,
|
|
|
myconn->get_error_code_str(), myconn->get_error_message().c_str());
|
|
|
}
|
|
|
|
|
|
|
|
|
// this function used to be inline.
|
|
|
// now it returns:
|
|
|
// true:
|
|
|
// if handler_ret == -1 : return
|
|
|
// if handler_ret == 0 : NEXT_IMMEDIATE(CONNECTING_SERVER) needs to be called
|
|
|
// false: continue
|
|
|
bool PgSQL_Session::handler_minus1_HandleErrorCodes(PgSQL_Data_Stream* myds, int& handler_ret) {
|
|
|
bool retry_conn = false;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
handler_ret = 0; // default
|
|
|
switch (myconn->get_error_code()) {
|
|
|
case PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED: // Query execution was interrupted
|
|
|
if (killed == true) { // this session is being kiled
|
|
|
handler_ret = -1;
|
|
|
return true;
|
|
|
}
|
|
|
if (myds->killed_at) {
|
|
|
// we intentionally killed the query
|
|
|
break;
|
|
|
}
|
|
|
break;
|
|
|
case PGSQL_ERROR_CODES::ERRCODE_ADMIN_SHUTDOWN: // Server shutdown in progress. Requested by Admin
|
|
|
case PGSQL_ERROR_CODES::ERRCODE_CRASH_SHUTDOWN: // Server shutdown in progress
|
|
|
case PGSQL_ERROR_CODES::ERRCODE_CANNOT_CONNECT_NOW: // Server in initialization mode and not ready to handle new connections
|
|
|
myconn->parent->connect_error(9999);
|
|
|
if (myds->query_retries_on_failure > 0) {
|
|
|
myds->query_retries_on_failure--;
|
|
|
if ((myconn->reusable == true) && myconn->IsActiveTransaction() == false && myconn->MultiplexDisabled() == false) {
|
|
|
retry_conn = true;
|
|
|
proxy_warning("Retrying query.\n");
|
|
|
}
|
|
|
}
|
|
|
myds->destroy_MySQL_Connection_From_Pool(false);
|
|
|
myconn = myds->myconn;
|
|
|
myds->fd = 0;
|
|
|
if (retry_conn) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
//previous_status.push(PROCESSING_QUERY);
|
|
|
set_previous_status_mode3(false);
|
|
|
return true; // it will call NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
//NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
}
|
|
|
//handler_ret = -1;
|
|
|
//return handler_ret;
|
|
|
break;
|
|
|
case PGSQL_ERROR_CODES::ERRCODE_OUT_OF_MEMORY:
|
|
|
proxy_warning("Error OUT_OF_MEMORY during query on (%d,%s,%d,%d): %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_backend_pid(), myconn->get_error_code_with_message().c_str());
|
|
|
break;
|
|
|
default:
|
|
|
break; // continue normally
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// this function used to be inline.
|
|
|
void PgSQL_Session::handler_minus1_GenerateErrorMessage(PgSQL_Data_Stream* myds, bool& wrong_pass) {
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
switch (status) {
|
|
|
case PROCESSING_QUERY:
|
|
|
if (myconn) {
|
|
|
PgSQL_Result_to_PgSQL_wire(myconn, myds);
|
|
|
}
|
|
|
else {
|
|
|
PgSQL_Result_to_PgSQL_wire(NULL, myds);
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// this function was inline
|
|
|
void PgSQL_Session::handler_minus1_HandleBackendConnection(PgSQL_Data_Stream* myds) {
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
if (myconn) {
|
|
|
myconn->reduce_auto_increment_delay_token();
|
|
|
if (pgsql_thread___multiplexing && (myconn->reusable == true) && myconn->IsActiveTransaction() == false && myconn->MultiplexDisabled() == false) {
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
if (mysql_thread___autocommit_false_not_reusable && myconn->IsAutoCommit() == false) {
|
|
|
create_new_session_and_reset_connection(myds);
|
|
|
} else {
|
|
|
myds->return_MySQL_Connection_To_Pool();
|
|
|
}
|
|
|
} else {
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// this function was inline
|
|
|
int PgSQL_Session::RunQuery(PgSQL_Data_Stream* myds, PgSQL_Connection* myconn) {
|
|
|
PROXY_TRACE2();
|
|
|
int rc = 0;
|
|
|
switch (status) {
|
|
|
case PROCESSING_QUERY:
|
|
|
rc = myconn->async_query(myds->revents, myds->mysql_real_query.QueryPtr, myds->mysql_real_query.QuerySize);
|
|
|
break;
|
|
|
/*case PROCESSING_STMT_PREPARE:
|
|
|
rc = myconn->async_query(myds->revents, (char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength, &CurrentQuery.mysql_stmt);
|
|
|
break;
|
|
|
case PROCESSING_STMT_EXECUTE:
|
|
|
PROXY_TRACE2();
|
|
|
rc = myconn->async_query(myds->revents, (char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength, &CurrentQuery.mysql_stmt, CurrentQuery.stmt_meta);
|
|
|
break;
|
|
|
*/
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
|
// this function was inline
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA() {
|
|
|
// NOTE: Maintenance of 'multiplex_delayed' has been moved to 'housekeeping_before_pkts'. The previous impl
|
|
|
// is left below as an example of how to perform a more passive maintenance over session connections.
|
|
|
}
|
|
|
|
|
|
int PgSQL_Session::handler() {
|
|
|
#if ENABLE_TIMER
|
|
|
Timer timer(thread->Timers.Sessions_Handlers);
|
|
|
#endif // ENABLE_TIMER
|
|
|
int handler_ret = 0;
|
|
|
bool prepared_stmt_with_no_params = false;
|
|
|
bool wrong_pass = false;
|
|
|
if (to_process == 0) return 0; // this should be redundant if the called does the same check
|
|
|
proxy_debug(PROXY_DEBUG_NET, 1, "Thread=%p, Session=%p -- Processing session %p\n", this->thread, this, this);
|
|
|
//unsigned int j;
|
|
|
//unsigned char c;
|
|
|
|
|
|
// FIXME: Sessions without frontend are an ugly hack
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
if (client_myds == NULL) {
|
|
|
// if we are here, probably we are trying to ping backends
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Processing session %p without client_myds\n", this);
|
|
|
assert(mybe);
|
|
|
assert(mybe->server_myds);
|
|
|
goto handler_again;
|
|
|
}
|
|
|
else {
|
|
|
if (mirror == true) {
|
|
|
if (mirrorPkt.ptr) { // this is the first time we call handler()
|
|
|
pkt.ptr = mirrorPkt.ptr;
|
|
|
pkt.size = mirrorPkt.size;
|
|
|
mirrorPkt.ptr = NULL; // this will prevent the copy to happen again
|
|
|
}
|
|
|
else {
|
|
|
if (status == WAITING_CLIENT_DATA) {
|
|
|
// we are being called a second time with WAITING_CLIENT_DATA
|
|
|
handler_ret = 0;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
housekeeping_before_pkts();
|
|
|
handler_ret = get_pkts_from_client(wrong_pass, pkt);
|
|
|
if (handler_ret != 0) {
|
|
|
return handler_ret;
|
|
|
}
|
|
|
|
|
|
handler_again:
|
|
|
|
|
|
switch (status) {
|
|
|
case WAITING_CLIENT_DATA:
|
|
|
// housekeeping
|
|
|
handler___status_WAITING_CLIENT_DATA();
|
|
|
break;
|
|
|
case FAST_FORWARD:
|
|
|
{
|
|
|
if (mybe->server_myds->mypolls == NULL) {
|
|
|
// register the PgSQL_Data_Stream
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime);
|
|
|
}
|
|
|
client_myds->PSarrayOUT->copy_add(mybe->server_myds->PSarrayIN, 0, mybe->server_myds->PSarrayIN->len);
|
|
|
|
|
|
constexpr unsigned char ready_packet[] = { 0x5A, 0x00, 0x00, 0x00, 0x05 };
|
|
|
bool is_copy_ready_packet = false;
|
|
|
while (mybe->server_myds->PSarrayIN->len) {
|
|
|
|
|
|
// if session_fast_forward type is COPY STDIN, we need to check if it is ready packet
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_COPY_FROM_STDIN_STDOUT) {
|
|
|
const PtrSize_t& data = mybe->server_myds->PSarrayIN->pdata[mybe->server_myds->PSarrayIN->len - 1];
|
|
|
if (is_copy_ready_packet == false && data.size == 6) {
|
|
|
//const unsigned char* ptr = (static_cast<unsigned char*>(data.ptr) /*+ (data.size - 6)*/);
|
|
|
if (memcmp(data.ptr, ready_packet, sizeof(ready_packet)) == 0) {
|
|
|
is_copy_ready_packet = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
mybe->server_myds->PSarrayIN->remove_index(mybe->server_myds->PSarrayIN->len - 1, NULL);
|
|
|
}
|
|
|
|
|
|
// if ready packet is found, we need to switch back to normal mode
|
|
|
if (is_copy_ready_packet) {
|
|
|
switch_fast_forward_to_normal_mode();
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
case CONNECTING_CLIENT:
|
|
|
//fprintf(stderr,"CONNECTING_CLIENT\n");
|
|
|
// FIXME: to implement
|
|
|
break;
|
|
|
case PINGING_SERVER:
|
|
|
{
|
|
|
int rc = handler_again___status_PINGING_SERVER();
|
|
|
if (rc == -1) { // if the ping fails, we destroy the session
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case RESETTING_CONNECTION:
|
|
|
{
|
|
|
int rc = handler_again___status_RESETTING_CONNECTION();
|
|
|
if (rc == -1) { // we always destroy the session
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
//case PROCESSING_STMT_PREPARE:
|
|
|
//case PROCESSING_STMT_EXECUTE:
|
|
|
case PROCESSING_QUERY:
|
|
|
//fprintf(stderr,"PROCESSING_QUERY\n");
|
|
|
if (pause_until > thread->curtime) {
|
|
|
handler_ret = 0;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
if (pgsql_thread___connect_timeout_server_max) {
|
|
|
if (mybe->server_myds->max_connect_time == 0)
|
|
|
mybe->server_myds->max_connect_time = thread->curtime + (long long)pgsql_thread___connect_timeout_server_max * 1000;
|
|
|
}
|
|
|
else {
|
|
|
mybe->server_myds->max_connect_time = 0;
|
|
|
}
|
|
|
if (
|
|
|
(mybe->server_myds->myconn && mybe->server_myds->myconn->async_state_machine != ASYNC_IDLE && mybe->server_myds->wait_until && thread->curtime >= mybe->server_myds->wait_until)
|
|
|
// query timed out
|
|
|
||
|
|
|
(killed == true) // session was killed by admin
|
|
|
) {
|
|
|
// we only log in case on timing out here. Logging for 'killed' is done in the places that hold that contextual information.
|
|
|
if (mybe->server_myds->myconn && (mybe->server_myds->myconn->async_state_machine != ASYNC_IDLE) && mybe->server_myds->wait_until && (thread->curtime >= mybe->server_myds->wait_until)) {
|
|
|
std::string query{};
|
|
|
|
|
|
if (CurrentQuery.stmt_info == NULL) { // text protocol
|
|
|
query = std::string{ mybe->server_myds->myconn->query.ptr, mybe->server_myds->myconn->query.length };
|
|
|
}
|
|
|
else { // prepared statement
|
|
|
query = std::string{ CurrentQuery.stmt_info->query, CurrentQuery.stmt_info->query_length };
|
|
|
}
|
|
|
|
|
|
std::string client_addr{ "" };
|
|
|
int client_port = 0;
|
|
|
|
|
|
if (client_myds) {
|
|
|
client_addr = client_myds->addr.addr ? client_myds->addr.addr : "";
|
|
|
client_port = client_myds->addr.port;
|
|
|
}
|
|
|
|
|
|
proxy_warning(
|
|
|
"Killing connection %s:%d because query '%s' from client '%s':%d timed out.\n",
|
|
|
mybe->server_myds->myconn->parent->address,
|
|
|
mybe->server_myds->myconn->parent->port,
|
|
|
query.c_str(),
|
|
|
client_addr.c_str(),
|
|
|
client_port
|
|
|
);
|
|
|
}
|
|
|
handler_again___new_thread_to_kill_connection();
|
|
|
}
|
|
|
if (mybe->server_myds->DSS == STATE_NOT_INITIALIZED) {
|
|
|
// we don't have a backend yet
|
|
|
// It saves the current processing status of the session (status) onto the previous_status stack
|
|
|
// Sets the previous status of the PgSQL session according to the current status.
|
|
|
set_previous_status_mode3();
|
|
|
// It transitions the session to the CONNECTING_SERVER state immediately.
|
|
|
NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
} else {
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
mybe->server_myds->max_connect_time = 0;
|
|
|
// we insert it in mypolls only if not already there
|
|
|
if (myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime);
|
|
|
}
|
|
|
if (default_hostgroup >= 0) {
|
|
|
if (handler_again___verify_backend_user_db()) {
|
|
|
goto handler_again;
|
|
|
}
|
|
|
if (mirror == false) { // do not care about autocommit and charset if mirror
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session %p , default_HG=%d server_myds DSS=%d , locked_on_HG=%d\n", this, default_hostgroup, mybe->server_myds->DSS, locked_on_hostgroup);
|
|
|
if (mybe->server_myds->DSS == STATE_READY || mybe->server_myds->DSS == STATE_MARIADB_GENERIC) {
|
|
|
if (handler_again___verify_init_connect()) {
|
|
|
goto handler_again;
|
|
|
}
|
|
|
if (locked_on_hostgroup == -1 || locked_on_hostgroup_and_all_variables_set == false) {
|
|
|
|
|
|
for (auto i = 0; i < PGSQL_NAME_LAST_LOW_WM; i++) {
|
|
|
auto client_hash = client_myds->myconn->var_hash[i];
|
|
|
#ifdef DEBUG
|
|
|
if (GloVars.global.gdbg) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session %p , variable %s has value %s\n", this, pgsql_tracked_variables[i].set_variable_name, client_myds->myconn->variables[i].value);
|
|
|
}
|
|
|
#endif // DEBUG
|
|
|
if (client_hash) {
|
|
|
auto server_hash = myconn->var_hash[i];
|
|
|
if (client_hash != server_hash) {
|
|
|
if (!myconn->var_absent[i] && pgsql_variables.verify_variable(this, i)) {
|
|
|
goto handler_again;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
PgSQL_Connection* c_con = client_myds->myconn;
|
|
|
vector<uint32_t>::const_iterator it_c = c_con->dynamic_variables_idx.begin(); // client connection iterator
|
|
|
for (; it_c != c_con->dynamic_variables_idx.end(); it_c++) {
|
|
|
auto i = *it_c;
|
|
|
auto client_hash = c_con->var_hash[i];
|
|
|
auto server_hash = myconn->var_hash[i];
|
|
|
if (client_hash != server_hash) {
|
|
|
if (!myconn->var_absent[i] && pgsql_variables.verify_variable(this, i)) {
|
|
|
goto handler_again;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (locked_on_hostgroup != -1) {
|
|
|
locked_on_hostgroup_and_all_variables_set = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
/*if (status == PROCESSING_STMT_EXECUTE) {
|
|
|
CurrentQuery.mysql_stmt = myconn->local_stmts->find_backend_stmt_by_global_id(CurrentQuery.stmt_global_id);
|
|
|
if (CurrentQuery.mysql_stmt == NULL) {
|
|
|
MySQL_STMT_Global_info* stmt_info = NULL;
|
|
|
// the connection we too doesn't have the prepared statements prepared
|
|
|
// we try to create it now
|
|
|
stmt_info = GloMyStmt->find_prepared_statement_by_stmt_id(CurrentQuery.stmt_global_id);
|
|
|
CurrentQuery.QueryLength = stmt_info->query_length;
|
|
|
CurrentQuery.QueryPointer = (unsigned char*)stmt_info->query;
|
|
|
// NOTE: Update 'first_comment' with the 'first_comment' from the retrieved
|
|
|
// 'stmt_info' from the found prepared statement. 'CurrentQuery' requires its
|
|
|
// own copy of 'first_comment' because it will later be free by 'QueryInfo::end'.
|
|
|
if (stmt_info->first_comment) {
|
|
|
CurrentQuery.QueryParserArgs.first_comment = strdup(stmt_info->first_comment);
|
|
|
}
|
|
|
previous_status.push(PROCESSING_STMT_EXECUTE);
|
|
|
NEXT_IMMEDIATE(PROCESSING_STMT_PREPARE);
|
|
|
if (CurrentQuery.stmt_global_id != stmt_info->statement_id) {
|
|
|
PROXY_TRACE();
|
|
|
}
|
|
|
}
|
|
|
}*/
|
|
|
}
|
|
|
}
|
|
|
// Swtich to fast forward mode if the query matches copy ... stdin command
|
|
|
re2::StringPiece matched;
|
|
|
const char* query_to_match = (CurrentQuery.get_digest_text() ? CurrentQuery.get_digest_text() : (char*)CurrentQuery.QueryPointer);
|
|
|
if (copy_cmd_matcher->match(query_to_match, &matched)) {
|
|
|
switch_normal_to_fast_forward_mode(pkt, std::string(matched.data(), matched.size()), SESSION_FORWARD_TYPE_COPY_FROM_STDIN_STDOUT);
|
|
|
break;
|
|
|
}
|
|
|
if (myconn->async_state_machine == ASYNC_IDLE) {
|
|
|
SetQueryTimeout();
|
|
|
}
|
|
|
int rc;
|
|
|
timespec begint;
|
|
|
if (thread->variables.stats_time_backend_query) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begint);
|
|
|
}
|
|
|
rc = RunQuery(myds, myconn);
|
|
|
timespec endt;
|
|
|
if (thread->variables.stats_time_backend_query) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &endt);
|
|
|
thread->status_variables.stvar[st_var_backend_query_time] = thread->status_variables.stvar[st_var_backend_query_time] +
|
|
|
(endt.tv_sec * 1000000000 + endt.tv_nsec) -
|
|
|
(begint.tv_sec * 1000000000 + begint.tv_nsec);
|
|
|
}
|
|
|
|
|
|
if (rc == 0) {
|
|
|
|
|
|
if (active_transactions != 0) { // run this only if currently we think there is a transaction
|
|
|
if (myconn->IsKnownActiveTransaction() == false) { // there is no transaction on the backend connection
|
|
|
active_transactions = NumActiveTransactions(); // we check all the hostgroups/backends
|
|
|
if (active_transactions == 0)
|
|
|
transaction_started_at = 0; // reset it
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// if we are locked on hostgroup, the value of autocommit is copied from the backend connection
|
|
|
// see bug #3549
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
assert(myconn != NULL);
|
|
|
assert(myconn->pgsql_conn != NULL);
|
|
|
//autocommit = myconn->pgsql->server_status & SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
|
|
|
/*if (mirror == false && myconn->pgsql) {
|
|
|
// Support for LAST_INSERT_ID()
|
|
|
if (myconn->pgsql->insert_id) {
|
|
|
last_insert_id = myconn->pgsql->insert_id;
|
|
|
}
|
|
|
if (myconn->pgsql->affected_rows) {
|
|
|
if (myconn->pgsql->affected_rows != ULLONG_MAX) {
|
|
|
last_HG_affected_rows = current_hostgroup;
|
|
|
if (pgsql_thread___auto_increment_delay_multiplex && myconn->pgsql->insert_id) {
|
|
|
myconn->auto_increment_delay_token = pgsql_thread___auto_increment_delay_multiplex + 1;
|
|
|
__sync_fetch_and_add(&PgHGM->status.auto_increment_delay_multiplex, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}*/
|
|
|
|
|
|
switch (status) {
|
|
|
case PROCESSING_QUERY:
|
|
|
PgSQL_Result_to_PgSQL_wire(myconn, myconn->myds);
|
|
|
break;
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
RequestEnd(myds);
|
|
|
finishQuery(myds, myconn, prepared_stmt_with_no_params);
|
|
|
}
|
|
|
else {
|
|
|
if (rc == -1) {
|
|
|
// the query failed
|
|
|
const bool is_error_present = myconn->is_error_present(); // false means failure is due to server being in OFFLINE state
|
|
|
if (is_error_present == false) {
|
|
|
|
|
|
/*if (CurrentQuery.mysql_stmt) {
|
|
|
myerr = mysql_stmt_errno(CurrentQuery.mysql_stmt);
|
|
|
errmsg = strdup(mysql_stmt_error(CurrentQuery.mysql_stmt));
|
|
|
}*/
|
|
|
}
|
|
|
PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::pgsql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, 9999); // TOFIX
|
|
|
//CurrentQuery.mysql_stmt = NULL; // immediately reset mysql_stmt
|
|
|
int rc1 = handler_ProcessingQueryError_CheckBackendConnectionStatus(myds);
|
|
|
if (rc1 == -1) {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
else {
|
|
|
if (rc1 == 1)
|
|
|
NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
}
|
|
|
if (myconn->is_connection_in_reusable_state() == false) {
|
|
|
if (handler_minus1_ClientLibraryError(myds)) {
|
|
|
NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
} else {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
} else {
|
|
|
handler_minus1_LogErrorDuringQuery(myconn);
|
|
|
if (handler_minus1_HandleErrorCodes(myds, handler_ret)) {
|
|
|
if (handler_ret == 0)
|
|
|
NEXT_IMMEDIATE(CONNECTING_SERVER);
|
|
|
return handler_ret;
|
|
|
}
|
|
|
handler_minus1_GenerateErrorMessage(myds, wrong_pass);
|
|
|
RequestEnd(myds);
|
|
|
handler_minus1_HandleBackendConnection(myds);
|
|
|
}
|
|
|
} else {
|
|
|
switch (rc) {
|
|
|
// rc==1 , query is still running
|
|
|
// start sending to frontend if pgsql_thread___threshold_resultset_size is reached
|
|
|
case 1:
|
|
|
if (myconn->query_result && myconn->query_result->get_resultset_size() > (unsigned int)pgsql_thread___threshold_resultset_size) {
|
|
|
myconn->query_result->get_resultset(client_myds->PSarrayOUT);
|
|
|
}
|
|
|
break;
|
|
|
// rc==2 : a multi-resultset (or multi statement) was detected, and the current statement is completed
|
|
|
case 2:
|
|
|
PgSQL_Result_to_PgSQL_wire(myconn, myconn->myds);
|
|
|
if (myconn->query_result) { // we also need to clear query_result, so that the next statement will recreate it if needed
|
|
|
if (myconn->query_result_reuse) {
|
|
|
delete myconn->query_result_reuse;
|
|
|
}
|
|
|
myconn->query_result_reuse = myconn->query_result;
|
|
|
myconn->query_result = NULL;
|
|
|
}
|
|
|
NEXT_IMMEDIATE(PROCESSING_QUERY);
|
|
|
break;
|
|
|
// rc==3 , a multi statement query is still running
|
|
|
// start sending to frontend if pgsql_thread___threshold_resultset_size is reached
|
|
|
case 3:
|
|
|
if (myconn->query_result && myconn->query_result->get_resultset_size() > (unsigned int)pgsql_thread___threshold_resultset_size) {
|
|
|
myconn->query_result->get_resultset(client_myds->PSarrayOUT);
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
goto __exit_DSS__STATE_NOT_INITIALIZED;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case SETTING_ISOLATION_LEVEL:
|
|
|
case SETTING_TRANSACTION_READ:
|
|
|
//case SETTING_CHARSET:
|
|
|
case SETTING_VARIABLE:
|
|
|
case SETTING_NEXT_ISOLATION_LEVEL:
|
|
|
case SETTING_NEXT_TRANSACTION_READ:
|
|
|
{
|
|
|
int rc = 0;
|
|
|
if (pgsql_variables.update_variable(this, status, rc)) {
|
|
|
goto handler_again;
|
|
|
}
|
|
|
if (rc == -1) {
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
case CONNECTING_SERVER:
|
|
|
{
|
|
|
int rc = 0;
|
|
|
if (handler_again___status_CONNECTING_SERVER(&rc))
|
|
|
goto handler_again; // we changed status
|
|
|
if (rc == 1) //handler_again___status_CONNECTING_SERVER returns 1
|
|
|
goto __exit_DSS__STATE_NOT_INITIALIZED;
|
|
|
}
|
|
|
break;
|
|
|
case session_status___NONE:
|
|
|
fprintf(stderr, "NONE\n");
|
|
|
default:
|
|
|
{
|
|
|
int rc = 0;
|
|
|
if (handler_again___multiple_statuses(&rc)) // a sort of catch all
|
|
|
goto handler_again; // we changed status
|
|
|
if (rc == -1) { // we have an error we can't handle
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
|
|
|
__exit_DSS__STATE_NOT_INITIALIZED:
|
|
|
|
|
|
|
|
|
if (mybe && mybe->server_myds) {
|
|
|
if (mybe->server_myds->DSS > STATE_MARIADB_BEGIN && mybe->server_myds->DSS < STATE_MARIADB_END) {
|
|
|
#ifdef DEBUG
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = mybe->server_myds->myconn;
|
|
|
#endif /* DEBUG */
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess=%p, status=%d, server_myds->DSS==%d , revents==%d , async_state_machine=%d\n", this, status, mybe->server_myds->DSS, myds->revents, myconn->async_state_machine);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
writeout();
|
|
|
|
|
|
if (wrong_pass == true) {
|
|
|
client_myds->array2buffer_full();
|
|
|
client_myds->write_to_net();
|
|
|
handler_ret = -1;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
handler_ret = 0;
|
|
|
return handler_ret;
|
|
|
}
|
|
|
// end ::handler()
|
|
|
|
|
|
|
|
|
bool PgSQL_Session::handler_again___multiple_statuses(int* rc) {
|
|
|
bool ret = false;
|
|
|
switch (status) {
|
|
|
case RESETTING_CONNECTION_V2:
|
|
|
ret = handler_again___status_RESETTING_CONNECTION(rc);
|
|
|
break;
|
|
|
// TODO: fix this
|
|
|
//case SETTING_INIT_CONNECT:
|
|
|
// ret = handler_again___status_SETTING_INIT_CONNECT(rc);
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t* pkt, bool* wrong_pass) {
|
|
|
bool is_encrypted = client_myds->encrypted;
|
|
|
bool handshake_response_return = false;
|
|
|
bool ssl_request = false;
|
|
|
|
|
|
if (client_myds->auth_received_startup == false) {
|
|
|
if (client_myds->myprot.process_startup_packet((unsigned char*)pkt->ptr, pkt->size, ssl_request) == true ) {
|
|
|
if (ssl_request) {
|
|
|
if (is_encrypted == false && client_myds->encrypted == true) {
|
|
|
// switch to SSL...
|
|
|
} else {
|
|
|
// if sslmode is prefer, same connection will be used for plain text
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return;
|
|
|
}
|
|
|
} else if (client_myds->myprot.generate_pkt_initial_handshake(true, NULL, NULL, &thread_session_id, true) == true) {
|
|
|
client_myds->auth_received_startup = true;
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return;
|
|
|
} else {
|
|
|
assert(0); // this should never happen
|
|
|
}
|
|
|
} else {
|
|
|
*wrong_pass = true; //to forcefully close the connection. Is there a better way to do it?
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool handshake_err = true;
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p , handshake_response=%d , switching_auth_stage=%d , is_encrypted=%d , client_encrypted=%d\n", this, client_myds, handshake_response_return, client_myds->switching_auth_stage, is_encrypted, client_myds->encrypted);
|
|
|
|
|
|
if (client_myds->auth_received_startup) {
|
|
|
EXECUTION_STATE state = client_myds->myprot.process_handshake_response_packet((unsigned char*)pkt->ptr, pkt->size);
|
|
|
|
|
|
if (state == EXECUTION_STATE::PENDING) {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
handshake_response_return = (state == EXECUTION_STATE::SUCCESSFUL) ? true : false;
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
(handshake_response_return == false) && (client_myds->switching_auth_stage == 1)
|
|
|
) {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p . Returning\n", this, client_myds);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
(is_encrypted == false) && // the connection was encrypted
|
|
|
(handshake_response_return == false) && // the authentication didn't complete
|
|
|
(client_myds->encrypted == true) // client is asking for encryption
|
|
|
) {
|
|
|
// use SSL
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p . SSL_INIT\n", this, client_myds);
|
|
|
client_myds->DSS = STATE_SSL_INIT;
|
|
|
client_myds->rbio_ssl = BIO_new(BIO_s_mem());
|
|
|
client_myds->wbio_ssl = BIO_new(BIO_s_mem());
|
|
|
client_myds->ssl = GloVars.get_SSL_new();
|
|
|
SSL_set_fd(client_myds->ssl, client_myds->fd);
|
|
|
SSL_set_accept_state(client_myds->ssl);
|
|
|
SSL_set_bio(client_myds->ssl, client_myds->rbio_ssl, client_myds->wbio_ssl);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
proxysql_keylog_attach_callback(GloVars.get_SSL_ctx());
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
//(client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size)==true)
|
|
|
(handshake_response_return == true)
|
|
|
&&
|
|
|
(
|
|
|
#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP)
|
|
|
(default_hostgroup < 0 && (session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS || session_type == PROXYSQL_SESSION_SQLITE))
|
|
|
#else
|
|
|
(default_hostgroup < 0 && (session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS))
|
|
|
#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP
|
|
|
||
|
|
|
(default_hostgroup == 0 && session_type == PROXYSQL_SESSION_CLICKHOUSE)
|
|
|
||
|
|
|
//(default_hostgroup>=0 && session_type == PROXYSQL_SESSION_PGSQL)
|
|
|
(default_hostgroup >= 0 && (session_type == PROXYSQL_SESSION_PGSQL || session_type == PROXYSQL_SESSION_SQLITE))
|
|
|
||
|
|
|
(
|
|
|
client_myds->encrypted == false
|
|
|
&&
|
|
|
strncmp(client_myds->myconn->userinfo->username, pgsql_thread___monitor_username, strlen(pgsql_thread___monitor_username)) == 0
|
|
|
)
|
|
|
) // Do not delete this line. See bug #492
|
|
|
) {
|
|
|
if (session_type == PROXYSQL_SESSION_ADMIN) {
|
|
|
if ((default_hostgroup < 0) || (strncmp(client_myds->myconn->userinfo->username, pgsql_thread___monitor_username, strlen(pgsql_thread___monitor_username)) == 0)) {
|
|
|
if (default_hostgroup == STATS_HOSTGROUP) {
|
|
|
session_type = PROXYSQL_SESSION_STATS;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
//if (client_myds->encrypted==false) {
|
|
|
assert(client_myds->myconn->userinfo->dbname);
|
|
|
|
|
|
int free_users = 0;
|
|
|
int used_users = 0;
|
|
|
if (
|
|
|
(max_connections_reached == false)
|
|
|
&&
|
|
|
(session_type == PROXYSQL_SESSION_PGSQL || session_type == PROXYSQL_SESSION_CLICKHOUSE || session_type == PROXYSQL_SESSION_SQLITE)
|
|
|
) {
|
|
|
//if (session_type == PROXYSQL_SESSION_PGSQL || session_type == PROXYSQL_SESSION_CLICKHOUSE) {
|
|
|
client_authenticated = true;
|
|
|
switch (session_type) {
|
|
|
case PROXYSQL_SESSION_SQLITE:
|
|
|
//#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP)
|
|
|
free_users = 1;
|
|
|
break;
|
|
|
//#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP
|
|
|
case PROXYSQL_SESSION_PGSQL:
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p , session_type=PROXYSQL_SESSION_PGSQL\n", this, client_myds);
|
|
|
if (use_ldap_auth == false) {
|
|
|
free_users = GloPgAuth->increase_frontend_user_connections(client_myds->myconn->userinfo->username, &used_users);
|
|
|
}
|
|
|
else {
|
|
|
free_users = GloMyLdapAuth->increase_frontend_user_connections(client_myds->myconn->userinfo->fe_username, &used_users);
|
|
|
}
|
|
|
break;
|
|
|
#ifdef PROXYSQLCLICKHOUSE
|
|
|
case PROXYSQL_SESSION_CLICKHOUSE:
|
|
|
free_users = GloClickHouseAuth->increase_frontend_user_connections(client_myds->myconn->userinfo->username, &used_users);
|
|
|
break;
|
|
|
#endif /* PROXYSQLCLICKHOUSE */
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0);
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
free_users = 1;
|
|
|
}
|
|
|
if (max_connections_reached == true || free_users <= 0) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p , max_connections_reached=%d , free_users=%d\n", this, client_myds, max_connections_reached, free_users);
|
|
|
client_authenticated = false;
|
|
|
*wrong_pass = true;
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
uint8_t _pid = 2;
|
|
|
if (client_myds->switching_auth_stage) _pid += 2;
|
|
|
if (max_connections_reached == true) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p , Too many connections\n", this, client_myds);
|
|
|
client_myds->myprot.generate_error_packet(true, false, "Too many connections", PGSQL_ERROR_CODES::ERRCODE_TOO_MANY_CONNECTIONS,
|
|
|
true, true);
|
|
|
proxy_warning("pgsql-max_connections reached. Returning 'Too many connections'\n");
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, (char*)"pgsql-max_connections reached");
|
|
|
__sync_fetch_and_add(&PgHGM->status.access_denied_max_connections, 1);
|
|
|
}
|
|
|
else { // see issue #794
|
|
|
__sync_fetch_and_add(&PgHGM->status.access_denied_max_user_connections, 1);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . User '%s' has exceeded the 'max_user_connections' resource (current value: %d)\n", this, client_myds, client_myds->myconn->userinfo->username, used_users);
|
|
|
char* a = (char*)"User '%s' has exceeded the 'max_user_connections' resource (current value: %d)";
|
|
|
char* b = (char*)malloc(strlen(a) + strlen(client_myds->myconn->userinfo->username) + 16);
|
|
|
sprintf(b, a, client_myds->myconn->userinfo->username, used_users);
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, b);
|
|
|
client_myds->myprot.generate_error_packet(true, false, b, PGSQL_ERROR_CODES::ERRCODE_TOO_MANY_CONNECTIONS,
|
|
|
true, true);
|
|
|
proxy_warning("User '%s' has exceeded the 'max_user_connections' resource (current value: %d)\n", client_myds->myconn->userinfo->username, used_users);
|
|
|
free(b);
|
|
|
}
|
|
|
__sync_add_and_fetch(&PgHGM->status.client_connections_aborted, 1);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
else {
|
|
|
if (
|
|
|
(default_hostgroup == ADMIN_HOSTGROUP && strcmp(client_myds->myconn->userinfo->username, (char*)"admin") == 0)
|
|
|
||
|
|
|
(default_hostgroup == STATS_HOSTGROUP && strcmp(client_myds->myconn->userinfo->username, (char*)"stats") == 0)
|
|
|
||
|
|
|
(default_hostgroup < 0 && strcmp(client_myds->myconn->userinfo->username, (char*)"monitor") == 0)
|
|
|
) {
|
|
|
char* client_addr = NULL;
|
|
|
union {
|
|
|
struct sockaddr_in in;
|
|
|
struct sockaddr_in6 in6;
|
|
|
} custom_sockaddr;
|
|
|
struct sockaddr* addr = (struct sockaddr*)malloc(sizeof(custom_sockaddr));
|
|
|
socklen_t addrlen = sizeof(custom_sockaddr);
|
|
|
memset(addr, 0, sizeof(custom_sockaddr));
|
|
|
int rc = 0;
|
|
|
rc = getpeername(client_myds->fd, addr, &addrlen);
|
|
|
if (rc == 0) {
|
|
|
char buf[512];
|
|
|
switch (addr->sa_family) {
|
|
|
case AF_INET: {
|
|
|
struct sockaddr_in* ipv4 = (struct sockaddr_in*)addr;
|
|
|
inet_ntop(addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
break;
|
|
|
}
|
|
|
case AF_INET6: {
|
|
|
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)addr;
|
|
|
inet_ntop(addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
client_addr = strdup((char*)"localhost");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
client_addr = strdup((char*)"");
|
|
|
}
|
|
|
uint8_t _pid = 2;
|
|
|
if (client_myds->switching_auth_stage) _pid += 2;
|
|
|
if (is_encrypted) _pid++;
|
|
|
if (
|
|
|
(strcmp(client_addr, (char*)"127.0.0.1") == 0)
|
|
|
||
|
|
|
(strcmp(client_addr, (char*)"localhost") == 0)
|
|
|
||
|
|
|
(strcmp(client_addr, (char*)"::1") == 0)
|
|
|
) {
|
|
|
// we are good!
|
|
|
client_myds->myprot.welcome_client();
|
|
|
handshake_err = false;
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_OK, this, NULL);
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
client_myds->DSS = STATE_CLIENT_AUTH_OK;
|
|
|
}
|
|
|
else {
|
|
|
char* a = (char*)"User '%s' can only connect locally";
|
|
|
char* b = (char*)malloc(strlen(a) + strlen(client_myds->myconn->userinfo->username));
|
|
|
sprintf(b, a, client_myds->myconn->userinfo->username);
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, b);
|
|
|
client_myds->myprot.generate_error_packet(true, false, b, PGSQL_ERROR_CODES::ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
|
|
|
true, true);
|
|
|
free(b);
|
|
|
}
|
|
|
free(addr);
|
|
|
free(client_addr);
|
|
|
}
|
|
|
else {
|
|
|
uint8_t _pid = 2;
|
|
|
if (client_myds->switching_auth_stage) _pid += 2;
|
|
|
if (is_encrypted) _pid++;
|
|
|
// If this condition is met, it means that the
|
|
|
// 'STATE_SERVER_HANDSHAKE' being performed isn't from the start of a
|
|
|
// connection, but as a consequence of a 'COM_USER_CHANGE' which
|
|
|
// requires an 'Auth Switch'. Thus, we impose a 'pid' of '3' for the
|
|
|
// response 'OK' packet. See #3504 for more context.
|
|
|
if (change_user_auth_switch) {
|
|
|
_pid = 3;
|
|
|
change_user_auth_switch = 0;
|
|
|
}
|
|
|
if (use_ssl == true && is_encrypted == false) {
|
|
|
*wrong_pass = true;
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL);
|
|
|
|
|
|
char* _a = (char*)"ProxySQL Error: Access denied for user '%s' (using password: %s). SSL is required";
|
|
|
char* _s = (char*)malloc(strlen(_a) + strlen(client_myds->myconn->userinfo->username) + 32);
|
|
|
sprintf(_s, _a, client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
client_myds->myprot.generate_error_packet(true, false, _s, PGSQL_ERROR_CODES::ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
|
|
|
true, true);
|
|
|
proxy_error("ProxySQL Error: Access denied for user '%s' (using password: %s). SSL is required\n", client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p . Access denied for user '%s' (using password: %s). SSL is required\n", this, client_myds, client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
__sync_add_and_fetch(&PgHGM->status.client_connections_aborted, 1);
|
|
|
free(_s);
|
|
|
__sync_fetch_and_add(&PgHGM->status.access_denied_wrong_password, 1);
|
|
|
}
|
|
|
else {
|
|
|
// we are good!
|
|
|
//client_myds->myprot.generate_pkt_OK(true,NULL,NULL, (is_encrypted ? 3 : 2), 0,0,0,0,NULL,false);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p . STATE_CLIENT_AUTH_OK\n", this, client_myds);
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_OK, this, NULL);
|
|
|
client_myds->myprot.welcome_client();
|
|
|
handshake_err = false;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
client_myds->DSS = STATE_CLIENT_AUTH_OK;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Wrong credentials for frontend: disconnecting\n", this, client_myds);
|
|
|
*wrong_pass = true;
|
|
|
// FIXME: this should become close connection
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
char* client_addr = NULL;
|
|
|
if (client_myds->client_addr && client_myds->myconn->userinfo->username) {
|
|
|
char buf[512];
|
|
|
switch (client_myds->client_addr->sa_family) {
|
|
|
case AF_INET: {
|
|
|
struct sockaddr_in* ipv4 = (struct sockaddr_in*)client_myds->client_addr;
|
|
|
if (ipv4->sin_port) {
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
}
|
|
|
else {
|
|
|
client_addr = strdup((char*)"localhost");
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case AF_INET6: {
|
|
|
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
client_addr = strdup((char*)"localhost");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
client_addr = strdup((char*)"");
|
|
|
}
|
|
|
if (client_myds->myconn->userinfo->username && client_myds->myconn->userinfo->username[0] != '\0') {
|
|
|
char* _s = (char*)malloc(strlen(client_myds->myconn->userinfo->username) + 100 + strlen(client_addr));
|
|
|
uint8_t _pid = 2;
|
|
|
if (client_myds->switching_auth_stage) _pid += 2;
|
|
|
if (is_encrypted) _pid++;
|
|
|
#ifdef DEBUG
|
|
|
if (client_myds->myconn->userinfo->password) {
|
|
|
char* tmp_pass = strdup(client_myds->myconn->userinfo->password);
|
|
|
int lpass = strlen(tmp_pass);
|
|
|
for (int i = 2; i < lpass - 1; i++) {
|
|
|
tmp_pass[i] = '*';
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Error: Access denied for user '%s'@'%s' , Password='%s'. Disconnecting\n", this, client_myds, client_myds->myconn->userinfo->username, client_addr, tmp_pass);
|
|
|
free(tmp_pass);
|
|
|
}
|
|
|
else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Error: Access denied for user '%s'@'%s' . No password. Disconnecting\n", this, client_myds, client_myds->myconn->userinfo->username, client_addr);
|
|
|
}
|
|
|
#endif // DEBUG
|
|
|
sprintf(_s, "ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
client_myds->myprot.generate_error_packet(true, false, _s, PGSQL_ERROR_CODES::ERRCODE_INVALID_PASSWORD, true, true);
|
|
|
proxy_error("%s\n", _s);
|
|
|
free(_s);
|
|
|
__sync_fetch_and_add(&PgHGM->status.access_denied_wrong_password, 1);
|
|
|
}
|
|
|
if (client_addr) {
|
|
|
free(client_addr);
|
|
|
}
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL);
|
|
|
__sync_add_and_fetch(&PgHGM->status.client_connections_aborted, 1);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
|
|
|
if (pgsql_thread___client_host_cache_size) {
|
|
|
GloPTH->update_client_host_cache(client_myds->client_addr, handshake_err);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
// Note: as commented in issue #546 and #547 , some clients ignore the status of CLIENT_MULTI_STATEMENTS
|
|
|
// therefore tracking it is not needed, unless in future this should become a security enhancement,
|
|
|
// returning errors to all clients trying to send multi-statements .
|
|
|
// see also #1140
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_SET_OPTION(PtrSize_t* pkt) {
|
|
|
|
|
|
char v;
|
|
|
v = *((char*)pkt->ptr + 3);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_SET_OPTION packet , value %d\n", v);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
|
|
|
bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF;
|
|
|
if (deprecate_eof_active)
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL, true);
|
|
|
else
|
|
|
client_myds->myprot.generate_pkt_EOF(true, NULL, NULL, 1, 0, setStatus);
|
|
|
|
|
|
if (v == 1) { // disabled. MYSQL_OPTION_MULTI_STATEMENTS_OFF == 1
|
|
|
client_myds->myconn->options.client_flag &= ~CLIENT_MULTI_STATEMENTS;
|
|
|
}
|
|
|
else { // enabled, MYSQL_OPTION_MULTI_STATEMENTS_ON == 0
|
|
|
client_myds->myconn->options.client_flag |= CLIENT_MULTI_STATEMENTS;
|
|
|
}
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PING(PtrSize_t* pkt) {
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_PING packet\n");
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_FIELD_LIST(PtrSize_t* pkt) {
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
/* FIXME: temporary */
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
|
false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
|
false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PROCESS_KILL(PtrSize_t* pkt) {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, false);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB(PtrSize_t* pkt) {
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_INIT_DB packet\n");
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
//__sync_fetch_and_add(&PgHGM->status.frontend_init_db, 1);
|
|
|
//client_myds->myconn->userinfo->set_dbname((char*)pkt->ptr + sizeof(mysql_hdr) + 1, pkt->size - sizeof(mysql_hdr) - 1);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_INITDB, this, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// this function was introduced due to isseu #718
|
|
|
// some application (like the one written in Perl) do not use COM_INIT_DB , but COM_QUERY with USE dbname
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(PtrSize_t* pkt) {
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUERY with USE dbname\n");
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
//__sync_fetch_and_add(&PgHGM->status.frontend_use_db, 1);
|
|
|
string nq = string((char*)pkt->ptr + sizeof(mysql_hdr) + 1, pkt->size - sizeof(mysql_hdr) - 1);
|
|
|
RE2::GlobalReplace(&nq, (char*)"(?U)/\\*.*\\*/", (char*)" ");
|
|
|
char* sn_tmp = (char*)nq.c_str();
|
|
|
while (sn_tmp < (nq.c_str() + nq.length() - 4) && *sn_tmp == ' ')
|
|
|
sn_tmp++;
|
|
|
//char *schemaname=strdup(nq.c_str()+4);
|
|
|
char* schemaname = strdup(sn_tmp + 3);
|
|
|
char* schemanameptr = trim_spaces_and_quotes_in_place(schemaname);
|
|
|
// handle cases like "USE `schemaname`
|
|
|
if (schemanameptr[0] == '`' && schemanameptr[strlen(schemanameptr) - 1] == '`') {
|
|
|
schemanameptr[strlen(schemanameptr) - 1] = '\0';
|
|
|
schemanameptr++;
|
|
|
}
|
|
|
//client_myds->myconn->userinfo->set_dbname(schemanameptr);
|
|
|
free(schemaname);
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_INITDB, this, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo
|
|
|
void PgSQL_Session::handler_WCD_SS_MCQ_qpo_QueryRewrite(PtrSize_t* pkt) {
|
|
|
// the query was rewritten
|
|
|
l_free(pkt->size, pkt->ptr); // free old pkt
|
|
|
// allocate new pkt
|
|
|
timespec begint;
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begint);
|
|
|
}
|
|
|
|
|
|
PG_pkt pgpkt(1 + 4 + qpo->new_query->length() + 1);
|
|
|
pgpkt.put_char('Q');
|
|
|
pgpkt.put_uint32(4 + qpo->new_query->length() + 1);
|
|
|
pgpkt.put_bytes(qpo->new_query->data(), qpo->new_query->length());
|
|
|
pgpkt.put_char('\0');
|
|
|
auto buff = pgpkt.detach();
|
|
|
pkt->ptr = buff.first;
|
|
|
pkt->size = buff.second;
|
|
|
CurrentQuery.query_parser_free();
|
|
|
CurrentQuery.begin((unsigned char*)pkt->ptr, pkt->size, true);
|
|
|
delete qpo->new_query;
|
|
|
timespec endt;
|
|
|
if (thread->variables.stats_time_query_processor) {
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &endt);
|
|
|
thread->status_variables.stvar[st_var_query_processor_time] = thread->status_variables.stvar[st_var_query_processor_time] +
|
|
|
(endt.tv_sec * 1000000000 + endt.tv_nsec) -
|
|
|
(begint.tv_sec * 1000000000 + begint.tv_nsec);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo
|
|
|
void PgSQL_Session::handler_WCD_SS_MCQ_qpo_OK_msg(PtrSize_t* pkt) {
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, qpo->OK_msg, 0, (const char*)pkt->ptr + 5, trx_state);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
}
|
|
|
|
|
|
// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo
|
|
|
void PgSQL_Session::handler_WCD_SS_MCQ_qpo_error_msg(PtrSize_t* pkt) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, qpo->error_msg,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_INSUFFICIENT_PRIVILEGE, false);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
}
|
|
|
|
|
|
// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo
|
|
|
void PgSQL_Session::handler_WCD_SS_MCQ_qpo_LargePacket(PtrSize_t* pkt) {
|
|
|
// ER_NET_PACKET_TOO_LARGE
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Got a packet bigger than 'max_allowed_packet' bytes",
|
|
|
PGSQL_ERROR_CODES::ERRCODE_PROGRAM_LIMIT_EXCEEDED, false);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t* pkt, bool* lock_hostgroup, PgSQL_ps_type prepare_stmt_type) {
|
|
|
/*
|
|
|
lock_hostgroup:
|
|
|
If this variable is set to true, this session will get lock to a
|
|
|
specific hostgroup, and also have multiplexing disabled.
|
|
|
It means that parsing the query wasn't completely possible (mostly
|
|
|
a SET statement) and proxysql won't be able to set the same variable
|
|
|
in another connection.
|
|
|
This algorithm will be become obsolete once we implement session
|
|
|
tracking for MySQL 5.7+
|
|
|
*/
|
|
|
//bool exit_after_SetParse = true;
|
|
|
|
|
|
if (qpo->new_query) {
|
|
|
handler_WCD_SS_MCQ_qpo_QueryRewrite(pkt);
|
|
|
}
|
|
|
|
|
|
if (pkt->size > (unsigned int)pgsql_thread___max_allowed_packet) {
|
|
|
handler_WCD_SS_MCQ_qpo_LargePacket(pkt);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (qpo->OK_msg) {
|
|
|
handler_WCD_SS_MCQ_qpo_OK_msg(pkt);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (qpo->error_msg) {
|
|
|
handler_WCD_SS_MCQ_qpo_error_msg(pkt);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (prepare_stmt_type & PgSQL_ps_type_execute_stmt) { // for prepared statement execute we exit here
|
|
|
goto __exit_set_destination_hostgroup;
|
|
|
}
|
|
|
|
|
|
// Check if the session is not locked on a hostgroup and there are untracked option parameters
|
|
|
if (locked_on_hostgroup < 0 && untracked_option_parameters.empty() == false) {
|
|
|
if (client_myds && client_myds->addr.addr) {
|
|
|
proxy_warning("Unknown connection options from client %s:%d. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", client_myds->addr.addr, client_myds->addr.port, untracked_option_parameters.c_str());
|
|
|
} else {
|
|
|
// Log a warning message without client address and port
|
|
|
proxy_warning("Unknown connection options. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", untracked_option_parameters.c_str());
|
|
|
}
|
|
|
|
|
|
// If there are untracked option parameters, lock the hostgroup
|
|
|
*lock_hostgroup = true;
|
|
|
|
|
|
// Always create a new connection to pass untracked options to the server
|
|
|
qpo->create_new_conn = true;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// handle here #509, #815 and #816
|
|
|
if (CurrentQuery.QueryParserArgs.digest_text) {
|
|
|
char* dig = CurrentQuery.QueryParserArgs.digest_text;
|
|
|
//unsigned int nTrx = NumActiveTransactions();
|
|
|
if ((locked_on_hostgroup == -1) && (strncasecmp(dig, (char*)"SET ", 4) == 0)) {
|
|
|
// this code is executed only if locked_on_hostgroup is not set yet
|
|
|
// if locked_on_hostgroup is set, we do not try to parse the SET statement
|
|
|
#ifdef DEBUG
|
|
|
{
|
|
|
string nqn = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Parsing SET command = %s\n", nqn.c_str());
|
|
|
}
|
|
|
#endif
|
|
|
if (index(dig, ';') && (index(dig, ';') != dig + strlen(dig) - 1)) {
|
|
|
string nqn;
|
|
|
if (pgsql_thread___parse_failure_logs_digest)
|
|
|
nqn = string(CurrentQuery.get_digest_text());
|
|
|
else
|
|
|
nqn = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
proxy_warning(
|
|
|
"Unable to parse multi-statements command with SET statement from client"
|
|
|
" %s:%d: setting lock hostgroup. Command: %s\n", client_myds->addr.addr,
|
|
|
client_myds->addr.port, nqn.c_str()
|
|
|
);
|
|
|
*lock_hostgroup = true;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
string nq = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
RE2::GlobalReplace(&nq, (char*)"^/\\*!\\d\\d\\d\\d\\d SET(.*)\\*/", (char*)"SET\\1");
|
|
|
RE2::GlobalReplace(&nq, (char*)"(?U)/\\*.*\\*/", (char*)"");
|
|
|
// remove trailing space and semicolon if present. See issue#4380
|
|
|
nq.erase(nq.find_last_not_of(" ;") + 1);
|
|
|
if (
|
|
|
(match_regexes && match_regexes[1]->match(dig))
|
|
|
)
|
|
|
{
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Parsing SET command %s\n", nq.c_str());
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Parsing SET command = %s\n", nq.c_str());
|
|
|
PgSQL_Set_Stmt_Parser parser(nq);
|
|
|
std::map<std::string, std::vector<std::string>> set = {};
|
|
|
std::vector<std::pair<std::string, std::string>> param_status = {};
|
|
|
bool send_param_status = false;
|
|
|
|
|
|
thread->thr_SetParser->set_query(nq); // replace the query
|
|
|
set = thread->thr_SetParser->parse1v2(); // use algorithm v2
|
|
|
|
|
|
// Flag to be set if any variable within the 'SET' statement fails to be tracked,
|
|
|
// due to being unknown or because it's an user defined variable.
|
|
|
bool failed_to_parse_var = set.empty();
|
|
|
for (auto it = std::begin(set); it != std::end(set); ++it) {
|
|
|
std::string var = it->first;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET variable %s\n", var.c_str());
|
|
|
if (it->second.size() < 1 || it->second.size() > 2) {
|
|
|
// error not enough arguments
|
|
|
string query_str = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
string digest_str = string(CurrentQuery.get_digest_text());
|
|
|
string nqn;
|
|
|
if (pgsql_thread___parse_failure_logs_digest)
|
|
|
nqn = digest_str;
|
|
|
else
|
|
|
nqn = query_str;
|
|
|
// PMC-10002: A query has failed to be parsed. This can be due a incorrect query or
|
|
|
// due to ProxySQL not being able to properly parse it. In case the query is correct a
|
|
|
// bug report should be filed including the offending query.
|
|
|
proxy_error2(10002, "Unable to parse query. If correct, report it as a bug: %s\n", nqn.c_str());
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Locking hostgroup for query %s\n",
|
|
|
query_str.c_str());
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
auto values = std::begin(it->second);
|
|
|
if (std::find(pgsql_critical_variables.begin(), pgsql_critical_variables.end(), var) != pgsql_critical_variables.end() ||
|
|
|
pgsql_other_variables.find(var) != pgsql_other_variables.end()) {
|
|
|
std::string value1 = *values;
|
|
|
|
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
|
for (int i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
if (variable_name_exists(pgsql_tracked_variables[i], var.c_str()) == true) {
|
|
|
idx = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (idx != PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
|
|
|
if ((value1.size() == sizeof("DEFAULT") - 1) && strncasecmp(value1.c_str(), (char*)"DEFAULT",sizeof("DEFAULT")-1) == 0) {
|
|
|
value1 = get_default_session_variable((enum pgsql_variable_name)idx);
|
|
|
}
|
|
|
|
|
|
char* transformed_value = nullptr;
|
|
|
if (pgsql_tracked_variables[idx].validator && pgsql_tracked_variables[idx].validator->validate &&
|
|
|
(
|
|
|
*pgsql_tracked_variables[idx].validator->validate)(
|
|
|
value1.c_str(), &pgsql_tracked_variables[idx].validator->params, this, &transformed_value) == false
|
|
|
) {
|
|
|
char* m = NULL;
|
|
|
char* errmsg = NULL;
|
|
|
proxy_error("invalid value for parameter \"%s\": \"%s\"\n", pgsql_tracked_variables[idx].set_variable_name, value1.c_str());
|
|
|
m = (char*)"invalid value for parameter \"%s\": \"%s\"";
|
|
|
errmsg = (char*)malloc(value1.length() + strlen(pgsql_tracked_variables[idx].set_variable_name) + strlen(m));
|
|
|
sprintf(errmsg, m, pgsql_tracked_variables[idx].set_variable_name, value1.c_str());
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, errmsg,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_INVALID_PARAMETER_VALUE, false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
free(errmsg);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (transformed_value) {
|
|
|
value1 = transformed_value;
|
|
|
free(transformed_value);
|
|
|
}
|
|
|
|
|
|
if (idx == PGSQL_DATESTYLE) {
|
|
|
if (value1.empty()) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, dig, trx_state, NULL, param_status);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection %s to %s\n", var.c_str(), value1.c_str());
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, idx, value1.c_str(), true)) {
|
|
|
return false;
|
|
|
}
|
|
|
if (idx == PGSQL_DATESTYLE) {
|
|
|
// always set current_datestyle
|
|
|
current_datestyle = PgSQL_DateStyle_Util::parse_datestyle(value1);
|
|
|
// No need to set send_param_status to true, as the original DateStyle value may have been modified.
|
|
|
// When send_param_status is true, it always sends the original value provided by the user in the SET statement.
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx])) {
|
|
|
param_status.push_back(std::make_pair(var, value1));
|
|
|
}
|
|
|
} else {
|
|
|
send_param_status = IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} /*else if (var == "time_zone") {
|
|
|
std::string value1 = *values;
|
|
|
std::size_t found_at = value1.find("@");
|
|
|
if (found_at != std::string::npos) {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET Time Zone value %s\n", value1.c_str());
|
|
|
{
|
|
|
// reformat +1:23 to +01:23
|
|
|
if (value1.length() == 5) {
|
|
|
if (value1[0] == '+' || value1[0] == '-') {
|
|
|
if (value1[2] == ':') {
|
|
|
std::string s = std::string(value1, 0, 1);
|
|
|
s += "0";
|
|
|
s += std::string(value1, 1, 4);
|
|
|
value1 = s;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
uint32_t time_zone_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, SQL_TIME_ZONE) != time_zone_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, SQL_TIME_ZONE, value1.c_str()))
|
|
|
return false;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection Time zone to %s\n", value1.c_str());
|
|
|
}
|
|
|
} else if ((var == "client_encoding") || (var == "names")) {
|
|
|
std::string value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 7, "Processing SET %s value %s\n", var.c_str(), value1.c_str());
|
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
|
for (int i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
if (variable_name_exists(pgsql_tracked_variables[i], var.c_str()) == true) {
|
|
|
idx = pgsql_tracked_variables[i].idx;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (idx == PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
proxy_error("Variable %s not found in pgsql_tracked_variables[]\n", var.c_str());
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
uint32_t var_value_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, idx) != var_value_int) {
|
|
|
|
|
|
int charset_encoding = PgSQL_Connection::char_to_encoding(value1.c_str());
|
|
|
|
|
|
if (charset_encoding == -1) {
|
|
|
char* m = NULL;
|
|
|
char* errmsg = NULL;
|
|
|
proxy_error("Cannot find charset [%s]\n", value1.c_str());
|
|
|
m = (char*)"Unknown character set: '%s'";
|
|
|
errmsg = (char*)malloc(value1.length() + strlen(m));
|
|
|
sprintf(errmsg, m, value1.c_str());
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, errmsg,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
free(errmsg);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing connection %s to %s\n", var.c_str(), value1.c_str());
|
|
|
|
|
|
if (!pgsql_variables.client_set_value(this, idx, value1.c_str()))
|
|
|
return false;
|
|
|
|
|
|
//client_myds->myconn->set_charset(value1.c_str());
|
|
|
}
|
|
|
}
|
|
|
else if (var == "names") {
|
|
|
std::string value1 = *values++;
|
|
|
std::size_t found_at = value1.find("@");
|
|
|
if (found_at != std::string::npos) {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET NAMES %s\n", value1.c_str());
|
|
|
const MARIADB_CHARSET_INFO* c;
|
|
|
std::string value2;
|
|
|
if (values != std::end(it->second)) {
|
|
|
value2 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET NAMES With COLLATE %s\n", value2.c_str());
|
|
|
c = proxysql_find_charset_collate_names(value1.c_str(), value2.c_str());
|
|
|
}
|
|
|
else {
|
|
|
c = proxysql_find_charset_name(value1.c_str());
|
|
|
}
|
|
|
if (!c) {
|
|
|
char* m = NULL;
|
|
|
char* errmsg = NULL;
|
|
|
if (value2.length()) {
|
|
|
m = (char*)"Unknown character set '%s' or collation '%s'";
|
|
|
errmsg = (char*)malloc(value1.length() + value2.length() + strlen(m));
|
|
|
sprintf(errmsg, m, value1.c_str(), value2.c_str());
|
|
|
}
|
|
|
else {
|
|
|
m = (char*)"Unknown character set: '%s'";
|
|
|
errmsg = (char*)malloc(value1.length() + strlen(m));
|
|
|
sprintf(errmsg, m, value1.c_str());
|
|
|
}
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, errmsg,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
free(errmsg);
|
|
|
return true;
|
|
|
}
|
|
|
else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection charset to %d\n", c->nr);
|
|
|
//-- client_myds->myconn->set_charset(c->nr, NAMES);
|
|
|
}
|
|
|
}
|
|
|
else if (var == "tx_isolation") {
|
|
|
std::string value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET tx_isolation value %s\n", value1.c_str());
|
|
|
auto pos = value1.find('-');
|
|
|
if (pos != std::string::npos)
|
|
|
value1[pos] = ' ';
|
|
|
uint32_t isolation_level_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, SQL_ISOLATION_LEVEL) != isolation_level_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, SQL_ISOLATION_LEVEL, value1.c_str()))
|
|
|
return false;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection TX ISOLATION to %s\n", value1.c_str());
|
|
|
}
|
|
|
}
|
|
|
else if (var == "tx_read_only") {
|
|
|
std::string value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET tx_read_only value %s\n", value1.c_str());
|
|
|
|
|
|
if (
|
|
|
(value1 == "0") ||
|
|
|
(strcasecmp(value1.c_str(), "false") == 0) ||
|
|
|
(strcasecmp(value1.c_str(), "off") == 0)
|
|
|
) {
|
|
|
value1 = "WRITE";
|
|
|
}
|
|
|
else if (
|
|
|
(value1 == "1") ||
|
|
|
(strcasecmp(value1.c_str(), "true") == 0) ||
|
|
|
(strcasecmp(value1.c_str(), "on") == 0)
|
|
|
) {
|
|
|
value1 = "ONLY";
|
|
|
}
|
|
|
else {
|
|
|
//proxy_warning("Unknown tx_read_only value \"%s\"\n", value1.c_str());
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
uint32_t read_only_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, SQL_TRANSACTION_READ) != read_only_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, SQL_TRANSACTION_READ, value1.c_str()))
|
|
|
return false;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection TX ACCESS MODE to READ %s\n", value1.c_str());
|
|
|
}
|
|
|
}*/
|
|
|
else if (std::find(pgsql_variables.ignore_vars.begin(), pgsql_variables.ignore_vars.end(), var) != pgsql_variables.ignore_vars.end()) {
|
|
|
// this is a variable we parse but ignore
|
|
|
// see MySQL_Variables::MySQL_Variables() for a list of ignored variables
|
|
|
#ifdef DEBUG
|
|
|
std::string value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET %s value %s\n", var.c_str(), value1.c_str());
|
|
|
#endif // DEBUG
|
|
|
}
|
|
|
else {
|
|
|
// At this point the variable is unknown to us, or it's a user variable
|
|
|
// prefixed by '@', in both cases, we should fail to parse. We don't
|
|
|
// fail inmediately so we can anyway keep track of the other variables
|
|
|
// supplied within the 'SET' statement being parsed.
|
|
|
failed_to_parse_var = true;
|
|
|
}
|
|
|
|
|
|
if (send_param_status)
|
|
|
param_status.push_back(std::make_pair(var, *values));
|
|
|
}
|
|
|
|
|
|
if (failed_to_parse_var) {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
/*
|
|
|
if (exit_after_SetParse) {
|
|
|
goto __exit_set_destination_hostgroup;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, dig, trx_state, NULL, param_status);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
/* TODO
|
|
|
else if (match_regexes && match_regexes[2]->match(dig)) {
|
|
|
PgSQL_Set_Stmt_Parser parser(nq);
|
|
|
std::map<std::string, std::vector<std::string>> set = parser.parse2();
|
|
|
|
|
|
for (auto it = std::begin(set); it != std::end(set); ++it) {
|
|
|
|
|
|
const std::vector<std::string>& val = split_string(it->first, ':');
|
|
|
|
|
|
if (val.size() == 2) {
|
|
|
|
|
|
const auto values = std::begin(it->second);
|
|
|
const std::string& var = val[1];
|
|
|
|
|
|
enum mysql_variable_name isolation_level_val;
|
|
|
enum mysql_variable_name transaction_read_val;
|
|
|
|
|
|
if (val[0] == "session") {
|
|
|
isolation_level_val = SQL_ISOLATION_LEVEL;
|
|
|
transaction_read_val = SQL_TRANSACTION_READ;
|
|
|
}
|
|
|
else {
|
|
|
isolation_level_val = SQL_NEXT_ISOLATION_LEVEL;
|
|
|
transaction_read_val = SQL_NEXT_TRANSACTION_READ;
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET variable %s\n", var.c_str());
|
|
|
if (var == "isolation level") {
|
|
|
const std::string& value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET %s TRANSACTION ISOLATION LEVEL value %s\n", val[0].c_str(), value1.c_str());
|
|
|
const uint32_t isolation_level_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, isolation_level_val) != isolation_level_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, isolation_level_val, value1.c_str()))
|
|
|
return false;
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection TRANSACTION ISOLATION LEVEL to %s\n", value1.c_str());
|
|
|
}
|
|
|
}
|
|
|
else if (var == "read") {
|
|
|
const std::string& value1 = *values;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET %s TRANSACTION READ value %s\n", val[0].c_str(), value1.c_str());
|
|
|
const uint32_t transaction_read_int = SpookyHash::Hash32(value1.c_str(), value1.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, transaction_read_val) != transaction_read_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, transaction_read_val, value1.c_str()))
|
|
|
return false;
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection TRANSACTION READ to %s\n", value1.c_str());
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, dig, trx_state);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
|
|
|
} else if (match_regexes && match_regexes[3]->match(dig)) {
|
|
|
std::vector<std::pair<std::string, std::string>> param_status;
|
|
|
PgSQL_Set_Stmt_Parser parser(nq);
|
|
|
std::string charset = parser.parse_character_set();
|
|
|
int charset_encoding = -1;
|
|
|
if (!charset.empty()) {
|
|
|
|
|
|
if ((charset.size() == sizeof("DEFAULT") - 1) && strncasecmp(charset.c_str(), (char*)"DEFAULT", sizeof("DEFAULT") - 1) == 0) {
|
|
|
charset = get_default_session_variable(PGSQL_CLIENT_ENCODING);
|
|
|
assert(charset.empty() == false);
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET CLIENT_ENCODING %s\n", charset.c_str());
|
|
|
charset_encoding = PgSQL_Connection::char_to_encoding(charset.c_str());
|
|
|
} else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
if (charset_encoding == -1) {
|
|
|
char* m = NULL;
|
|
|
char* errmsg = NULL;
|
|
|
proxy_error("invalid value for parameter \"Client_Encoding\": \"%s\"\n", charset.c_str());
|
|
|
m = (char*)"invalid value for parameter \"Client_Encoding\": \"%s\"";
|
|
|
errmsg = (char*)malloc(charset.length() + strlen(m));
|
|
|
sprintf(errmsg, m, charset.c_str());
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
client_myds->myprot.generate_error_packet(true, true, errmsg,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_INVALID_PARAMETER_VALUE, false, true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
free(errmsg);
|
|
|
return true;
|
|
|
} else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection charset to %s\n", charset.c_str());
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(charset.c_str(), charset.length(), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, pgsql_tracked_variables[PGSQL_CLIENT_ENCODING].idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, pgsql_tracked_variables[PGSQL_CLIENT_ENCODING].idx, charset.c_str())) {
|
|
|
return false;
|
|
|
}
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[PGSQL_CLIENT_ENCODING]))
|
|
|
param_status.push_back(std::make_pair(pgsql_tracked_variables[PGSQL_CLIENT_ENCODING].set_variable_name, charset));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, dig, trx_state, NULL, param_status);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}*/ else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
} else if ((locked_on_hostgroup == -1) && (strncasecmp(dig, (char*)"RESET ", 6) == 0)) {
|
|
|
#ifdef DEBUG
|
|
|
{
|
|
|
std::string nqn = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Parsing RESET command = %s\n", nqn.c_str());
|
|
|
}
|
|
|
#endif
|
|
|
if (index(dig, ';') && (index(dig, ';') != dig + strlen(dig) - 1)) {
|
|
|
string nqn;
|
|
|
if (pgsql_thread___parse_failure_logs_digest)
|
|
|
nqn = string(CurrentQuery.get_digest_text());
|
|
|
else
|
|
|
nqn = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
proxy_warning(
|
|
|
"Unable to parse multi-statements command with RESET statement from client"
|
|
|
" %s:%d: setting lock hostgroup. Command: %s\n", client_myds->addr.addr,
|
|
|
client_myds->addr.port, nqn.c_str()
|
|
|
);
|
|
|
*lock_hostgroup = true;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
string nq = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
|
|
|
RE2::GlobalReplace(&nq, (char*)"(?U)/\\*.*\\*/", (char*)"");
|
|
|
RE2::GlobalReplace(&nq, (char*)"(?i)RESET", (char*)"");
|
|
|
RE2::GlobalReplace(&nq, (char*)"[^\\w]*", (char*)"");
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Parsing RESET command %s\n", nq.c_str());
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Parsing RESET command = %s\n", nq.c_str());
|
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> param_status = {};
|
|
|
|
|
|
if (strncasecmp(nq.c_str(), "ALL", 3) == 0) {
|
|
|
|
|
|
for (int idx = 0; idx < PGSQL_NAME_LAST_LOW_WM; idx++) {
|
|
|
|
|
|
const char* name = pgsql_tracked_variables[idx].set_variable_name;
|
|
|
const char* value = get_default_session_variable((enum pgsql_variable_name)idx);
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection %s to %s\n", name, value);
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(value, strlen(value), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, idx, value, false)) {
|
|
|
return false;
|
|
|
}
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx])) {
|
|
|
param_status.push_back(std::make_pair(name, value));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (int idx : client_myds->myconn->dynamic_variables_idx) {
|
|
|
assert(idx < PGSQL_NAME_LAST_HIGH_WM);
|
|
|
const char* name = pgsql_tracked_variables[idx].set_variable_name;
|
|
|
const char* value = get_default_session_variable((enum pgsql_variable_name)idx);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection %s to %s\n", name, value);
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(value, strlen(value), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, idx, value, false)) {
|
|
|
return false;
|
|
|
}
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx])) {
|
|
|
param_status.push_back(std::make_pair(name, value));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
client_myds->myconn->reorder_dynamic_variables_idx();
|
|
|
|
|
|
} else if (std::find(pgsql_variables.ignore_vars.begin(), pgsql_variables.ignore_vars.end(), nq) != pgsql_variables.ignore_vars.end()) {
|
|
|
// this is a variable we parse but ignore
|
|
|
#ifdef DEBUG
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing RESET %s\n", nq.c_str());
|
|
|
#endif // DEBUG
|
|
|
} else {
|
|
|
|
|
|
int idx = PGSQL_NAME_LAST_HIGH_WM;
|
|
|
for (int i = 0; i < PGSQL_NAME_LAST_HIGH_WM; i++) {
|
|
|
|
|
|
if (i == PGSQL_NAME_LAST_LOW_WM)
|
|
|
continue;
|
|
|
|
|
|
if (variable_name_exists(pgsql_tracked_variables[i], nq.c_str()) == true) {
|
|
|
idx = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (idx != PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
const char* name = pgsql_tracked_variables[idx].set_variable_name;
|
|
|
const char* value = get_default_session_variable((enum pgsql_variable_name)idx);
|
|
|
|
|
|
uint32_t var_hash_int = SpookyHash::Hash32(value, strlen(value), 10);
|
|
|
if (pgsql_variables.client_get_hash(this, idx) != var_hash_int) {
|
|
|
if (!pgsql_variables.client_set_value(this, idx, value, true)) {
|
|
|
return false;
|
|
|
}
|
|
|
if (IS_PGTRACKED_VAR_OPTION_SET_PARAM_STATUS(pgsql_tracked_variables[idx])) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection %s to %s\n", name, value);
|
|
|
param_status.emplace_back(std::make_pair(name, value));
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
unable_to_parse_set_statement(lock_hostgroup);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
const char trx_state = (nTrx ? 'T' : 'I');
|
|
|
client_myds->myprot.generate_ok_packet(true, true, NULL, 0, dig, trx_state, NULL, param_status);
|
|
|
|
|
|
if (mirror == false) {
|
|
|
RequestEnd(NULL);
|
|
|
}
|
|
|
else {
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (mirror == true) { // for mirror session we exit here
|
|
|
current_hostgroup = qpo->destination_hostgroup;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
// handle case #1797
|
|
|
// handle case #2564
|
|
|
if ((pkt->size == SELECT_CONNECTION_ID_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncasecmp((char*)SELECT_CONNECTION_ID, (char*)pkt->ptr + 5, pkt->size - 5) == 0)) {
|
|
|
char buf[32];
|
|
|
char buf2[32];
|
|
|
sprintf(buf, "%u", thread_session_id);
|
|
|
int l0 = strlen("CONNECTION_ID()");
|
|
|
memcpy(buf2, (char*)pkt->ptr + 5 + SELECT_CONNECTION_ID_LEN - l0, l0);
|
|
|
buf2[l0] = 0;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
PgSQL_Data_Stream* myds = client_myds;
|
|
|
MySQL_Protocol* myprot = &client_myds->myprot;
|
|
|
myds->DSS = STATE_QUERY_SENT_DS;
|
|
|
int sid = 1;
|
|
|
myprot->generate_pkt_column_count(true, NULL, NULL, sid, 1); sid++;
|
|
|
myprot->generate_pkt_field(true, NULL, NULL, sid, (char*)"", (char*)"", (char*)"", buf2, (char*)"", 63, 31, MYSQL_TYPE_LONGLONG, 161, 0, false, 0, NULL); sid++;
|
|
|
myds->DSS = STATE_COLUMN_DEFINITION;
|
|
|
|
|
|
bool deprecate_eof_active = myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF;
|
|
|
if (!deprecate_eof_active) {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
|
|
|
char** p = (char**)malloc(sizeof(char*) * 1);
|
|
|
unsigned long* l = (unsigned long*)malloc(sizeof(unsigned long*) * 1);
|
|
|
l[0] = strlen(buf);
|
|
|
p[0] = buf;
|
|
|
myprot->generate_pkt_row(true, NULL, NULL, sid, 1, l, p); sid++;
|
|
|
myds->DSS = STATE_ROW;
|
|
|
|
|
|
if (deprecate_eof_active) {
|
|
|
myprot->generate_pkt_OK(true, NULL, NULL, sid, 0, 0, setStatus, 0, NULL, true); sid++;
|
|
|
}
|
|
|
else {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
myds->DSS = STATE_SLEEP;
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
free(p);
|
|
|
free(l);
|
|
|
return true;
|
|
|
}
|
|
|
#endif
|
|
|
// handle case #1421 , about LAST_INSERT_ID
|
|
|
if (CurrentQuery.QueryParserArgs.digest_text) {
|
|
|
char* dig = CurrentQuery.QueryParserArgs.digest_text;
|
|
|
if (strcasestr(dig, "LAST_INSERT_ID") || strcasestr(dig, "@@IDENTITY")) {
|
|
|
// we need to try to execute it where the last write was successful
|
|
|
if (last_HG_affected_rows >= 0) {
|
|
|
PgSQL_Backend* _mybe = NULL;
|
|
|
_mybe = find_backend(last_HG_affected_rows);
|
|
|
if (_mybe) {
|
|
|
if (_mybe->server_myds) {
|
|
|
if (_mybe->server_myds->myconn) {
|
|
|
if (_mybe->server_myds->myconn->pgsql_conn) { // we have an established connection
|
|
|
// this seems to be the right backend
|
|
|
qpo->destination_hostgroup = last_HG_affected_rows;
|
|
|
current_hostgroup = qpo->destination_hostgroup;
|
|
|
return false; // execute it on backend!
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#if 0 // TODO: fix this
|
|
|
// if we reached here, we don't know the right backend
|
|
|
// we try to determine if it is a simple "SELECT LAST_INSERT_ID()" or "SELECT @@IDENTITY" and we return pgsql->last_insert_id
|
|
|
|
|
|
//handle 2564
|
|
|
if (
|
|
|
(pkt->size == SELECT_LAST_INSERT_ID_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncasecmp((char*)SELECT_LAST_INSERT_ID, (char*)pkt->ptr + 5, pkt->size - 5) == 0)
|
|
|
||
|
|
|
(pkt->size == SELECT_LAST_INSERT_ID_LIMIT1_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncasecmp((char*)SELECT_LAST_INSERT_ID_LIMIT1, (char*)pkt->ptr + 5, pkt->size - 5) == 0)
|
|
|
||
|
|
|
(pkt->size == SELECT_VARIABLE_IDENTITY_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncasecmp((char*)SELECT_VARIABLE_IDENTITY, (char*)pkt->ptr + 5, pkt->size - 5) == 0)
|
|
|
||
|
|
|
(pkt->size == SELECT_VARIABLE_IDENTITY_LIMIT1_LEN + 5 && *((char*)(pkt->ptr) + 4) == (char)0x03 && strncasecmp((char*)SELECT_VARIABLE_IDENTITY_LIMIT1, (char*)pkt->ptr + 5, pkt->size - 5) == 0)
|
|
|
) {
|
|
|
char buf[32];
|
|
|
sprintf(buf, "%llu", last_insert_id);
|
|
|
char buf2[32];
|
|
|
int l0 = 0;
|
|
|
if (strcasestr(dig, "LAST_INSERT_ID")) {
|
|
|
l0 = strlen("LAST_INSERT_ID()");
|
|
|
memcpy(buf2, (char*)pkt->ptr + 5 + SELECT_LAST_INSERT_ID_LEN - l0, l0);
|
|
|
}
|
|
|
else if (strcasestr(dig, "@@IDENTITY")) {
|
|
|
l0 = strlen("@@IDENTITY");
|
|
|
memcpy(buf2, (char*)pkt->ptr + 5 + SELECT_VARIABLE_IDENTITY_LEN - l0, l0);
|
|
|
}
|
|
|
buf2[l0] = 0;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
PgSQL_Data_Stream* myds = client_myds;
|
|
|
MySQL_Protocol* myprot = &client_myds->myprot;
|
|
|
myds->DSS = STATE_QUERY_SENT_DS;
|
|
|
int sid = 1;
|
|
|
myprot->generate_pkt_column_count(true, NULL, NULL, sid, 1); sid++;
|
|
|
myprot->generate_pkt_field(true, NULL, NULL, sid, (char*)"", (char*)"", (char*)"", buf2, (char*)"", 63, 31, MYSQL_TYPE_LONGLONG, 161, 0, false, 0, NULL); sid++;
|
|
|
myds->DSS = STATE_COLUMN_DEFINITION;
|
|
|
|
|
|
bool deprecate_eof_active = myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF;
|
|
|
if (!deprecate_eof_active) {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
char** p = (char**)malloc(sizeof(char*) * 1);
|
|
|
unsigned long* l = (unsigned long*)malloc(sizeof(unsigned long*) * 1);
|
|
|
l[0] = strlen(buf);
|
|
|
p[0] = buf;
|
|
|
myprot->generate_pkt_row(true, NULL, NULL, sid, 1, l, p); sid++;
|
|
|
myds->DSS = STATE_ROW;
|
|
|
if (deprecate_eof_active) {
|
|
|
myprot->generate_pkt_OK(true, NULL, NULL, sid, 0, 0, setStatus, 0, NULL, true); sid++;
|
|
|
}
|
|
|
else {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
myds->DSS = STATE_SLEEP;
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
free(p);
|
|
|
free(l);
|
|
|
return true;
|
|
|
}
|
|
|
#endif
|
|
|
// if we reached here, we don't know the right backend and we cannot answer the query directly
|
|
|
// We continue the normal way
|
|
|
|
|
|
// as a precaution, we reset cache_ttl
|
|
|
qpo->cache_ttl = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// handle command KILL #860
|
|
|
//if (prepared == false) {
|
|
|
if (handle_command_query_kill(pkt)) {
|
|
|
return true;
|
|
|
}
|
|
|
//}
|
|
|
if (qpo->cache_ttl > 0 && ((prepare_stmt_type & PgSQL_ps_type_prepare_stmt) == 0)) {
|
|
|
|
|
|
const std::shared_ptr<PgSQL_QC_entry_t> pgsql_qc_entry = GloPgQC->get(
|
|
|
client_myds->myconn->userinfo->hash,
|
|
|
(const unsigned char*)CurrentQuery.QueryPointer,
|
|
|
CurrentQuery.QueryLength,
|
|
|
thread->curtime / 1000,
|
|
|
qpo->cache_ttl
|
|
|
);
|
|
|
if (pgsql_qc_entry) {
|
|
|
// FIXME: Add Error Transaction state detection
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
PgSQL_Data_Stream::copy_buffer_to_resultset(client_myds->PSarrayOUT,
|
|
|
pgsql_qc_entry->value, pgsql_qc_entry->length, (nTrx ? 'T' : 'I'));
|
|
|
//client_myds->PSarrayOUT->copy_add(resultset, 0, resultset->len);
|
|
|
if (transaction_persistent_hostgroup == -1) {
|
|
|
// not active, we can change it
|
|
|
current_hostgroup = -1;
|
|
|
}
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
__exit_set_destination_hostgroup:
|
|
|
|
|
|
if (qpo->next_query_flagIN >= 0) {
|
|
|
next_query_flagIN = qpo->next_query_flagIN;
|
|
|
}
|
|
|
if (qpo->destination_hostgroup >= 0) {
|
|
|
if (transaction_persistent_hostgroup == -1) {
|
|
|
current_hostgroup = qpo->destination_hostgroup;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (pgsql_thread___set_query_lock_on_hostgroup == 1) { // algorithm introduced in 2.0.6
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
if (current_hostgroup != locked_on_hostgroup) {
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
char buf[140];
|
|
|
sprintf(buf, "ProxySQL Error: connection is locked to hostgroup %d but trying to reach hostgroup %d", locked_on_hostgroup, current_hostgroup);
|
|
|
client_myds->myprot.generate_error_packet(true, true, buf,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, false);
|
|
|
thread->status_variables.stvar[st_var_hostgroup_locked_queries]++;
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STATISTICS(PtrSize_t* pkt) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_STATISTICS packet\n");
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_statistics_response(true, NULL, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_CHANGE_USER(PtrSize_t* pkt, bool* wrong_pass) {
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_CHANGE_USER packet\n");
|
|
|
//if (session_type == PROXYSQL_SESSION_PGSQL) {
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL || session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
reset();
|
|
|
init();
|
|
|
if (client_authenticated) {
|
|
|
if (use_ldap_auth == false) {
|
|
|
GloPgAuth->decrease_frontend_user_connections(client_myds->myconn->userinfo->username);
|
|
|
}
|
|
|
else {
|
|
|
GloMyLdapAuth->decrease_frontend_user_connections(client_myds->myconn->userinfo->fe_username);
|
|
|
}
|
|
|
}
|
|
|
client_authenticated = false;
|
|
|
if (client_myds->myprot.process_pkt_COM_CHANGE_USER((unsigned char*)pkt->ptr, pkt->size) == true) {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, 2, 0, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
*wrong_pass = false;
|
|
|
client_authenticated = true;
|
|
|
//int free_users=0;
|
|
|
int used_users = 0;
|
|
|
/*free_users */GloPgAuth->increase_frontend_user_connections(client_myds->myconn->userinfo->username, &used_users);
|
|
|
// FIXME: max_connections is not handled for CHANGE_USER
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
// 'COM_CHANGE_USER' didn't supply a password, and an 'Auth Switch Response' is
|
|
|
// required, going back to 'STATE_SERVER_HANDSHAKE' to perform the regular
|
|
|
// 'Auth Switch Response' for a connection is required. See #3504 for more context.
|
|
|
if (change_user_auth_switch) {
|
|
|
client_myds->DSS = STATE_SERVER_HANDSHAKE;
|
|
|
status = CONNECTING_CLIENT;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Wrong credentials for frontend: disconnecting\n");
|
|
|
*wrong_pass = true;
|
|
|
// FIXME: this should become close connection
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
char* client_addr = NULL;
|
|
|
if (client_myds->client_addr) {
|
|
|
char buf[512];
|
|
|
switch (client_myds->client_addr->sa_family) {
|
|
|
case AF_INET: {
|
|
|
struct sockaddr_in* ipv4 = (struct sockaddr_in*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
break;
|
|
|
}
|
|
|
case AF_INET6: {
|
|
|
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)client_myds->client_addr;
|
|
|
inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN);
|
|
|
client_addr = strdup(buf);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
client_addr = strdup((char*)"localhost");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
client_addr = strdup((char*)"");
|
|
|
}
|
|
|
char* _s = (char*)malloc(strlen(client_myds->myconn->userinfo->username) + 100 + strlen(client_addr));
|
|
|
sprintf(_s, "ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
proxy_error("ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)\n", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO"));
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 2, 1045, (char*)"28000", _s, true);
|
|
|
free(_s);
|
|
|
__sync_fetch_and_add(&PgHGM->status.access_denied_wrong_password, 1);
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
//FIXME: send an error message saying "not supported" or disconnect
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_RESET_CONNECTION(PtrSize_t* pkt) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got MYSQL_COM_RESET_CONNECTION packet\n");
|
|
|
|
|
|
if (session_type == PROXYSQL_SESSION_PGSQL || session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
// Backup the current relevant session values
|
|
|
int default_hostgroup = this->default_hostgroup;
|
|
|
bool transaction_persistent = this->transaction_persistent;
|
|
|
|
|
|
// Re-initialize the session
|
|
|
reset();
|
|
|
init();
|
|
|
|
|
|
// Recover the relevant session values
|
|
|
this->default_hostgroup = default_hostgroup;
|
|
|
this->transaction_persistent = transaction_persistent;
|
|
|
//-- client_myds->myconn->set_charset(default_charset, NAMES);
|
|
|
|
|
|
if (user_attributes != NULL && strlen(user_attributes)) {
|
|
|
nlohmann::json j_user_attributes = nlohmann::json::parse(user_attributes);
|
|
|
auto default_transaction_isolation = j_user_attributes.find("default-transaction_isolation");
|
|
|
|
|
|
if (default_transaction_isolation != j_user_attributes.end()) {
|
|
|
std::string def_trx_isolation_val =
|
|
|
j_user_attributes["default-transaction_isolation"].get<std::string>();
|
|
|
pgsql_variables.client_set_value(this, SQL_ISOLATION_LEVEL, def_trx_isolation_val.c_str());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, 2, 0, NULL);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
else {
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
|
|
|
std::string t_sql_error_msg{ "Received unsupported 'COM_RESET_CONNECTION' for session type '%s'" };
|
|
|
std::string sql_error_msg{};
|
|
|
string_format(t_sql_error_msg, sql_error_msg, proxysql_session_type_str(session_type).c_str());
|
|
|
|
|
|
client_myds->setDSS_STATE_QUERY_SENT_NET();
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 2, 1047, (char*)"28000", sql_error_msg.c_str(), true);
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
void PgSQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED__get_connection() {
|
|
|
// Get a MySQL Connection
|
|
|
|
|
|
PgSQL_Connection* mc = NULL;
|
|
|
char uuid[64];
|
|
|
uint64_t trxid = 0;
|
|
|
unsigned long long now_us = 0;
|
|
|
if (qpo->max_lag_ms >= 0) {
|
|
|
if (qpo->max_lag_ms > 360000) { // this is an absolute time, we convert it to relative
|
|
|
if (now_us == 0) {
|
|
|
now_us = realtime_time();
|
|
|
}
|
|
|
long long now_ms = now_us / 1000;
|
|
|
qpo->max_lag_ms = now_ms - qpo->max_lag_ms;
|
|
|
if (qpo->max_lag_ms < 0) {
|
|
|
qpo->max_lag_ms = -1; // time expired
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE && qpo->create_new_conn == false) {
|
|
|
#ifndef STRESSTEST_POOL
|
|
|
mc = thread->get_MyConn_local(mybe->hostgroup_id, this, NULL, 0, (int)qpo->max_lag_ms);
|
|
|
#endif // STRESSTEST_POOL
|
|
|
}
|
|
|
#ifdef STRESSTEST_POOL
|
|
|
// Check STRESSTEST_POOL in MySQL_HostGroups_Manager.h
|
|
|
// Note: this works only if session_fast_forward==false and create_new_conn is false too
|
|
|
#define NUM_SLOW_LOOPS 1000
|
|
|
// if STRESSTESTPOOL_MEASURE is define, time is measured in Query_Processor_time_nsec
|
|
|
// even if not the right variable
|
|
|
//#define STRESSTESTPOOL_MEASURE
|
|
|
#ifdef STRESSTESTPOOL_MEASURE
|
|
|
timespec begint;
|
|
|
timespec endt;
|
|
|
clock_gettime(CLOCK_MONOTONIC, &begint);
|
|
|
#endif // STRESSTESTPOOL_MEASURE
|
|
|
for (unsigned int loops = 0; loops < NUM_SLOW_LOOPS; loops++) {
|
|
|
#endif // STRESSTEST_POOL
|
|
|
|
|
|
if (mc == NULL) {
|
|
|
if (trxid) {
|
|
|
mc = PgHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), uuid, trxid, -1);
|
|
|
}
|
|
|
else {
|
|
|
mc = PgHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), NULL, 0, (int)qpo->max_lag_ms);
|
|
|
}
|
|
|
#ifdef STRESSTEST_POOL
|
|
|
if (mc && (loops < NUM_SLOW_LOOPS - 1)) {
|
|
|
if (mc->pgsql) {
|
|
|
mybe->server_myds->attach_connection(mc);
|
|
|
mybe->server_myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
mybe->server_myds->return_MySQL_Connection_To_Pool();
|
|
|
mc = NULL;
|
|
|
}
|
|
|
}
|
|
|
#endif // STRESSTEST_POOL
|
|
|
}
|
|
|
else {
|
|
|
thread->status_variables.stvar[st_var_ConnPool_get_conn_immediate]++;
|
|
|
}
|
|
|
#ifdef STRESSTEST_POOL
|
|
|
#ifdef STRESSTESTPOOL_MEASURE
|
|
|
clock_gettime(CLOCK_MONOTONIC, &endt);
|
|
|
thread->status_variables.query_processor_time = thread->status_variables.query_processor_time +
|
|
|
(endt.tv_sec * 1000000000 + endt.tv_nsec) -
|
|
|
(begint.tv_sec * 1000000000 + begint.tv_nsec);
|
|
|
#endif // STRESSTESTPOOL_MEASURE
|
|
|
}
|
|
|
#endif // STRESSTEST_POOL
|
|
|
if (mc) {
|
|
|
mybe->server_myds->attach_connection(mc);
|
|
|
thread->status_variables.stvar[st_var_ConnPool_get_conn_success]++;
|
|
|
}
|
|
|
else {
|
|
|
thread->status_variables.stvar[st_var_ConnPool_get_conn_failure]++;
|
|
|
}
|
|
|
if (qpo->max_lag_ms >= 0) {
|
|
|
if (qpo->max_lag_ms <= 360000) { // this is a relative time , we convert it to absolute
|
|
|
if (mc == NULL) {
|
|
|
if (CurrentQuery.waiting_since == 0) {
|
|
|
CurrentQuery.waiting_since = thread->curtime;
|
|
|
thread->status_variables.stvar[st_var_queries_with_max_lag_ms__delayed]++;
|
|
|
}
|
|
|
}
|
|
|
if (now_us == 0) {
|
|
|
now_us = realtime_time();
|
|
|
}
|
|
|
long long now_ms = now_us / 1000;
|
|
|
qpo->max_lag_ms = now_ms - qpo->max_lag_ms;
|
|
|
}
|
|
|
}
|
|
|
if (mc) {
|
|
|
if (CurrentQuery.waiting_since) {
|
|
|
unsigned long long waited = thread->curtime - CurrentQuery.waiting_since;
|
|
|
thread->status_variables.stvar[st_var_queries_with_max_lag_ms__total_wait_time_us] += waited;
|
|
|
CurrentQuery.waiting_since = 0;
|
|
|
}
|
|
|
}
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess=%p -- server_myds=%p -- PgSQL_Connection %p\n", this, mybe->server_myds, mybe->server_myds->myconn);
|
|
|
if (mybe->server_myds->myconn == NULL) {
|
|
|
// we couldn't get a connection for whatever reason, ex: no backends, or too busy
|
|
|
if (thread->mypolls.poll_timeout == 0) { // tune poll timeout
|
|
|
thread->mypolls.poll_timeout = pgsql_thread___poll_timeout_on_failure * 1000;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , DS=%p , poll_timeout=%u\n", mybe->server_myds->sess, mybe->server_myds, thread->mypolls.poll_timeout);
|
|
|
}
|
|
|
else {
|
|
|
if (thread->mypolls.poll_timeout > (unsigned int)pgsql_thread___poll_timeout_on_failure * 1000) {
|
|
|
thread->mypolls.poll_timeout = pgsql_thread___poll_timeout_on_failure * 1000;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , DS=%p , poll_timeout=%u\n", mybe->server_myds->sess, mybe->server_myds, thread->mypolls.poll_timeout);
|
|
|
}
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
if (mybe->server_myds->myconn->fd == -1) {
|
|
|
// we didn't get a valid connection, we need to create one
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess=%p -- PgSQL Connection has no FD\n", this);
|
|
|
PgSQL_Connection* myconn = mybe->server_myds->myconn;
|
|
|
myconn->userinfo->set(client_myds->myconn->userinfo);
|
|
|
|
|
|
myconn->handler(0);
|
|
|
mybe->server_myds->fd = myconn->fd;
|
|
|
mybe->server_myds->DSS = STATE_MARIADB_CONNECTING;
|
|
|
status = CONNECTING_SERVER;
|
|
|
mybe->server_myds->myconn->reusable = true;
|
|
|
}
|
|
|
else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess=%p -- PgSQL Connection found = %p\n", this, mybe->server_myds->myconn);
|
|
|
mybe->server_myds->assign_fd_from_mysql_conn();
|
|
|
mybe->server_myds->myds_type = MYDS_BACKEND;
|
|
|
mybe->server_myds->DSS = STATE_READY;
|
|
|
|
|
|
if (session_fast_forward) {
|
|
|
status = FAST_FORWARD;
|
|
|
mybe->server_myds->myconn->reusable = false; // the connection cannot be usable anymore
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::PgSQL_Result_to_PgSQL_wire(PgSQL_Connection* _conn, PgSQL_Data_Stream* _myds) {
|
|
|
if (_conn == NULL) {
|
|
|
// error
|
|
|
client_myds->myprot.generate_error_packet(true, true, "Lost connection to PostgreSQL server during query",
|
|
|
PGSQL_ERROR_CODES::ERRCODE_CONNECTION_FAILURE, false);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
PgSQL_Query_Result* query_result = _conn->query_result;
|
|
|
|
|
|
if (query_result && query_result->get_result_packet_type() != PGSQL_QUERY_RESULT_NO_DATA) {
|
|
|
bool transfer_started = query_result->is_transfer_started();
|
|
|
// if there is an error, it will be false so results are not cached
|
|
|
bool is_tuple = (
|
|
|
(query_result->get_result_packet_type() == (PGSQL_QUERY_RESULT_TUPLE | PGSQL_QUERY_RESULT_COMMAND | PGSQL_QUERY_RESULT_READY)) ||
|
|
|
(query_result->get_result_packet_type() == (PGSQL_QUERY_RESULT_NOTICE | PGSQL_QUERY_RESULT_TUPLE | PGSQL_QUERY_RESULT_COMMAND | PGSQL_QUERY_RESULT_READY))
|
|
|
);
|
|
|
const uint64_t num_rows = query_result->get_num_rows();
|
|
|
const uint64_t resultset_size = query_result->get_resultset_size();
|
|
|
const auto _affected_rows = query_result->get_affected_rows();
|
|
|
if (_affected_rows != static_cast<unsigned long long>(-1)) {
|
|
|
CurrentQuery.affected_rows = _affected_rows;
|
|
|
CurrentQuery.have_affected_rows = true;
|
|
|
}
|
|
|
CurrentQuery.rows_sent = num_rows;
|
|
|
bool resultset_completed = query_result->get_resultset(client_myds->PSarrayOUT);
|
|
|
if (_conn->processing_multi_statement == false)
|
|
|
assert(resultset_completed); // the resultset should always be completed if PgSQL_Result_to_PgSQL_wire is called
|
|
|
if (transfer_started == false && _conn->processing_multi_statement == false) { // we have all the resultset when PgSQL_Result_to_PgSQL_wire was called
|
|
|
if (qpo && qpo->cache_ttl > 0 && is_tuple == true) { // the resultset should be cached
|
|
|
|
|
|
if (_conn->is_error_present() == false &&
|
|
|
(/* check warnings count here*/ true ||
|
|
|
pgsql_thread___query_cache_handle_warnings == 1)) { // no errors
|
|
|
|
|
|
if (
|
|
|
(qpo->cache_empty_result == 1) ||
|
|
|
(
|
|
|
(qpo->cache_empty_result == -1) &&
|
|
|
(thread->variables.query_cache_stores_empty_result || num_rows)
|
|
|
)
|
|
|
) {
|
|
|
// Query Cache will have the ownership to buff. No need to free it here
|
|
|
unsigned char* buff = PgSQL_Data_Stream::copy_array_to_buffer(client_myds->PSarrayOUT,
|
|
|
resultset_size, false);
|
|
|
GloPgQC->set(
|
|
|
client_myds->myconn->userinfo->hash,
|
|
|
CurrentQuery.QueryPointer,
|
|
|
CurrentQuery.QueryLength,
|
|
|
buff,
|
|
|
resultset_size,
|
|
|
thread->curtime / 1000,
|
|
|
thread->curtime / 1000,
|
|
|
thread->curtime / 1000 + qpo->cache_ttl
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} else { // if query result is empty, means there was an error before query result was generated
|
|
|
|
|
|
if (!_conn->is_error_present())
|
|
|
assert(0); // if query result is empty, there should be an error present in connection.
|
|
|
|
|
|
if (_myds && _myds->killed_at) {
|
|
|
if (_myds->kill_type == 0) {
|
|
|
client_myds->myprot.generate_error_packet(true, true, (char*)"Query execution was interrupted, query_timeout exceeded",
|
|
|
PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false);
|
|
|
//PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1907);
|
|
|
}
|
|
|
else {
|
|
|
client_myds->myprot.generate_error_packet(true, true, (char*)"Query execution was interrupted",
|
|
|
PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false);
|
|
|
//PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1317);
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
client_myds->myprot.generate_error_packet(true, true, _conn->get_error_message().c_str(), _conn->get_error_code(), false);
|
|
|
//PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1907);
|
|
|
}
|
|
|
|
|
|
/*int myerrno = mysql_errno(pgsql);
|
|
|
if (myerrno == 0) {
|
|
|
unsigned int num_rows = mysql_affected_rows(pgsql);
|
|
|
uint16_t setStatus = (active_transactions ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
if (pgsql->server_status & SERVER_MORE_RESULTS_EXIST)
|
|
|
setStatus |= SERVER_MORE_RESULTS_EXIST;
|
|
|
setStatus |= (pgsql->server_status & ~SERVER_STATUS_AUTOCOMMIT); // get flags from server_status but ignore autocommit
|
|
|
setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, client_myds->pkt_sid + 1, num_rows, pgsql->insert_id, setStatus, warning_count, pgsql->info);
|
|
|
//client_myds->pkt_sid++;
|
|
|
}
|
|
|
else {
|
|
|
// error
|
|
|
char sqlstate[10];
|
|
|
sprintf(sqlstate, "%s", mysql_sqlstate(pgsql));
|
|
|
if (_myds && _myds->killed_at) { // see case #750
|
|
|
if (_myds->kill_type == 0) {
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 1907, sqlstate, (char*)"Query execution was interrupted, query_timeout exceeded");
|
|
|
}
|
|
|
else {
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 1317, sqlstate, (char*)"Query execution was interrupted");
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, mysql_errno(pgsql), sqlstate, mysql_error(pgsql));
|
|
|
}
|
|
|
//client_myds->pkt_sid++;
|
|
|
}
|
|
|
*/
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::SQLite3_to_MySQL(SQLite3_result* result, char* error, int affected_rows, MySQL_Protocol* myprot, bool in_transaction, bool deprecate_eof_active) {
|
|
|
assert(myprot);
|
|
|
MySQL_Data_Stream* myds = myprot->get_myds();
|
|
|
myds->DSS = STATE_QUERY_SENT_DS;
|
|
|
int sid = 1;
|
|
|
if (result) {
|
|
|
myprot->generate_pkt_column_count(true, NULL, NULL, sid, result->columns); sid++;
|
|
|
for (int i = 0; i < result->columns; i++) {
|
|
|
myprot->generate_pkt_field(true, NULL, NULL, sid, (char*)"", (char*)"", (char*)"", result->column_definition[i]->name, (char*)"", 33, 15, MYSQL_TYPE_VAR_STRING, 1, 0x1f, false, 0, NULL);
|
|
|
sid++;
|
|
|
}
|
|
|
myds->DSS = STATE_COLUMN_DEFINITION;
|
|
|
unsigned int nTrx = 0;
|
|
|
uint16_t setStatus = 0;
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
if (in_transaction == false) {
|
|
|
nTrx = NumActiveTransactions();
|
|
|
setStatus |= (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
}
|
|
|
else {
|
|
|
// this is for SQLite3 Server
|
|
|
if (session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
//if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
else {
|
|
|
// for sessions that are not SQLITE . Admin and Clickhouse .
|
|
|
// default
|
|
|
setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
setStatus |= SERVER_STATUS_IN_TRANS;
|
|
|
}
|
|
|
if (!deprecate_eof_active) {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
|
|
|
char** p = (char**)malloc(sizeof(char*) * result->columns);
|
|
|
unsigned long* l = (unsigned long*)malloc(sizeof(unsigned long*) * result->columns);
|
|
|
|
|
|
MySQL_ResultSet query_result{};
|
|
|
query_result.buffer_init(myprot);
|
|
|
|
|
|
for (int r = 0; r < result->rows_count; r++) {
|
|
|
for (int i = 0; i < result->columns; i++) {
|
|
|
l[i] = result->rows[r]->sizes[i];
|
|
|
p[i] = result->rows[r]->fields[i];
|
|
|
}
|
|
|
sid = myprot->generate_pkt_row3(&query_result, NULL, sid, result->columns, l, p, 0); sid++;
|
|
|
}
|
|
|
|
|
|
query_result.buffer_to_PSarrayOut();
|
|
|
query_result.get_resultset(myds->PSarrayOUT);
|
|
|
|
|
|
myds->DSS = STATE_ROW;
|
|
|
|
|
|
if (deprecate_eof_active) {
|
|
|
myprot->generate_pkt_OK(true, NULL, NULL, sid, 0, 0, setStatus, 0, NULL, true); sid++;
|
|
|
}
|
|
|
else {
|
|
|
myprot->generate_pkt_EOF(true, NULL, NULL, sid, 0, setStatus); sid++;
|
|
|
}
|
|
|
|
|
|
myds->DSS = STATE_SLEEP;
|
|
|
free(l);
|
|
|
free(p);
|
|
|
|
|
|
}
|
|
|
else { // no result set
|
|
|
if (error) {
|
|
|
// there was an error
|
|
|
if (strcmp(error, (char*)"database is locked") == 0) {
|
|
|
client_myds->myprot.generate_error_packet(true, true, error,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_T_R_DEADLOCK_DETECTED, false);
|
|
|
}
|
|
|
else {
|
|
|
client_myds->myprot.generate_error_packet(true, true, error,
|
|
|
PGSQL_ERROR_CODES::ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, false);
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
// no error, DML succeeded
|
|
|
unsigned int nTrx = 0;
|
|
|
uint16_t setStatus = 0;
|
|
|
if (in_transaction == false) {
|
|
|
nTrx = NumActiveTransactions();
|
|
|
setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
else {
|
|
|
// this is for SQLite3 Server
|
|
|
if (session_type == PROXYSQL_SESSION_SQLITE) {
|
|
|
//if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
else {
|
|
|
// for sessions that are not SQLITE . Admin and Clickhouse .
|
|
|
// default
|
|
|
setStatus |= SERVER_STATUS_AUTOCOMMIT;
|
|
|
}
|
|
|
setStatus |= SERVER_STATUS_IN_TRANS;
|
|
|
}
|
|
|
myprot->generate_pkt_OK(true, NULL, NULL, sid, affected_rows, 0, setStatus, 0, NULL);
|
|
|
}
|
|
|
myds->DSS = STATE_SLEEP;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
unsigned long long PgSQL_Session::IdleTime() {
|
|
|
unsigned long long ret = 0;
|
|
|
if (client_myds == 0) return 0;
|
|
|
if (status != WAITING_CLIENT_DATA && status != CONNECTING_CLIENT) return 0;
|
|
|
int idx = client_myds->poll_fds_idx;
|
|
|
unsigned long long last_sent = thread->mypolls.last_sent[idx];
|
|
|
unsigned long long last_recv = thread->mypolls.last_recv[idx];
|
|
|
unsigned long long last_time = (last_sent > last_recv ? last_sent : last_recv);
|
|
|
if (thread->curtime > last_time) {
|
|
|
ret = thread->curtime - last_time;
|
|
|
}
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// this is called either from RequestEnd(), or at the end of executing
|
|
|
// prepared statements
|
|
|
void PgSQL_Session::LogQuery(PgSQL_Data_Stream* myds) {
|
|
|
// we need to access statistics before calling CurrentQuery.end()
|
|
|
// so we track the time here
|
|
|
CurrentQuery.end_time = thread->curtime;
|
|
|
|
|
|
if (qpo) {
|
|
|
if (qpo->log == 1) {
|
|
|
GloPgSQL_Logger->log_request(this, myds); // we send for logging only if logging is enabled for this query
|
|
|
}
|
|
|
else {
|
|
|
if (qpo->log == -1) {
|
|
|
if (pgsql_thread___eventslog_default_log == 1) {
|
|
|
GloPgSQL_Logger->log_request(this, myds); // we send for logging only if enabled by default
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
void PgSQL_Session::RequestEnd(PgSQL_Data_Stream* myds, const unsigned int myerrno, const char * errmsg) {
|
|
|
// check if multiplexing needs to be disabled
|
|
|
char* qdt = NULL;
|
|
|
|
|
|
if (status != PROCESSING_STMT_EXECUTE) {
|
|
|
qdt = CurrentQuery.get_digest_text();
|
|
|
}
|
|
|
else {
|
|
|
qdt = CurrentQuery.stmt_info->digest_text;
|
|
|
}
|
|
|
|
|
|
if (qdt && myds && myds->myconn) {
|
|
|
myds->myconn->ProcessQueryAndSetStatusFlags(qdt);
|
|
|
}
|
|
|
|
|
|
switch (status) {
|
|
|
/*case PROCESSING_STMT_EXECUTE:
|
|
|
case PROCESSING_STMT_PREPARE:
|
|
|
// if a prepared statement is executed, LogQuery was already called
|
|
|
break;
|
|
|
*/
|
|
|
default:
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
LogQuery(myds);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
GloPgQPro->delete_QP_out(qpo);
|
|
|
// if there is an associated myds, clean its status
|
|
|
if (myds) {
|
|
|
// if there is a pgsql connection, clean its status
|
|
|
if (myds->myconn) {
|
|
|
myds->myconn->async_free_result();
|
|
|
myds->myconn->compute_unknown_transaction_status();
|
|
|
}
|
|
|
myds->free_mysql_real_query();
|
|
|
}
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) {
|
|
|
// reset status of the session
|
|
|
status = WAITING_CLIENT_DATA;
|
|
|
if (client_myds) {
|
|
|
// reset status of client data stream
|
|
|
client_myds->DSS = STATE_SLEEP;
|
|
|
// finalize the query
|
|
|
CurrentQuery.end();
|
|
|
}
|
|
|
}
|
|
|
//started_sending_data_to_client = false;
|
|
|
previous_hostgroup = current_hostgroup;
|
|
|
}
|
|
|
|
|
|
|
|
|
// this function tries to report all the memory statistics related to the sessions
|
|
|
void PgSQL_Session::Memory_Stats() {
|
|
|
if (thread == NULL)
|
|
|
return;
|
|
|
unsigned int i;
|
|
|
unsigned long long backend = 0;
|
|
|
unsigned long long frontend = 0;
|
|
|
unsigned long long internal = 0;
|
|
|
internal += sizeof(PgSQL_Session);
|
|
|
if (qpo)
|
|
|
internal += sizeof(PgSQL_Query_Processor_Output);
|
|
|
if (client_myds) {
|
|
|
internal += sizeof(PgSQL_Data_Stream);
|
|
|
if (client_myds->queueIN.buffer)
|
|
|
frontend += QUEUE_T_DEFAULT_SIZE;
|
|
|
if (client_myds->queueOUT.buffer)
|
|
|
frontend += QUEUE_T_DEFAULT_SIZE;
|
|
|
if (client_myds->myconn) {
|
|
|
internal += sizeof(PgSQL_Connection);
|
|
|
}
|
|
|
if (client_myds->PSarrayIN) {
|
|
|
internal += client_myds->PSarrayIN->total_size();
|
|
|
}
|
|
|
if (client_myds->PSarrayIN) {
|
|
|
if (session_fast_forward) {
|
|
|
internal += client_myds->PSarrayOUT->total_size();
|
|
|
} else {
|
|
|
internal += client_myds->PSarrayOUT->total_size(PGSQL_RESULTSET_BUFLEN);
|
|
|
//internal += client_myds->resultset->total_size(PGSQL_RESULTSET_BUFLEN);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
for (i = 0; i < mybes->len; i++) {
|
|
|
PgSQL_Backend* _mybe = (PgSQL_Backend*)mybes->index(i);
|
|
|
internal += sizeof(PgSQL_Backend);
|
|
|
if (_mybe->server_myds) {
|
|
|
internal += sizeof(PgSQL_Data_Stream);
|
|
|
if (_mybe->server_myds->queueIN.buffer)
|
|
|
backend += QUEUE_T_DEFAULT_SIZE;
|
|
|
if (_mybe->server_myds->queueOUT.buffer)
|
|
|
backend += QUEUE_T_DEFAULT_SIZE;
|
|
|
if (_mybe->server_myds->myconn) {
|
|
|
PgSQL_Connection* myconn = _mybe->server_myds->myconn;
|
|
|
internal += sizeof(PgSQL_Connection);
|
|
|
if (myconn->is_connected()) {
|
|
|
//backend += sizeof(MYSQL);
|
|
|
//backend += myconn->pgsql->net.max_packet;
|
|
|
backend += myconn->get_memory_usage();
|
|
|
//backend += (4096 * 15); // ASYNC_CONTEXT_DEFAULT_STACK_SIZE
|
|
|
}
|
|
|
if (myconn->query_result) {
|
|
|
backend += myconn->query_result->current_size();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
thread->status_variables.stvar[st_var_mysql_backend_buffers_bytes] += backend;
|
|
|
thread->status_variables.stvar[st_var_mysql_frontend_buffers_bytes] += frontend;
|
|
|
thread->status_variables.stvar[st_var_mysql_session_internal_bytes] += internal;
|
|
|
}
|
|
|
|
|
|
|
|
|
void PgSQL_Session::create_new_session_and_reset_connection(PgSQL_Data_Stream* _myds) {
|
|
|
PgSQL_Data_Stream* new_myds = NULL;
|
|
|
PgSQL_Connection* mc = _myds->myconn;
|
|
|
// we remove the connection from the original data stream
|
|
|
_myds->detach_connection();
|
|
|
_myds->unplug_backend();
|
|
|
|
|
|
// we create a brand new session, a new data stream, and attach the connection to it
|
|
|
PgSQL_Session* new_sess = new PgSQL_Session();
|
|
|
new_sess->mybe = new_sess->find_or_create_backend(mc->parent->myhgc->hid);
|
|
|
|
|
|
new_myds = new_sess->mybe->server_myds;
|
|
|
new_myds->attach_connection(mc);
|
|
|
new_myds->assign_fd_from_mysql_conn();
|
|
|
new_myds->myds_type = MYDS_BACKEND;
|
|
|
new_sess->to_process = 1;
|
|
|
new_myds->wait_until = thread->curtime + pgsql_thread___connect_timeout_server * 1000; // max_timeout
|
|
|
mc->last_time_used = thread->curtime;
|
|
|
new_myds->myprot.init(&new_myds, new_myds->myconn->userinfo, NULL);
|
|
|
new_sess->status = RESETTING_CONNECTION;
|
|
|
mc->async_state_machine = ASYNC_IDLE; // may not be true, but is used to correctly perform error handling
|
|
|
mc->auto_increment_delay_token = 0;
|
|
|
new_myds->DSS = STATE_MARIADB_QUERY;
|
|
|
thread->register_session_connection_handler(new_sess, true);
|
|
|
if (new_myds->mypolls == NULL) {
|
|
|
thread->mypolls.add(POLLIN | POLLOUT, new_myds->fd, new_myds, thread->curtime);
|
|
|
}
|
|
|
int rc = new_sess->handler();
|
|
|
if (rc == -1) {
|
|
|
unsigned int sess_idx = thread->mysql_sessions->len - 1;
|
|
|
thread->unregister_session(sess_idx);
|
|
|
delete new_sess;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool PgSQL_Session::handle_command_query_kill(PtrSize_t* pkt) {
|
|
|
/*unsigned char command_type = *((unsigned char*)pkt->ptr + sizeof(mysql_hdr));
|
|
|
if (CurrentQuery.QueryParserArgs.digest_text) {
|
|
|
if (command_type == _MYSQL_COM_QUERY) {
|
|
|
if (client_myds && client_myds->myconn) {
|
|
|
PgSQL_Connection* mc = client_myds->myconn;
|
|
|
if (mc->userinfo && mc->userinfo->username) {
|
|
|
if (CurrentQuery.PgQueryCmd == PGSQL_QUERY_KILL) {
|
|
|
char* qu = query_strip_comments((char*)pkt->ptr + 1 + sizeof(mysql_hdr), pkt->size - 1 - sizeof(mysql_hdr),
|
|
|
pgsql_thread___query_digests_lowercase);
|
|
|
string nq = string(qu, strlen(qu));
|
|
|
re2::RE2::Options* opt2 = new re2::RE2::Options(RE2::Quiet);
|
|
|
opt2->set_case_sensitive(false);
|
|
|
char* pattern = (char*)"^KILL\\s+(CONNECTION |QUERY |)\\s*(\\d+)\\s*$";
|
|
|
re2::RE2* re = new RE2(pattern, *opt2);
|
|
|
int id = 0;
|
|
|
string tk;
|
|
|
RE2::FullMatch(nq, *re, &tk, &id);
|
|
|
delete re;
|
|
|
delete opt2;
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 2, "filtered query= \"%s\"\n", qu);
|
|
|
free(qu);
|
|
|
if (id) {
|
|
|
int tki = -1;
|
|
|
if (tk.c_str()) {
|
|
|
if ((strlen(tk.c_str()) == 0) || (strcasecmp(tk.c_str(), "CONNECTION ") == 0)) {
|
|
|
tki = 0;
|
|
|
}
|
|
|
else {
|
|
|
if (strcasecmp(tk.c_str(), "QUERY ") == 0) {
|
|
|
tki = 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (tki >= 0) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 2, "Killing %s %d\n", (tki == 0 ? "CONNECTION" : "QUERY"), id);
|
|
|
GloPTH->kill_connection_or_query(id, (tki == 0 ? false : true), mc->userinfo->username);
|
|
|
client_myds->DSS = STATE_QUERY_SENT_NET;
|
|
|
unsigned int nTrx = NumActiveTransactions();
|
|
|
uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0);
|
|
|
if (autocommit) setStatus = SERVER_STATUS_AUTOCOMMIT;
|
|
|
client_myds->myprot.generate_pkt_OK(true, NULL, NULL, 1, 0, 0, setStatus, 0, NULL);
|
|
|
RequestEnd(NULL);
|
|
|
l_free(pkt->size, pkt->ptr);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}*/
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::finishQuery(PgSQL_Data_Stream* myds, PgSQL_Connection* myconn, bool prepared_stmt_with_no_params) {
|
|
|
myds->myconn->reduce_auto_increment_delay_token();
|
|
|
if (locked_on_hostgroup >= 0) {
|
|
|
if (qpo->multiplex == -1) {
|
|
|
myds->myconn->set_status(true, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const bool is_active_transaction = myds->myconn->IsActiveTransaction();
|
|
|
const bool multiplex_disabled_by_status = myds->myconn->MultiplexDisabled(false);
|
|
|
|
|
|
const bool multiplex_delayed = myds->myconn->auto_increment_delay_token > 0;
|
|
|
const bool multiplex_delayed_with_timeout =
|
|
|
!multiplex_disabled_by_status && multiplex_delayed && pgsql_thread___auto_increment_delay_multiplex_timeout_ms > 0;
|
|
|
|
|
|
const bool multiplex_disabled = !multiplex_disabled_by_status && (!multiplex_delayed || multiplex_delayed_with_timeout);
|
|
|
const bool conn_is_reusable = myds->myconn->reusable == true && !is_active_transaction && multiplex_disabled;
|
|
|
|
|
|
if (pgsql_thread___multiplexing && conn_is_reusable) {
|
|
|
if ((pgsql_thread___connection_delay_multiplex_ms || multiplex_delayed_with_timeout) && mirror == false) {
|
|
|
if (multiplex_delayed_with_timeout) {
|
|
|
uint64_t delay_multiplex_us = pgsql_thread___connection_delay_multiplex_ms * 1000;
|
|
|
uint64_t auto_increment_delay_us = pgsql_thread___auto_increment_delay_multiplex_timeout_ms * 1000;
|
|
|
uint64_t delay_us = delay_multiplex_us > auto_increment_delay_us ? delay_multiplex_us : auto_increment_delay_us;
|
|
|
|
|
|
myds->wait_until = thread->curtime + delay_us;
|
|
|
} else {
|
|
|
myds->wait_until = thread->curtime + pgsql_thread___connection_delay_multiplex_ms * 1000;
|
|
|
}
|
|
|
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myconn->multiplex_delayed = true;
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
} else if (prepared_stmt_with_no_params == true) { // see issue #1432
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
myds->wait_until = 0;
|
|
|
myconn->multiplex_delayed = false;
|
|
|
} else {
|
|
|
myconn->multiplex_delayed = false;
|
|
|
myds->wait_until = 0;
|
|
|
myds->DSS = STATE_NOT_INITIALIZED;
|
|
|
if (mysql_thread___autocommit_false_not_reusable && myds->myconn->IsAutoCommit() == false) {
|
|
|
create_new_session_and_reset_connection(myds);
|
|
|
}
|
|
|
else {
|
|
|
myds->return_MySQL_Connection_To_Pool();
|
|
|
}
|
|
|
}
|
|
|
if (transaction_persistent == true) {
|
|
|
transaction_persistent_hostgroup = -1;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
myconn->multiplex_delayed = false;
|
|
|
myconn->compute_unknown_transaction_status();
|
|
|
myconn->async_state_machine = ASYNC_IDLE;
|
|
|
myds->DSS = STATE_MARIADB_GENERIC;
|
|
|
if (transaction_persistent == true) {
|
|
|
if (transaction_persistent_hostgroup == -1) { // change only if not set already, do not allow to change it again
|
|
|
if (myds->myconn->IsActiveTransaction() == true) { // only active transaction is important here. Ignore other criterias
|
|
|
transaction_persistent_hostgroup = current_hostgroup;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (myds->myconn->IsActiveTransaction() == false) { // a transaction just completed
|
|
|
transaction_persistent_hostgroup = -1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
bool PgSQL_Session::known_query_for_locked_on_hostgroup(uint64_t digest) {
|
|
|
bool ret = false;
|
|
|
/*switch (digest) {
|
|
|
case 1732998280766099668ULL: // "SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT"
|
|
|
ret = true;
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}*/
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PgSQL_Session::unable_to_parse_set_statement(bool* lock_hostgroup) {
|
|
|
// we couldn't parse the query
|
|
|
string query_str = string((char*)CurrentQuery.QueryPointer, CurrentQuery.QueryLength);
|
|
|
string digest_str = string(CurrentQuery.get_digest_text());
|
|
|
const string& nqn = (pgsql_thread___parse_failure_logs_digest == true ? digest_str : query_str);
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Locking hostgroup for query %s\n", query_str.c_str());
|
|
|
if (qpo->multiplex == -1) {
|
|
|
// we have no rule about this SET statement. We set hostgroup locking
|
|
|
if (locked_on_hostgroup < 0) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "SET query to cause setting lock_hostgroup: %s\n", nqn.c_str());
|
|
|
if (known_query_for_locked_on_hostgroup(CurrentQuery.QueryParserArgs.digest)) {
|
|
|
proxy_info("Setting lock_hostgroup for SET query: %s\n", nqn.c_str());
|
|
|
} else {
|
|
|
if (client_myds && client_myds->addr.addr) {
|
|
|
proxy_warning("Unable to parse unknown SET query from client %s:%d. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", client_myds->addr.addr, client_myds->addr.port, nqn.c_str());
|
|
|
} else {
|
|
|
proxy_warning("Unable to parse unknown SET query. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", nqn.c_str());
|
|
|
}
|
|
|
}
|
|
|
*lock_hostgroup = true;
|
|
|
} else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "SET query to cause setting lock_hostgroup, but already set: %s\n", nqn.c_str());
|
|
|
if (known_query_for_locked_on_hostgroup(CurrentQuery.QueryParserArgs.digest)) {
|
|
|
//proxy_info("Setting lock_hostgroup for SET query: %s\n", nqn.c_str());
|
|
|
} else {
|
|
|
if (client_myds && client_myds->addr.addr) {
|
|
|
proxy_warning("Unable to parse unknown SET query from client %s:%d. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", client_myds->addr.addr, client_myds->addr.port, nqn.c_str());
|
|
|
} else {
|
|
|
proxy_warning("Unable to parse unknown SET query. Setting lock_hostgroup. Please report a bug for future enhancements:%s\n", nqn.c_str());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5,
|
|
|
"Unable to parse SET query but NOT setting lock_hostgroup %s\n", query_str.c_str());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::detected_broken_connection(const char* file, unsigned int line, const char* func, const char* action, PgSQL_Connection* myconn, bool verbose) {
|
|
|
|
|
|
const char* code = PgSQL_Error_Helper::get_error_code(PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION);;
|
|
|
const char* msg = "Detected offline server prior to statement execution";
|
|
|
|
|
|
if (myconn->is_error_present() == true) {
|
|
|
code = myconn->get_error_code_str();
|
|
|
msg = myconn->get_error_message().c_str();
|
|
|
}
|
|
|
|
|
|
unsigned long long last_used = thread->curtime - myconn->last_time_used;
|
|
|
last_used /= 1000;
|
|
|
if (verbose) {
|
|
|
proxy_error_inline(file, line, func, "Detected a broken connection while %s on (%d,%s,%d,%d) , FD (Conn:%d , MyDS:%d) , user %s , last_used %llums ago : %s, %s\n", action, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_backend_pid(), myconn->myds->fd, myconn->fd, myconn->userinfo->username, last_used, code, msg);
|
|
|
} else {
|
|
|
proxy_error_inline(file, line, func, "Detected a broken connection while %s on (%d,%s,%d,%d) , user %s , last_used %llums ago : %s, %s\n", action, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_backend_pid(), myconn->userinfo->username, last_used, code, msg);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::generate_status_one_hostgroup(int hid, std::string& s) {
|
|
|
SQLite3_result* resultset = PgHGM->SQL3_Connection_Pool(false, &hid);
|
|
|
json j_res;
|
|
|
if (resultset->rows_count) {
|
|
|
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
|
|
|
SQLite3_row* r = *it;
|
|
|
json j; // one json for each row
|
|
|
for (int i = 0; i < resultset->columns; i++) {
|
|
|
// using the format j["name"] == "value"
|
|
|
j[resultset->column_definition[i]->name] = (r->fields[i] ? std::string(r->fields[i]) : std::string("(null)"));
|
|
|
}
|
|
|
j_res.push_back(j); // the row json is added to the final json
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
j_res = json::array();
|
|
|
}
|
|
|
s = j_res.dump();
|
|
|
delete resultset;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief Sets the previous status of the PgSQL session according to the current status, with an option to allow EXECUTE statements.
|
|
|
*
|
|
|
* This method updates the previous status of the PgSQL session based on its current status. It employs a switch statement
|
|
|
* to determine the current status and then pushes the corresponding status value onto the `previous_status` stack. If the
|
|
|
* `allow_execute` parameter is set to true and the current status is `PROCESSING_STMT_EXECUTE`, the method pushes this status
|
|
|
* onto the stack; otherwise, it skips pushing the status for EXECUTE statements. If the current status does not match any known
|
|
|
* status value (which should not occur under normal circumstances), the method asserts to indicate a programming error.
|
|
|
* It currently works with only 3 possible status:
|
|
|
* - PROCESSING_QUERY
|
|
|
* - PROCESSING_STMT_PREPARE
|
|
|
* - PROCESSING_STMT_EXECUTE
|
|
|
*
|
|
|
* @param allow_execute A boolean value indicating whether to allow the status of EXECUTE statements to be pushed onto the
|
|
|
* `previous_status` stack. If set to true, the method will include EXECUTE statements in the session's status history.
|
|
|
*
|
|
|
* @return void.
|
|
|
* @note This method assumes that the `status` member variable has been properly initialized with one of the predefined
|
|
|
* status values.
|
|
|
* @note This method is primarily used to maintain a history of the session's previous states for later reference or
|
|
|
* recovery purposes.
|
|
|
* @note The LCOV_EXCL_START and LCOV_EXCL_STOP directives are used to exclude the assert statement from code coverage
|
|
|
* analysis because the condition should not occur during normal execution and is included as a safeguard against
|
|
|
* programming errors.
|
|
|
*/
|
|
|
void PgSQL_Session::set_previous_status_mode3(bool allow_execute) {
|
|
|
switch (status) {
|
|
|
case PROCESSING_QUERY:
|
|
|
previous_status.push(PROCESSING_QUERY);
|
|
|
break;
|
|
|
/*case PROCESSING_STMT_PREPARE:
|
|
|
previous_status.push(PROCESSING_STMT_PREPARE);
|
|
|
break;
|
|
|
case PROCESSING_STMT_EXECUTE:
|
|
|
if (allow_execute == true) {
|
|
|
previous_status.push(PROCESSING_STMT_EXECUTE);
|
|
|
break;
|
|
|
}
|
|
|
*/
|
|
|
default:
|
|
|
// LCOV_EXCL_START
|
|
|
assert(0); // Assert to indicate an unexpected status value
|
|
|
break;
|
|
|
// LCOV_EXCL_STOP
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::switch_normal_to_fast_forward_mode(PtrSize_t& pkt, std::string_view command, SESSION_FORWARD_TYPE session_type) {
|
|
|
|
|
|
if (session_fast_forward || session_type == SESSION_FORWARD_TYPE_PERMANENT) return;
|
|
|
|
|
|
// we use a switch to write the command in the info message
|
|
|
std::string client_info;
|
|
|
// we add the client details in the info message
|
|
|
if (client_myds && client_myds->addr.addr) {
|
|
|
client_info += " from client " + std::string(client_myds->addr.addr) + ":" + std::to_string(client_myds->addr.port);
|
|
|
}
|
|
|
proxy_info("Received command '%s'%s. Switching to Fast Forward mode (Session Type:0x%02X)\n",
|
|
|
command.data(), client_info.c_str(), session_type);
|
|
|
session_fast_forward = session_type;
|
|
|
|
|
|
if (client_myds->PSarrayIN->len) {
|
|
|
proxy_error("UNEXPECTED PACKET FROM CLIENT -- PLEASE REPORT A BUG\n");
|
|
|
assert(0);
|
|
|
}
|
|
|
|
|
|
mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active
|
|
|
// We reinitialize the 'wait_until' since this session shouldn't wait for processing as
|
|
|
// we are now transitioning to 'FAST_FORWARD'.
|
|
|
mybe->server_myds->wait_until = 0;
|
|
|
assert(mybe->server_myds->DSS != STATE_NOT_INITIALIZED);
|
|
|
|
|
|
// In case of having a connection, we need to make user to reset the state machine
|
|
|
// for current server 'PgSQL_Data_Stream'
|
|
|
mybe->server_myds->DSS = STATE_READY;
|
|
|
// myds needs to have encrypted value set correctly
|
|
|
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
assert(myconn != NULL);
|
|
|
|
|
|
// if backend connection uses SSL we will set
|
|
|
// encrypted = true and we will start using the SSL structure
|
|
|
// directly from PGconn SSL structure.
|
|
|
if (myconn->is_connected() && myconn->get_pg_ssl_in_use()) {
|
|
|
SSL* ssl_obj = myconn->get_pg_ssl_object();
|
|
|
if (ssl_obj != NULL) {
|
|
|
myds->encrypted = true;
|
|
|
myds->ssl = ssl_obj;
|
|
|
myds->rbio_ssl = BIO_new(BIO_s_mem());
|
|
|
myds->wbio_ssl = BIO_new(BIO_s_mem());
|
|
|
SSL_set_bio(myds->ssl, myds->rbio_ssl, myds->wbio_ssl);
|
|
|
} else {
|
|
|
// it means that ProxySQL tried to use SSL to connect to the backend
|
|
|
// but the backend didn't support SSL
|
|
|
}
|
|
|
}
|
|
|
set_status(FAST_FORWARD); // we can set status to FAST_FORWARD
|
|
|
//client_myds->PSarrayIN->add(pkt.ptr, pkt.size);
|
|
|
mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size);
|
|
|
|
|
|
// as we are in FAST_FORWARD mode, we directly send the packet to the backend.
|
|
|
// need to reset mysql_real_query
|
|
|
mybe->server_myds->mysql_real_query.reset();
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::switch_fast_forward_to_normal_mode() {
|
|
|
if (session_fast_forward == SESSION_FORWARD_TYPE_NONE) return;
|
|
|
|
|
|
// only handle temporary session ff
|
|
|
if (session_fast_forward & SESSION_FORWARD_TYPE_TEMPORARY) {
|
|
|
// we use a switch to write the command in the info message
|
|
|
std::string client_info;
|
|
|
// we add the client details in the info message
|
|
|
if (client_myds && client_myds->addr.addr) {
|
|
|
client_info += " for client " + std::string(client_myds->addr.addr) + ":" + std::to_string(client_myds->addr.port);
|
|
|
}
|
|
|
|
|
|
proxy_info("Switching back to Normal mode (Session Type:0x%02X)%s\n",
|
|
|
session_fast_forward, client_info.c_str());
|
|
|
session_fast_forward = SESSION_FORWARD_TYPE_NONE;
|
|
|
PgSQL_Data_Stream* myds = mybe->server_myds;
|
|
|
PgSQL_Connection* myconn = myds->myconn;
|
|
|
if (myds->encrypted == true) {
|
|
|
myds->encrypted = false;
|
|
|
myds->ssl = NULL;
|
|
|
}
|
|
|
RequestEnd(myds);
|
|
|
finishQuery(myds, myconn, false);
|
|
|
} else {
|
|
|
// cannot switch Permanent Fast Forward to Normal
|
|
|
assert(0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::set_default_session_variable(enum pgsql_variable_name idx, const char* value) {
|
|
|
assert(value);
|
|
|
if (idx >= 0 && idx < PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
if (default_session_variables[idx]) {
|
|
|
free(default_session_variables[idx]);
|
|
|
}
|
|
|
default_session_variables[idx] = strdup(value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const char* PgSQL_Session::get_default_session_variable(enum pgsql_variable_name idx) {
|
|
|
// Check if index is within valid range
|
|
|
if (idx >= 0 && idx < PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
// Attempt to retrieve value from default session variables
|
|
|
const char* val = default_session_variables[idx];
|
|
|
|
|
|
// Return the found value, or fall back to thread-specific default if null
|
|
|
return (val) ? val : pgsql_thread___default_variables[idx];
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
void PgSQL_Session::reset_default_session_variable(enum pgsql_variable_name idx) {
|
|
|
if (idx >= 0 && idx < PGSQL_NAME_LAST_HIGH_WM) {
|
|
|
if (default_session_variables[idx]) {
|
|
|
free(default_session_variables[idx]);
|
|
|
default_session_variables[idx] = NULL;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Optimized single‐pass parser for PostgreSQL DateStyle strings.
|
|
|
// It supports input in one of these forms:
|
|
|
// - "ISO, MDY" (two tokens separated by a comma)
|
|
|
// - "ISO" (a single token; the second string will be empty)
|
|
|
// Leading and trailing whitespace is removed from each token.
|
|
|
std::vector<std::string> PgSQL_DateStyle_Util::split_datestyle(std::string_view input) {
|
|
|
|
|
|
if (input.empty())
|
|
|
return {};
|
|
|
|
|
|
std::string token1, token2;
|
|
|
// Reserve capacity in case the input is large (typically not needed for DateStyle)
|
|
|
token1.reserve(input.size());
|
|
|
token2.reserve(input.size());
|
|
|
|
|
|
// Track last non-space character positions; -1 means “none yet.”
|
|
|
int lastNonSpace1 = -1, lastNonSpace2 = -1;
|
|
|
// currentToken: 1 = populating token1, 2 = populating token2.
|
|
|
int currentToken = 1;
|
|
|
|
|
|
for (char c : input) {
|
|
|
if (c == ',') {
|
|
|
// When a comma is encountered, finalize token1 and switch to token2.
|
|
|
if (currentToken == 1) {
|
|
|
if (lastNonSpace1 != -1) {
|
|
|
token1.resize(lastNonSpace1 + 1); // trim trailing whitespace from token1
|
|
|
}
|
|
|
currentToken = 2;
|
|
|
}
|
|
|
else {
|
|
|
// More than one comma encountered – not allowed.
|
|
|
proxy_error("Invalid \"datestyle\" value was provided. %s\n", input.data());
|
|
|
return {};
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
// Determine which token to fill.
|
|
|
std::string* currentStr = (currentToken == 1) ? &token1 : &token2;
|
|
|
int* lastNonSpace = (currentToken == 1) ? &lastNonSpace1 : &lastNonSpace2;
|
|
|
|
|
|
// Cache is-space check.
|
|
|
bool is_space = std::isspace(static_cast<unsigned char>(c));
|
|
|
// Skip leading whitespace for a new token.
|
|
|
if (currentStr->empty() && is_space) {
|
|
|
continue;
|
|
|
}
|
|
|
// Append the character.
|
|
|
currentStr->push_back(c);
|
|
|
// Update lastNonSpace index if the character is not a whitespace.
|
|
|
if (!is_space) {
|
|
|
*lastNonSpace = static_cast<int>(currentStr->size()) - 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Final trimming for the token being built.
|
|
|
if (currentToken == 1) {
|
|
|
if (lastNonSpace1 != -1) {
|
|
|
token1.resize(lastNonSpace1 + 1);
|
|
|
}
|
|
|
}
|
|
|
else { // currentToken == 2
|
|
|
if (lastNonSpace2 != -1) {
|
|
|
token2.resize(lastNonSpace2 + 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
std::vector<std::string> result;
|
|
|
result.reserve(2);
|
|
|
for (const std::string& token : { token1, token2 }) {
|
|
|
if (!token.empty()) {
|
|
|
result.emplace_back(token);
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
PgSQL_DateStyle_t PgSQL_DateStyle_Util::parse_datestyle(std::string_view input) {
|
|
|
PgSQL_DateStyleFormat_t newDateStyle = DATESTYLE_FORMAT_NONE;
|
|
|
PgSQL_DateStyleOrder_t newDateOrder = DATESTYLE_ORDER_NONE;
|
|
|
bool have_style = false;
|
|
|
bool have_order = false;
|
|
|
bool ok = true;
|
|
|
|
|
|
auto split_tokens = split_datestyle(input);
|
|
|
|
|
|
if (split_tokens.empty()) {
|
|
|
return { DATESTYLE_FORMAT_NONE, DATESTYLE_ORDER_NONE };
|
|
|
}
|
|
|
|
|
|
for (std::string_view token : split_tokens) {
|
|
|
const char* tok = token.data();
|
|
|
if (strcasecmp(tok, "ISO") == 0) {
|
|
|
if (have_style && newDateStyle != DATESTYLE_FORMAT_ISO)
|
|
|
ok = false; /* conflicting styles */
|
|
|
newDateStyle = DATESTYLE_FORMAT_ISO;
|
|
|
have_style = true;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "SQL") == 0) {
|
|
|
if (have_style && newDateStyle != DATESTYLE_FORMAT_SQL)
|
|
|
ok = false; /* conflicting styles */
|
|
|
newDateStyle = DATESTYLE_FORMAT_SQL;
|
|
|
have_style = true;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "POSTGRES") == 0) {
|
|
|
if (have_style && newDateStyle != DATESTYLE_FORMAT_POSTGRES)
|
|
|
ok = false; /* conflicting styles */
|
|
|
newDateStyle = DATESTYLE_FORMAT_POSTGRES;
|
|
|
have_style = true;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "GERMAN") == 0) {
|
|
|
if (have_style && newDateStyle != DATESTYLE_FORMAT_GERMAN)
|
|
|
ok = false; /* conflicting styles */
|
|
|
newDateStyle = DATESTYLE_FORMAT_GERMAN;
|
|
|
have_style = true;
|
|
|
/* GERMAN also sets DMY, unless explicitly overridden */
|
|
|
if (!have_order)
|
|
|
newDateOrder = DATESTYLE_ORDER_DMY;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "YMD") == 0) {
|
|
|
if (have_order && newDateOrder != DATESTYLE_ORDER_YMD)
|
|
|
ok = false; /* conflicting orders */
|
|
|
newDateOrder = DATESTYLE_ORDER_YMD;
|
|
|
have_order = true;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "DMY") == 0 ||
|
|
|
strcasecmp(tok, "EURO") == 0) {
|
|
|
if (have_order && newDateOrder != DATESTYLE_ORDER_DMY)
|
|
|
ok = false; /* conflicting orders */
|
|
|
newDateOrder = DATESTYLE_ORDER_DMY;
|
|
|
have_order = true;
|
|
|
}
|
|
|
else if (strcasecmp(tok, "MDY") == 0 ||
|
|
|
strcasecmp(tok, "US") == 0 ||
|
|
|
strcasecmp(tok, "NONEURO") == 0) {
|
|
|
if (have_order && newDateOrder != DATESTYLE_ORDER_MDY)
|
|
|
ok = false; /* conflicting orders */
|
|
|
newDateOrder = DATESTYLE_ORDER_MDY;
|
|
|
have_order = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// if the provided datestyle includes both style and order, ensure both values are valid.
|
|
|
if (split_tokens.size() == 2 && (have_style && have_order) == false) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Invalid \"datestyle\" value. %s\n", input.data());
|
|
|
return { DATESTYLE_FORMAT_NONE, DATESTYLE_ORDER_NONE };
|
|
|
}
|
|
|
|
|
|
if (!ok) {
|
|
|
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Conflicting \"datestyle\" value. %s\n", input.data());
|
|
|
return { DATESTYLE_FORMAT_NONE, DATESTYLE_ORDER_NONE };
|
|
|
}
|
|
|
|
|
|
return { newDateStyle, newDateOrder };
|
|
|
}
|
|
|
|
|
|
std::string PgSQL_DateStyle_Util::datestyle_to_string(PgSQL_DateStyle_t datestyle, const PgSQL_DateStyle_t& default_datestyle) {
|
|
|
|
|
|
if (datestyle.format == DATESTYLE_FORMAT_NONE && datestyle.order == DATESTYLE_ORDER_NONE) {
|
|
|
return {};
|
|
|
}
|
|
|
|
|
|
if (datestyle.format == DATESTYLE_FORMAT_NONE && default_datestyle.format != DATESTYLE_FORMAT_NONE) {
|
|
|
datestyle.format = default_datestyle.format;
|
|
|
}
|
|
|
|
|
|
if (datestyle.order == DATESTYLE_ORDER_NONE && default_datestyle.order != DATESTYLE_ORDER_NONE) {
|
|
|
datestyle.order = default_datestyle.order;
|
|
|
}
|
|
|
|
|
|
std::string result;
|
|
|
result.reserve(32);
|
|
|
switch (datestyle.format)
|
|
|
{
|
|
|
case DATESTYLE_FORMAT_ISO:
|
|
|
result.append("ISO");
|
|
|
break;
|
|
|
case DATESTYLE_FORMAT_SQL:
|
|
|
result.append("SQL");
|
|
|
break;
|
|
|
case DATESTYLE_FORMAT_GERMAN:
|
|
|
result.append("German");
|
|
|
break;
|
|
|
default:
|
|
|
result.append("Postgres");
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
switch (datestyle.order)
|
|
|
{
|
|
|
case DATESTYLE_ORDER_YMD:
|
|
|
result.append(", YMD");
|
|
|
break;
|
|
|
case DATESTYLE_ORDER_DMY:
|
|
|
result.append(", DMY");
|
|
|
break;
|
|
|
default:
|
|
|
result.append(", MDY");
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
std::string PgSQL_DateStyle_Util::datestyle_to_string(std::string_view input, const PgSQL_DateStyle_t& default_datestyle) {
|
|
|
return datestyle_to_string(parse_datestyle(input), default_datestyle);
|
|
|
}
|