|
|
|
|
@ -29,9 +29,10 @@ extern class PgSQL_Query_Processor* GloPgQPro;
|
|
|
|
|
* @return The number of rows affected or sent.
|
|
|
|
|
*/
|
|
|
|
|
static uint64_t extract_pg_rows_affected(const unsigned char* payload, size_t len, bool& is_select) {
|
|
|
|
|
if (len == 0) return 0;
|
|
|
|
|
std::string command_tag(reinterpret_cast<const char*>(payload), len);
|
|
|
|
|
is_select = false;
|
|
|
|
|
std::regex re("(INSERT|UPDATE|DELETE|SELECT|MOVE|FETCH)\\s+\\S*\\s*(\\d+)");
|
|
|
|
|
static const std::regex re("(INSERT|UPDATE|DELETE|SELECT|MOVE|FETCH)\\b.*?\\b(\\d+)$");
|
|
|
|
|
std::smatch matches;
|
|
|
|
|
if (std::regex_search(command_tag, matches, re)) {
|
|
|
|
|
if (matches.size() == 3) {
|
|
|
|
|
@ -45,7 +46,7 @@ static uint64_t extract_pg_rows_affected(const unsigned char* payload, size_t le
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PgSQLFFTO::PgSQLFFTO(PgSQL_Session* session)
|
|
|
|
|
: m_session(session), m_state(IDLE), m_query_start_time(0) {
|
|
|
|
|
: m_session(session), m_state(IDLE), m_query_start_time(0), m_affected_rows(0), m_rows_sent(0) {
|
|
|
|
|
m_client_buffer.reserve(1024);
|
|
|
|
|
m_server_buffer.reserve(4096);
|
|
|
|
|
}
|
|
|
|
|
@ -54,65 +55,119 @@ PgSQLFFTO::~PgSQLFFTO() {
|
|
|
|
|
on_close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PgSQLFFTO::on_client_data(const char* buf, size_t len) {
|
|
|
|
|
void PgSQLFFTO::on_client_data(const char* buf, std::size_t len) {
|
|
|
|
|
if (!buf || len == 0) return;
|
|
|
|
|
m_client_buffer.insert(m_client_buffer.end(), buf, buf + len);
|
|
|
|
|
while (m_client_buffer.size() >= 5) {
|
|
|
|
|
char type = m_client_buffer[0];
|
|
|
|
|
uint32_t msg_len; memcpy(&msg_len, &m_client_buffer[1], 4); msg_len = ntohl(msg_len);
|
|
|
|
|
if (m_client_buffer.size() < 1 + msg_len) break;
|
|
|
|
|
const unsigned char* payload = reinterpret_cast<const unsigned char*>(m_client_buffer.data()) + 5;
|
|
|
|
|
while (m_client_buffer.size() - m_client_offset >= 5) {
|
|
|
|
|
char type = m_client_buffer[m_client_offset];
|
|
|
|
|
uint32_t msg_len; memcpy(&msg_len, &m_client_buffer[m_client_offset + 1], 4); msg_len = ntohl(msg_len);
|
|
|
|
|
if (msg_len < 4 || msg_len > 1024 * 1024 * 1024) { // Sanity check
|
|
|
|
|
on_close();
|
|
|
|
|
m_client_buffer.clear(); m_client_offset = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_client_buffer.size() - m_client_offset < 1 + msg_len) break;
|
|
|
|
|
const unsigned char* payload = reinterpret_cast<const unsigned char*>(m_client_buffer.data()) + m_client_offset + 5;
|
|
|
|
|
process_client_message(type, payload, msg_len - 4);
|
|
|
|
|
m_client_buffer.erase(m_client_buffer.begin(), m_client_buffer.begin() + 1 + msg_len);
|
|
|
|
|
m_client_offset += 1 + msg_len;
|
|
|
|
|
}
|
|
|
|
|
if (m_client_offset > 0) {
|
|
|
|
|
if (m_client_offset >= m_client_buffer.size()) {
|
|
|
|
|
m_client_buffer.clear();
|
|
|
|
|
m_client_offset = 0;
|
|
|
|
|
} else if (m_client_offset > 4096) {
|
|
|
|
|
m_client_buffer.erase(m_client_buffer.begin(), m_client_buffer.begin() + m_client_offset);
|
|
|
|
|
m_client_offset = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PgSQLFFTO::on_server_data(const char* buf, size_t len) {
|
|
|
|
|
void PgSQLFFTO::on_server_data(const char* buf, std::size_t len) {
|
|
|
|
|
if (!buf || len == 0) return;
|
|
|
|
|
m_server_buffer.insert(m_server_buffer.end(), buf, buf + len);
|
|
|
|
|
while (m_server_buffer.size() >= 5) {
|
|
|
|
|
char type = m_server_buffer[0];
|
|
|
|
|
uint32_t msg_len; memcpy(&msg_len, &m_server_buffer[1], 4); msg_len = ntohl(msg_len);
|
|
|
|
|
if (m_server_buffer.size() < 1 + msg_len) break;
|
|
|
|
|
const unsigned char* payload = reinterpret_cast<const unsigned char*>(m_server_buffer.data()) + 5;
|
|
|
|
|
while (m_server_buffer.size() - m_server_offset >= 5) {
|
|
|
|
|
char type = m_server_buffer[m_server_offset];
|
|
|
|
|
uint32_t msg_len; memcpy(&msg_len, &m_server_buffer[m_server_offset + 1], 4); msg_len = ntohl(msg_len);
|
|
|
|
|
if (msg_len < 4 || msg_len > 1024 * 1024 * 1024) { // Sanity check
|
|
|
|
|
on_close();
|
|
|
|
|
m_server_buffer.clear(); m_server_offset = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_server_buffer.size() - m_server_offset < 1 + msg_len) break;
|
|
|
|
|
const unsigned char* payload = reinterpret_cast<const unsigned char*>(m_server_buffer.data()) + m_server_offset + 5;
|
|
|
|
|
process_server_message(type, payload, msg_len - 4);
|
|
|
|
|
m_server_buffer.erase(m_server_buffer.begin(), m_server_buffer.begin() + 1 + msg_len);
|
|
|
|
|
m_server_offset += 1 + msg_len;
|
|
|
|
|
}
|
|
|
|
|
if (m_server_offset > 0) {
|
|
|
|
|
if (m_server_offset >= m_server_buffer.size()) {
|
|
|
|
|
m_server_buffer.clear();
|
|
|
|
|
m_server_offset = 0;
|
|
|
|
|
} else if (m_server_offset > 4096) {
|
|
|
|
|
m_server_buffer.erase(m_server_buffer.begin(), m_server_buffer.begin() + m_server_offset);
|
|
|
|
|
m_server_offset = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PgSQLFFTO::on_close() {
|
|
|
|
|
if (m_state != IDLE && m_query_start_time != 0) {
|
|
|
|
|
unsigned long long duration = monotonic_time() - m_query_start_time;
|
|
|
|
|
report_query_stats(m_current_query, duration);
|
|
|
|
|
report_query_stats(m_current_query, duration, m_affected_rows, m_rows_sent);
|
|
|
|
|
m_state = IDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PgSQLFFTO::process_client_message(char type, const unsigned char* payload, size_t len) {
|
|
|
|
|
if (type == 'Q') {
|
|
|
|
|
m_current_query = std::string(reinterpret_cast<const char*>(payload), len);
|
|
|
|
|
size_t query_len = (len > 0 && payload[len - 1] == 0) ? len - 1 : len;
|
|
|
|
|
m_current_query = std::string(reinterpret_cast<const char*>(payload), query_len);
|
|
|
|
|
m_query_start_time = monotonic_time();
|
|
|
|
|
m_state = AWAITING_RESPONSE;
|
|
|
|
|
m_affected_rows = 0; m_rows_sent = 0;
|
|
|
|
|
} else if (type == 'P') {
|
|
|
|
|
std::string stmt_name = reinterpret_cast<const char*>(payload);
|
|
|
|
|
const char* query = reinterpret_cast<const char*>(payload) + stmt_name.length() + 1;
|
|
|
|
|
m_statements[stmt_name] = query;
|
|
|
|
|
const char* p = reinterpret_cast<const char*>(payload);
|
|
|
|
|
size_t name_len = strnlen(p, len);
|
|
|
|
|
if (name_len >= len) return; // No null terminator
|
|
|
|
|
std::string stmt_name(p, name_len);
|
|
|
|
|
const char* query_ptr = p + name_len + 1;
|
|
|
|
|
size_t rem = len - (name_len + 1);
|
|
|
|
|
size_t query_text_len = strnlen(query_ptr, rem);
|
|
|
|
|
m_statements[stmt_name] = std::string(query_ptr, query_text_len);
|
|
|
|
|
} else if (type == 'B') {
|
|
|
|
|
std::string portal_name = reinterpret_cast<const char*>(payload);
|
|
|
|
|
const char* stmt_ptr = reinterpret_cast<const char*>(payload) + portal_name.length() + 1;
|
|
|
|
|
std::string stmt_name = stmt_ptr;
|
|
|
|
|
m_portals[portal_name] = stmt_name;
|
|
|
|
|
const char* p = reinterpret_cast<const char*>(payload);
|
|
|
|
|
size_t portal_len = strnlen(p, len);
|
|
|
|
|
if (portal_len >= len) return;
|
|
|
|
|
std::string portal_name(p, portal_len);
|
|
|
|
|
const char* stmt_ptr = p + portal_len + 1;
|
|
|
|
|
size_t rem = len - (portal_len + 1);
|
|
|
|
|
size_t stmt_name_len = strnlen(stmt_ptr, rem);
|
|
|
|
|
if (stmt_name_len >= rem) return;
|
|
|
|
|
m_portals[portal_name] = std::string(stmt_ptr, stmt_name_len);
|
|
|
|
|
} else if (type == 'E') {
|
|
|
|
|
std::string portal_name = reinterpret_cast<const char*>(payload);
|
|
|
|
|
const char* p = reinterpret_cast<const char*>(payload);
|
|
|
|
|
size_t portal_len = strnlen(p, len);
|
|
|
|
|
std::string portal_name(p, std::min(portal_len, len));
|
|
|
|
|
auto pit = m_portals.find(portal_name);
|
|
|
|
|
if (pit != m_portals.end()) {
|
|
|
|
|
auto sit = m_statements.find(pit->second);
|
|
|
|
|
if (sit != m_statements.end()) {
|
|
|
|
|
if (m_state == AWAITING_RESPONSE) {
|
|
|
|
|
on_close(); // Finalize previous if any
|
|
|
|
|
}
|
|
|
|
|
m_current_query = sit->second;
|
|
|
|
|
m_query_start_time = monotonic_time();
|
|
|
|
|
m_state = AWAITING_RESPONSE;
|
|
|
|
|
m_affected_rows = 0; m_rows_sent = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (type == 'C') { // Frontend Close
|
|
|
|
|
if (len < 1) return;
|
|
|
|
|
char close_type = static_cast<char>(payload[0]);
|
|
|
|
|
const char* name_ptr = reinterpret_cast<const char*>(payload) + 1;
|
|
|
|
|
size_t name_len = strnlen(name_ptr, len - 1);
|
|
|
|
|
std::string name(name_ptr, name_len);
|
|
|
|
|
if (close_type == 'S') m_statements.erase(name);
|
|
|
|
|
else if (close_type == 'P') m_portals.erase(name);
|
|
|
|
|
} else if (type == 'X') {
|
|
|
|
|
on_close();
|
|
|
|
|
}
|
|
|
|
|
@ -121,20 +176,18 @@ void PgSQLFFTO::process_client_message(char type, const unsigned char* payload,
|
|
|
|
|
void PgSQLFFTO::process_server_message(char type, const unsigned char* payload, size_t len) {
|
|
|
|
|
if (m_state == IDLE) return;
|
|
|
|
|
if (type == 'C') {
|
|
|
|
|
unsigned long long duration = monotonic_time() - m_query_start_time;
|
|
|
|
|
bool is_select = false;
|
|
|
|
|
uint64_t rows = extract_pg_rows_affected(payload, len, is_select);
|
|
|
|
|
if (is_select) report_query_stats(m_current_query, duration, 0, rows);
|
|
|
|
|
else report_query_stats(m_current_query, duration, rows, 0);
|
|
|
|
|
m_state = IDLE;
|
|
|
|
|
if (is_select) m_rows_sent += rows;
|
|
|
|
|
else m_affected_rows += rows;
|
|
|
|
|
} else if (type == 'Z' || type == 'E') {
|
|
|
|
|
// ReadyForQuery or ErrorResponse. We don't always want to report stats here if 'C' already did it.
|
|
|
|
|
// But if we didn't get 'C', report what we have.
|
|
|
|
|
// ReadyForQuery or ErrorResponse
|
|
|
|
|
if (m_state == AWAITING_RESPONSE) {
|
|
|
|
|
unsigned long long duration = monotonic_time() - m_query_start_time;
|
|
|
|
|
report_query_stats(m_current_query, duration);
|
|
|
|
|
report_query_stats(m_current_query, duration, m_affected_rows, m_rows_sent);
|
|
|
|
|
}
|
|
|
|
|
m_state = IDLE;
|
|
|
|
|
m_affected_rows = 0; m_rows_sent = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|