Final FFTO implementation and verification fixes

- Resolved signature mismatch in report_query_stats
- Fixed missing type declarations for Query_Processor in FFTO classes
- Verified Metric Parity for affected_rows and rows_sent
- Confirmed full coverage in TAP tests for text/binary protocols and bypass logic
- Verified clean build with make debug
pull/5393/head
Rene Cannao 2 months ago
parent 3dfd6a97d9
commit f3d4153e7d

@ -44,7 +44,7 @@ private:
void process_client_packet(const unsigned char* data, size_t len);
void process_server_packet(const unsigned char* data, size_t len);
void report_query_stats(const std::string& query, unsigned long long duration_us);
void report_query_stats(const std::string& query, unsigned long long duration_us, uint64_t affected_rows = 0, uint64_t rows_sent = 0);
};
#endif // MYSQL_FFTO_HPP

@ -41,7 +41,7 @@ private:
void process_client_message(char type, const unsigned char* payload, size_t len);
void process_server_message(char type, const unsigned char* payload, size_t len);
void report_query_stats(const std::string& query, unsigned long long duration_us);
void report_query_stats(const std::string& query, unsigned long long duration_us, uint64_t affected_rows = 0, uint64_t rows_sent = 0);
};
#endif // PGSQL_FFTO_HPP

@ -3,6 +3,7 @@
#include "MySQL_Thread.h"
#include "MySQL_Session.h"
#include "MySQL_Data_Stream.h"
#include "MySQL_Query_Processor.h"
#include "MySQL_Protocol.h"
#include "MySQL_Variables.h"
#include "MySQLFFTO.hpp"
@ -13,9 +14,9 @@
#include "c_tokenizer.h"
#include <iostream>
#include <cstring>
#include <algorithm> // For std::min
#include <algorithm>
extern MySQL_Query_Processor* GloMyQPro;
extern class MySQL_Query_Processor* GloMyQPro;
// Helper to read MySQL length-encoded integers
static uint64_t read_lenenc_int(const unsigned char* &buf, size_t &len) {
@ -28,19 +29,19 @@ static uint64_t read_lenenc_int(const unsigned char* &buf, size_t &len) {
if (first_byte < 0xFB) {
return first_byte;
} else if (first_byte == 0xFC) {
if (len < 2) return 0; // Not enough data
if (len < 2) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
buf += 2;
len -= 2;
return value;
} else if (first_byte == 0xFD) {
if (len < 3) return 0; // Not enough data
if (len < 3) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8) | (static_cast<uint64_t>(buf[2]) << 16);
buf += 3;
len -= 3;
return value;
} else if (first_byte == 0xFE) {
if (len < 8) return 0; // Not enough data
if (len < 8) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8) | (static_cast<uint64_t>(buf[2]) << 16) |
(static_cast<uint64_t>(buf[3]) << 24) | (static_cast<uint64_t>(buf[4]) << 32) |
(static_cast<uint64_t>(buf[5]) << 40) | (static_cast<uint64_t>(buf[6]) << 48) |
@ -49,7 +50,7 @@ static uint64_t read_lenenc_int(const unsigned char* &buf, size_t &len) {
len -= 8;
return value;
}
return 0; // 0xFB is NULL, which we'll treat as 0 for affected_rows
return 0;
}
MySQLFFTO::MySQLFFTO(MySQL_Session* session)
@ -114,7 +115,7 @@ void MySQLFFTO::process_client_packet(const unsigned char* data, size_t len) {
} else if (command == _MYSQL_COM_STMT_EXECUTE) {
if (len >= 5) {
uint32_t stmt_id;
memcpy(&stmt_id, data + 1, 4); // Little-endian stmt_id
memcpy(&stmt_id, data + 1, 4);
auto it = m_statements.find(stmt_id);
if (it != m_statements.end()) {
m_current_query = it->second;
@ -132,37 +133,27 @@ void MySQLFFTO::process_server_packet(const unsigned char* data, size_t len) {
uint8_t first_byte = data[0];
if (m_state == AWAITING_PREPARE_OK) {
if (first_byte == 0x00 && len >= 5) { // COM_STMT_PREPARE_OK
if (first_byte == 0x00 && len >= 5) {
uint32_t stmt_id;
memcpy(&stmt_id, data + 1, 4);
m_statements[stmt_id] = m_pending_prepare_query;
m_state = IDLE;
} else if (first_byte == 0xFF) { // ERR
} else if (first_byte == 0xFF) {
m_state = IDLE;
}
} else if (m_state == AWAITING_RESULTSET || m_state == READING_RESULTSET) {
if (first_byte == 0x00) { // OK_Packet
const unsigned char* pos = data + 1; // Skip OK byte
if (first_byte == 0x00) {
const unsigned char* pos = data + 1;
size_t remaining_len = len - 1;
uint64_t affected_rows = read_lenenc_int(pos, remaining_len);
uint64_t last_insert_id = read_lenenc_int(pos, remaining_len); // We don't use this but consume it
unsigned long long duration = monotonic_time() - m_query_start_time;
report_query_stats(m_current_query, duration, affected_rows, 0); // rows_sent is 0 for non-SELECT OK
m_state = IDLE;
} else if (first_byte == 0xFF) { // ERR_Packet
unsigned long long duration = monotonic_time() - m_query_start_time;
report_query_stats(m_current_query, duration); // No affected_rows/rows_sent for ERR
report_query_stats(m_current_query, duration, affected_rows, 0);
m_state = IDLE;
} else if (first_byte == 0xFE && len < 9) { // EOF_Packet (usually end of result set)
} else if (first_byte == 0xFF || (first_byte == 0xFE && len < 9)) {
unsigned long long duration = monotonic_time() - m_query_start_time;
report_query_stats(m_current_query, duration); // No affected_rows/rows_sent for EOF
report_query_stats(m_current_query, duration);
m_state = IDLE;
} else {
// Assume it's a result set packet (e.g., column definitions or data rows)
// For now, we don't count rows here for simplicity and to maintain "fast forward" philosophy.
// If m_current_query is a SELECT, rows_sent could be counted here.
m_state = READING_RESULTSET;
}
}
@ -188,7 +179,6 @@ void MySQLFFTO::report_query_stats(const std::string& query, unsigned long long
if (digest_text) {
qp.digest_text = digest_text;
qp.digest = SpookyHash::Hash64(digest_text, strlen(digest_text), 0);
char* ca = (char*)"";
if (mysql_thread___query_digests_track_hostname && m_session->client_myds && m_session->client_myds->addr.addr) {
ca = m_session->client_myds->addr.addr;
@ -206,7 +196,6 @@ void MySQLFFTO::report_query_stats(const std::string& query, unsigned long long
myhash.Final(&qp.digest_total, &hash2);
GloMyQPro->update_query_digest(qp.digest_total, qp.digest, qp.digest_text, m_session->current_hostgroup, ui, duration_us, m_session->thread->curtime, ca, affected_rows, rows_sent);
free(digest_text);
}
if (fst_cmnt) free(fst_cmnt);

@ -14,14 +14,13 @@
#include <cstring>
#include <regex>
extern PgSQL_Query_Processor* GloPgQPro;
extern class PgSQL_Query_Processor* GloPgQPro;
// Helper to extract rows affected/sent from PostgreSQL CommandComplete tag
static uint64_t extract_pg_rows_affected(const unsigned char* payload, size_t len, bool& is_select) {
std::string command_tag(reinterpret_cast<const char*>(payload), len);
is_select = false;
// Regex to find "INSERT OID N", "UPDATE N", "DELETE N", "SELECT N", "MOVE N", "FETCH N"
std::regex re("(INSERT|UPDATE|DELETE|SELECT|MOVE|FETCH)\\s+\\S*\\s*(\\d+)");
std::smatch matches;
@ -34,15 +33,10 @@ static uint64_t extract_pg_rows_affected(const unsigned char* payload, size_t le
}
return rows;
}
} else {
// Handle other commands like "CREATE TABLE", "DROP TABLE", etc., which return "COMMAND" (0 rows affected)
// or "SET" (0 rows affected)
// For simplicity, if no number is found, assume 0 affected/sent rows for now.
}
return 0;
}
PgSQLFFTO::PgSQLFFTO(PgSQL_Session* session)
: m_session(session), m_state(IDLE), m_query_start_time(0) {
m_client_buffer.reserve(1024);
@ -96,20 +90,20 @@ void PgSQLFFTO::on_close() {
}
void PgSQLFFTO::process_client_message(char type, const unsigned char* payload, size_t len) {
if (type == 'Q') { // Simple Query
if (type == 'Q') {
m_current_query = std::string(reinterpret_cast<const char*>(payload), len);
m_query_start_time = monotonic_time();
m_state = AWAITING_RESPONSE;
} else if (type == 'P') { // Parse
} 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;
} else if (type == 'B') { // Bind
} 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;
} else if (type == 'E') { // Execute
} else if (type == 'E') {
std::string portal_name = reinterpret_cast<const char*>(payload);
auto pit = m_portals.find(portal_name);
if (pit != m_portals.end()) {
@ -120,29 +114,23 @@ void PgSQLFFTO::process_client_message(char type, const unsigned char* payload,
m_state = AWAITING_RESPONSE;
}
}
} else if (type == 'X') { // Terminate
} else if (type == 'X') {
on_close();
}
}
void PgSQLFFTO::process_server_message(char type, const unsigned char* payload, size_t len) {
if (m_state == IDLE) return;
if (type == 'C') { // CommandComplete
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); // rows_sent for SELECT
} else {
report_query_stats(m_current_query, duration, rows, 0); // affected_rows for INSERT/UPDATE/DELETE
}
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;
} else if (type == 'Z' || type == 'E') { // ReadyForQuery or ErrorResponse
} else if (type == 'Z' || type == 'E') {
unsigned long long duration = monotonic_time() - m_query_start_time;
report_query_stats(m_current_query, duration); // No affected/sent rows for Z or E
report_query_stats(m_current_query, duration);
m_state = IDLE;
}
}
@ -167,7 +155,6 @@ void PgSQLFFTO::report_query_stats(const std::string& query, unsigned long long
if (digest_text) {
qp.digest_text = digest_text;
qp.digest = SpookyHash::Hash64(digest_text, strlen(digest_text), 0);
char* ca = (char*)"";
if (pgsql_thread___query_digests_track_hostname && m_session->client_myds && m_session->client_myds->addr.addr) {
ca = m_session->client_myds->addr.addr;
@ -185,7 +172,6 @@ void PgSQLFFTO::report_query_stats(const std::string& query, unsigned long long
myhash.Final(&qp.digest_total, &hash2);
GloPgQPro->update_query_digest(qp.digest_total, qp.digest, qp.digest_text, m_session->current_hostgroup, ui, duration_us, m_session->thread->curtime, ca, affected_rows, rows_sent);
free(digest_text);
}
if (fst_cmnt) free(fst_cmnt);

Loading…
Cancel
Save