From cbb8505ca1cb4297a0030848be4ec7e462d76dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Fri, 25 Apr 2025 05:45:26 +0000 Subject: [PATCH 1/6] Improvement in prepared statements logging In bufferTypeInfoMap , use sizeof(MYSQL_TIME) for the size of these types: * MYSQL_TYPE_TIMESTAMP * MYSQL_TYPE_DATE * MYSQL_TYPE_TIME * MYSQL_TYPE_DATETIME Notes: * sizeof(MYSQL_TIME) is 40 * client library may use MYSQL_TYPE_DATETIME for all of these types --- lib/MySQL_Logger.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index 99bb9549b..a92bcd134 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -64,6 +64,7 @@ struct BufferTypeInfo { // Helper lambda to convert binary data to a hex string. auto binaryToHex = [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { std::ostringstream oss; + oss << "0x"; const unsigned char* data = reinterpret_cast(bind->buffer); for (unsigned long i = 0; i < len; i++) { oss << std::setw(2) << std::setfill('0') << std::hex << (int)data[i]; @@ -109,7 +110,7 @@ static const std::unordered_map bufferTypeInfoMap }, { MYSQL_TYPE_TIMESTAMP, { "TIMESTAMP", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - binaryToHex(bind, len, out); + binaryToHex(bind, sizeof(MYSQL_TIME), out); }} }, { MYSQL_TYPE_LONGLONG, @@ -125,17 +126,17 @@ static const std::unordered_map bufferTypeInfoMap }, { MYSQL_TYPE_DATE, { "DATE", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - binaryToHex(bind, len, out); + binaryToHex(bind, sizeof(MYSQL_TIME), out); }} }, { MYSQL_TYPE_TIME, { "TIME", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - binaryToHex(bind, len, out); + binaryToHex(bind, sizeof(MYSQL_TIME), out); }} }, { MYSQL_TYPE_DATETIME, { "DATETIME", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - binaryToHex(bind, len, out); + binaryToHex(bind, sizeof(MYSQL_TIME), out); }} }, { MYSQL_TYPE_YEAR, From 65f79a07d297855fa9b3238a5797c4fc8813d92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Fri, 25 Apr 2025 06:21:14 +0000 Subject: [PATCH 2/6] Changes on write_query_format_1() for datetime When logging the parameters of COM_STMT_EXECUTE , use sizeof(MYSQL_TIME) for these types: * MYSQL_TYPE_TIMESTAMP * MYSQL_TYPE_DATE * MYSQL_TYPE_TIME * MYSQL_TYPE_DATETIME Notes: * sizeof(MYSQL_TIME) is 40 * client library may use MYSQL_TYPE_DATETIME for all of these types --- lib/MySQL_Logger.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index a92bcd134..b89e4b776 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -64,7 +64,6 @@ struct BufferTypeInfo { // Helper lambda to convert binary data to a hex string. auto binaryToHex = [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { std::ostringstream oss; - oss << "0x"; const unsigned char* data = reinterpret_cast(bind->buffer); for (unsigned long i = 0; i < len; i++) { oss << std::setw(2) << std::setfill('0') << std::hex << (int)data[i]; @@ -758,6 +757,17 @@ uint64_t MySQL_Event::write_query_format_1(std::fstream *f) { const MYSQL_BIND *bind = meta->binds ? &meta->binds[i] : nullptr; if (bind != nullptr && !(meta->is_nulls && meta->is_nulls[i])) { unsigned long len = meta->lengths ? meta->lengths[i] : 0; + auto bt = bind->buffer_type; + switch (bt) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + len = sizeof(MYSQL_TIME); + break; + default: + break; + } // Use getValueForBind() to produce a string representation. auto[valType, convVal] = getValueForBind(bind, len); convertedValue = convVal; @@ -915,6 +925,17 @@ uint64_t MySQL_Event::write_query_format_1(std::fstream *f) { std::string convertedValue; if (bind && bind->buffer && !(meta->is_nulls && meta->is_nulls[i])) { unsigned long len = meta->lengths ? meta->lengths[i] : 0; + auto bt = bind->buffer_type; + switch (bt) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + len = sizeof(MYSQL_TIME); + break; + default: + break; + } auto[valType, convVal] = getValueForBind(bind, len); convertedValue = convVal; } else { From 97304ffa9169c85d526601a65a45c5caddcf6bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Fri, 25 Apr 2025 07:57:03 +0000 Subject: [PATCH 3/6] prepared statements logging: use HEX for STRING type When parameters are strings or blobs, they are always converted to hex --- lib/MySQL_Logger.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index b89e4b776..0465d6b8f 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -64,6 +64,7 @@ struct BufferTypeInfo { // Helper lambda to convert binary data to a hex string. auto binaryToHex = [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { std::ostringstream oss; + oss << "0x"; const unsigned char* data = reinterpret_cast(bind->buffer); for (unsigned long i = 0; i < len; i++) { oss << std::setw(2) << std::setfill('0') << std::hex << (int)data[i]; @@ -216,12 +217,12 @@ static const std::unordered_map bufferTypeInfoMap }, { MYSQL_TYPE_VAR_STRING, { "VAR_STRING", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - out.assign(reinterpret_cast(bind->buffer), len); + binaryToHex(bind, len, out); }} }, { MYSQL_TYPE_STRING, { "STRING", [](const MYSQL_BIND* bind, unsigned long len, std::string &out) { - out.assign(reinterpret_cast(bind->buffer), len); + binaryToHex(bind, len, out); }} }, { MYSQL_TYPE_GEOMETRY, From 9737e0630a10a143348f48ad602853882c829ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Fri, 25 Apr 2025 09:46:58 +0000 Subject: [PATCH 4/6] Add variable mysql-eventslog_stmt_parameters #4923 Variable mysql-eventslog_stmt_parameters configures the logging of parameters in prepared statements: - 0 : logging disabled - 1 : logging enabled --- include/MySQL_Thread.h | 1 + include/proxysql_structs.h | 2 ++ lib/MySQL_Logger.cpp | 8 +++++--- lib/MySQL_Thread.cpp | 4 ++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index cc769c86b..20eada2ba 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -533,6 +533,7 @@ class MySQL_Threads_Handler int eventslog_buffer_max_query_length; int eventslog_default_log; int eventslog_format; + int eventslog_stmt_parameters; char *auditlog_filename; int auditlog_filesize; // SSL related, proxy to server diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 983b121fa..4fbbc7884 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -1314,6 +1314,7 @@ __thread int mysql_thread___eventslog_table_memory_size; __thread int mysql_thread___eventslog_buffer_max_query_length; __thread int mysql_thread___eventslog_default_log; __thread int mysql_thread___eventslog_format; +__thread int mysql_thread___eventslog_stmt_parameters; /* variables used by audit log */ __thread char * mysql_thread___auditlog_filename; @@ -1618,6 +1619,7 @@ extern __thread int mysql_thread___eventslog_table_memory_size; extern __thread int mysql_thread___eventslog_buffer_max_query_length; extern __thread int mysql_thread___eventslog_default_log; extern __thread int mysql_thread___eventslog_format; +extern __thread int mysql_thread___eventslog_stmt_parameters; /* variables used by audit log */ extern __thread char * mysql_thread___auditlog_filename; diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index 0465d6b8f..7e294c002 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -741,7 +741,7 @@ uint64_t MySQL_Event::write_query_format_1(std::fstream *f) { // Validate Session and Statement Metadata: // The code checks whether the session pointer and the current query's statement metadata (stmt_meta) // are non-null to ensure that parameter details are available. - if (session != nullptr && session->CurrentQuery.stmt_meta != nullptr) { + if (mysql_thread___eventslog_stmt_parameters > 0 && session != nullptr && session->CurrentQuery.stmt_meta != nullptr) { stmt_execute_metadata_t *meta = session->CurrentQuery.stmt_meta; uint16_t num_params = meta->num_params; // Add bytes for encoded parameter count. @@ -878,7 +878,7 @@ uint64_t MySQL_Event::write_query_format_1(std::fstream *f) { // Validate Session and Statement Metadata: // The code checks whether the session pointer and the current query's statement metadata (stmt_meta) // are non-null to ensure that parameter details are available. - if (session != nullptr && session->CurrentQuery.stmt_meta != nullptr) { + if (mysql_thread___eventslog_stmt_parameters > 0 && session != nullptr && session->CurrentQuery.stmt_meta != nullptr) { stmt_execute_metadata_t *meta = session->CurrentQuery.stmt_meta; // Write the number of parameters. // Writing the Encoded Parameter Count: @@ -1096,7 +1096,9 @@ uint64_t MySQL_Event::write_query_format_2_json(std::fstream *f) { } if (et == PROXYSQL_COM_STMT_EXECUTE) { if (session != nullptr) { - extractStmtExecuteMetadataToJson(j); + if (mysql_thread___eventslog_stmt_parameters != 0) { + extractStmtExecuteMetadataToJson(j); + } } } diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 834569625..edb2d283d 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -358,6 +358,7 @@ static char * mysql_thread_variables_names[]= { (char *)"eventslog_buffer_max_query_length", (char *)"eventslog_default_log", (char *)"eventslog_format", + (char *)"eventslog_stmt_parameters", (char *)"auditlog_filename", (char *)"auditlog_filesize", //(char *)"default_charset", // removed in 2.0.13 . Obsoleted previously using MySQL_Variables instead @@ -1083,6 +1084,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.eventslog_buffer_max_query_length = 32*1024; variables.eventslog_default_log=0; variables.eventslog_format=1; + variables.eventslog_stmt_parameters=0; variables.auditlog_filename=strdup((char *)""); variables.auditlog_filesize=100*1024*1024; //variables.server_capabilities=CLIENT_FOUND_ROWS | CLIENT_PROTOCOL_41 | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION | CLIENT_CONNECT_WITH_DB; @@ -2285,6 +2287,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["eventslog_table_memory_size"] = make_tuple(&variables.eventslog_table_memory_size, 0, 8*1024*1024, false); VariablesPointers_int["eventslog_buffer_max_query_length"] = make_tuple(&variables.eventslog_buffer_max_query_length, 128, 32*1024*1024, false); VariablesPointers_int["eventslog_default_log"] = make_tuple(&variables.eventslog_default_log, 0, 1, false); + VariablesPointers_int["eventslog_stmt_parameters"] = make_tuple(&variables.eventslog_stmt_parameters, 0, 1, false); // various VariablesPointers_int["long_query_time"] = make_tuple(&variables.long_query_time, 0, 20*24*3600*1000, false); VariablesPointers_int["max_allowed_packet"] = make_tuple(&variables.max_allowed_packet, 8192, 1024*1024*1024, false); @@ -4194,6 +4197,7 @@ void MySQL_Thread::refresh_variables() { REFRESH_VARIABLE_INT(eventslog_buffer_max_query_length); REFRESH_VARIABLE_INT(eventslog_default_log); REFRESH_VARIABLE_INT(eventslog_format); + REFRESH_VARIABLE_INT(eventslog_stmt_parameters); REFRESH_VARIABLE_CHAR(eventslog_filename); REFRESH_VARIABLE_INT(auditlog_filesize); REFRESH_VARIABLE_CHAR(auditlog_filename); From 4e05596dddcac1d2fa06727039883592b0f47490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Fri, 25 Apr 2025 09:51:06 +0000 Subject: [PATCH 5/6] Updating test_ps_logging-t after enabling mysql-eventslog_stmt_parameters Now that variable `mysql-eventslog_stmt_parameters` is present and disabled by default, `mysql-eventslog_stmt_parameters` needs to be enabled to run test `test_ps_logging-t` --- test/tap/tests/test_ps_logging-t.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tap/tests/test_ps_logging-t.cpp b/test/tap/tests/test_ps_logging-t.cpp index 5534deb84..70e2ccf90 100644 --- a/test/tap/tests/test_ps_logging-t.cpp +++ b/test/tap/tests/test_ps_logging-t.cpp @@ -902,6 +902,7 @@ int main(int argc, char** argv) { } MYSQL_QUERY(admin, set_log_mode.c_str()); MYSQL_QUERY(admin, "SET mysql-eventslog_default_log=1"); + MYSQL_QUERY(admin, "SET mysql-eventslog_stmt_parameters=1"); MYSQL_QUERY(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); MYSQL_QUERY(proxy, "DROP TABLE IF EXISTS test.prepared_log_test"); MYSQL_QUERY(proxy, create_table_query_ext().c_str()); From 216b31feae61cce1d7a3b69f2a80894379c39572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Wed, 7 May 2025 23:24:21 +0000 Subject: [PATCH 6/6] Added new event_type PROXYSQL_METADATA When creating an event log in binary format, a metadata packet is written. The metadata is in JSON format. It currently only provide the ProxySQL version. For illustration purposes, tool eventlog_reader_to_json.cpp supports it too. This commit includes also some reformatting --- include/proxysql_structs.h | 3 +- lib/MySQL_Logger.cpp | 25 ++ tools/eventlog_reader_to_json.cpp | 571 +++++++++++++++--------------- 3 files changed, 321 insertions(+), 278 deletions(-) diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 4fbbc7884..d5530aa21 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -40,7 +40,8 @@ enum log_event_type { PROXYSQL_SQLITE_AUTH_CLOSE, PROXYSQL_SQLITE_AUTH_QUIT, PROXYSQL_COM_STMT_EXECUTE, - PROXYSQL_COM_STMT_PREPARE + PROXYSQL_COM_STMT_PREPARE, + PROXYSQL_METADATA }; enum cred_username_type { USERNAME_BACKEND, USERNAME_FRONTEND, USERNAME_NONE }; diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index 7e294c002..9a50442a0 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -524,6 +524,11 @@ uint64_t MySQL_Event::write(std::fstream *f, MySQL_Session *sess) { case PROXYSQL_SQLITE_AUTH_QUIT: write_auth(f, sess); break; + case PROXYSQL_METADATA: + if (mysql_thread___eventslog_format==1) { // format 1 , binary + total_bytes=write_query_format_1(f); + } + break; default: break; } @@ -1275,6 +1280,26 @@ void MySQL_Logger::events_open_log_unlocked() { try { events.logfile->open(filen , std::ios::out | std::ios::binary); proxy_info("Starting new mysql event log file %s\n", filen); + if (mysql_thread___eventslog_format == 1) { + // create a new event, type PROXYSQL_METADATA, that writes the ProxySQL version as part of the payload + json j = {}; + j["version"] = string(PROXYSQL_VERSION); + string msg = j.dump(); + MySQL_Event metaEvent( + PROXYSQL_METADATA, // event type for metadata + 0, // thread_id (0 for metadata events) + (char*)msg.c_str(), // using "metadata" as the username + (char*)"", // empty schemaname + 0, // start_time (current time) + 0, // end_time (current time) + 0, // query_digest not used for metadata + (char *)"", // client field holds the version string + 0, // length of version string + nullptr // no session associated + ); + metaEvent.set_query((char *)"",0); + metaEvent.write(events.logfile, nullptr); + } } catch (const std::ofstream::failure&) { proxy_error("Error creating new mysql event log file %s\n", filen); diff --git a/tools/eventlog_reader_to_json.cpp b/tools/eventlog_reader_to_json.cpp index 7b0dc4d82..35f483c7e 100644 --- a/tools/eventlog_reader_to_json.cpp +++ b/tools/eventlog_reader_to_json.cpp @@ -11,314 +11,331 @@ using json = nlohmann::json; // this is copied from proxysql_structs.h enum log_event_type { - PROXYSQL_COM_QUERY, - PROXYSQL_MYSQL_AUTH_OK, - PROXYSQL_MYSQL_AUTH_ERR, - PROXYSQL_MYSQL_AUTH_CLOSE, - PROXYSQL_MYSQL_AUTH_QUIT, - PROXYSQL_MYSQL_CHANGE_USER_OK, - PROXYSQL_MYSQL_CHANGE_USER_ERR, - PROXYSQL_MYSQL_INITDB, - PROXYSQL_ADMIN_AUTH_OK, - PROXYSQL_ADMIN_AUTH_ERR, - PROXYSQL_ADMIN_AUTH_CLOSE, - PROXYSQL_ADMIN_AUTH_QUIT, - PROXYSQL_SQLITE_AUTH_OK, - PROXYSQL_SQLITE_AUTH_ERR, - PROXYSQL_SQLITE_AUTH_CLOSE, - PROXYSQL_SQLITE_AUTH_QUIT, - PROXYSQL_COM_STMT_EXECUTE, - PROXYSQL_COM_STMT_PREPARE + PROXYSQL_COM_QUERY, + PROXYSQL_MYSQL_AUTH_OK, + PROXYSQL_MYSQL_AUTH_ERR, + PROXYSQL_MYSQL_AUTH_CLOSE, + PROXYSQL_MYSQL_AUTH_QUIT, + PROXYSQL_MYSQL_CHANGE_USER_OK, + PROXYSQL_MYSQL_CHANGE_USER_ERR, + PROXYSQL_MYSQL_INITDB, + PROXYSQL_ADMIN_AUTH_OK, + PROXYSQL_ADMIN_AUTH_ERR, + PROXYSQL_ADMIN_AUTH_CLOSE, + PROXYSQL_ADMIN_AUTH_QUIT, + PROXYSQL_SQLITE_AUTH_OK, + PROXYSQL_SQLITE_AUTH_ERR, + PROXYSQL_SQLITE_AUTH_CLOSE, + PROXYSQL_SQLITE_AUTH_QUIT, + PROXYSQL_COM_STMT_EXECUTE, + PROXYSQL_COM_STMT_PREPARE, + PROXYSQL_METADATA }; // Decodes a MySQL length-encoded integer from the stream. // Returns true if decoding was successful. bool decodeInt(std::istream &in, uint64_t &val) { - unsigned char first; - if (!in.read(reinterpret_cast(&first), 1)) - return false; - if (first < 251) { - val = first; - return true; - } else if (first == 0xfc) { - uint16_t tmp; - if (!in.read(reinterpret_cast(&tmp), sizeof(tmp))) - return false; - val = tmp; - return true; - } else if (first == 0xfd) { - uint32_t tmp = 0; - char buf[3] = {0}; - if (!in.read(buf, 3)) - return false; - tmp = (unsigned char)buf[0] | - ((unsigned char)buf[1] << 8) | - ((unsigned char)buf[2] << 16); - val = tmp; - return true; - } else if (first == 0xfe) { - uint64_t tmp; - if (!in.read(reinterpret_cast(&tmp), sizeof(tmp))) - return false; - val = tmp; - return true; - } - return false; + unsigned char first; + if (!in.read(reinterpret_cast(&first), 1)) + return false; + if (first < 251) { + val = first; + return true; + } else if (first == 0xfc) { + uint16_t tmp; + if (!in.read(reinterpret_cast(&tmp), sizeof(tmp))) + return false; + val = tmp; + return true; + } else if (first == 0xfd) { + uint32_t tmp = 0; + char buf[3] = {0}; + if (!in.read(buf, 3)) + return false; + tmp = (unsigned char)buf[0] | + ((unsigned char)buf[1] << 8) | + ((unsigned char)buf[2] << 16); + val = tmp; + return true; + } else if (first == 0xfe) { + uint64_t tmp; + if (!in.read(reinterpret_cast(&tmp), sizeof(tmp))) + return false; + val = tmp; + return true; + } + return false; } // Reads a fixed-size value from the stream. template bool readValue(std::istream &in, T &value) { - in.read(reinterpret_cast(&value), sizeof(T)); - return in.good(); + in.read(reinterpret_cast(&value), sizeof(T)); + return in.good(); } // Reads raw bytes into a string. std::string readString(std::istream &in, uint64_t length) { - std::vector buf(length); - if (!in.read(buf.data(), length)) - return ""; - return std::string(buf.begin(), buf.end()); + std::vector buf(length); + if (!in.read(buf.data(), length)) + return ""; + return std::string(buf.begin(), buf.end()); } json parseEvent(std::istream &in, bool verbose = true) { - json j; - // Read total_bytes (first 8 bytes) - uint64_t total_bytes = 0; - if (!readValue(in, total_bytes)) - throw std::runtime_error("Cannot read total_bytes"); - // j["total_bytes"] = total_bytes; // optional - if (verbose == true) { - std::cout << "total_bytes: " << total_bytes << std::endl; - } + json j; + // Read total_bytes (first 8 bytes) + uint64_t total_bytes = 0; + if (!readValue(in, total_bytes)) + throw std::runtime_error("Cannot read total_bytes"); + // j["total_bytes"] = total_bytes; // optional + if (verbose == true) { + std::cout << "total_bytes: " << total_bytes << std::endl; + } - // Read event type (1 byte) - char etb; - enum log_event_type et; - std::string et_name = ""; + // Read event type (1 byte) + char etb; + enum log_event_type et; + std::string et_name = ""; - if (!readValue(in, etb)) - throw std::runtime_error("Cannot read event type"); - et = static_cast(etb); - switch (et) { - case PROXYSQL_COM_STMT_EXECUTE: - et_name="COM_STMT_EXECUTE"; - break; - case PROXYSQL_COM_STMT_PREPARE: - et_name="COM_STMT_PREPARE"; - break; - default: - et_name="COM_QUERY"; - break; - } + if (!readValue(in, etb)) + throw std::runtime_error("Cannot read event type"); + et = static_cast(etb); + switch (et) { + case PROXYSQL_COM_STMT_EXECUTE: + et_name="COM_STMT_EXECUTE"; + break; + case PROXYSQL_COM_STMT_PREPARE: + et_name="COM_STMT_PREPARE"; + break; + case PROXYSQL_METADATA: + et_name="METADATA"; + break; + default: + et_name="COM_QUERY"; + break; + } - j["event_type"] = et_name; - if (verbose == true) { - std::cout << "event_type: " << static_cast(et) << std::endl; - } + j["event_type"] = et_name; + if (verbose == true) { + std::cout << "event_type: " << static_cast(et) << std::endl; + } - // Read thread_id - uint64_t thread_id = 0; - if (!decodeInt(in, thread_id)) - throw std::runtime_error("Error decoding thread_id"); - j["thread_id"] = thread_id; - if (verbose == true) { - std::cout << "thread_id: " << thread_id << std::endl; - } + // Read thread_id + uint64_t thread_id = 0; + if (!decodeInt(in, thread_id)) + throw std::runtime_error("Error decoding thread_id"); + if (et != PROXYSQL_METADATA) { + j["thread_id"] = thread_id; + if (verbose == true) { + std::cout << "thread_id: " << thread_id << std::endl; + } + } - // Username: first read its length then the raw string. - uint64_t username_len = 0; - if (!decodeInt(in, username_len)) - throw std::runtime_error("Error decoding username length"); - std::string username = readString(in, username_len); - j["username"] = username; - if (verbose == true) { - std::cout << "username: " << username << std::endl; - } + // Username: first read its length then the raw string. + uint64_t username_len = 0; + if (!decodeInt(in, username_len)) + throw std::runtime_error("Error decoding username length"); + std::string username = readString(in, username_len); + if (et != PROXYSQL_METADATA) { + j["username"] = username; + if (verbose == true) { + std::cout << "username: " << username << std::endl; + } + } else { + json metadata = json::parse(username); + j["metadata"] = metadata; + } - // Schemaname - uint64_t schemaname_len = 0; - if (!decodeInt(in, schemaname_len)) - throw std::runtime_error("Error decoding schemaname length"); - std::string schemaname = readString(in, schemaname_len); - j["schemaname"] = schemaname; - if (verbose == true) { - std::cout << "schemaname: " << schemaname << std::endl; - } + // Schemaname + uint64_t schemaname_len = 0; + if (!decodeInt(in, schemaname_len)) + throw std::runtime_error("Error decoding schemaname length"); + std::string schemaname = readString(in, schemaname_len); + if (et != PROXYSQL_METADATA) { + j["schemaname"] = schemaname; + if (verbose == true) { + std::cout << "schemaname: " << schemaname << std::endl; + } + } + // Client string + uint64_t client_len = 0; + if (!decodeInt(in, client_len)) + throw std::runtime_error("Error decoding client length"); + std::string client = readString(in, client_len); + if (et != PROXYSQL_METADATA) { + j["client"] = client; + if (verbose == true) { + std::cout << "client: " << client << std::endl; + } + } + // Host id (hid) + uint64_t hid = 0; + if (!decodeInt(in, hid)) + throw std::runtime_error("Error decoding hid"); + if (et != PROXYSQL_METADATA) { + j["hid"] = hid; + if (verbose == true) { + std::cout << "hid: " << hid << std::endl; + } + } + // If hid != UINT64_MAX then read server string. + if (hid != UINT64_MAX) { + uint64_t server_len = 0; + if (!decodeInt(in, server_len)) + throw std::runtime_error("Error decoding server length"); + std::string server = readString(in, server_len); + j["server"] = server; + if (verbose == true) { + std::cout << "server: " << server << std::endl; + } + } - // Client string - uint64_t client_len = 0; - if (!decodeInt(in, client_len)) - throw std::runtime_error("Error decoding client length"); - std::string client = readString(in, client_len); - j["client"] = client; - if (verbose == true) { - std::cout << "client: " << client << std::endl; - } + // Start time and End time + uint64_t start_time = 0, end_time = 0; + if (!decodeInt(in, start_time)) + throw std::runtime_error("Error decoding start_time"); + if (!decodeInt(in, end_time)) + throw std::runtime_error("Error decoding end_time"); + if (et != PROXYSQL_METADATA) { + j["start_time"] = start_time; + j["end_time"] = end_time; + if (verbose == true) { + std::cout << "start_time: " << start_time << ", end_time: " << end_time << std::endl; + } + } + // Client statement id (only for COM_STMT_PREPARE/EXECUTE events) + uint64_t client_stmt_id = 0; + if (et == PROXYSQL_COM_STMT_PREPARE || et == PROXYSQL_COM_STMT_EXECUTE) { + if (!decodeInt(in, client_stmt_id)) + throw std::runtime_error("Error decoding client_stmt_id"); + j["client_stmt_id"] = client_stmt_id; + if (verbose == true) { + std::cout << "client_stmt_id: " << client_stmt_id << std::endl; + } + } - // Host id (hid) - uint64_t hid = 0; - if (!decodeInt(in, hid)) - throw std::runtime_error("Error decoding hid"); - j["hid"] = hid; - if (verbose == true) { - std::cout << "hid: " << hid << std::endl; - } + // affected_rows, last_insert_id, rows_sent, query_digest + uint64_t affected_rows, last_insert_id, rows_sent, query_digest; + if (!decodeInt(in, affected_rows)) + throw std::runtime_error("Error decoding affected_rows"); + if (!decodeInt(in, last_insert_id)) + throw std::runtime_error("Error decoding last_insert_id"); + if (!decodeInt(in, rows_sent)) + throw std::runtime_error("Error decoding rows_sent"); + if (!decodeInt(in, query_digest)) + throw std::runtime_error("Error decoding query_digest"); + if (et != PROXYSQL_METADATA) { + j["affected_rows"] = affected_rows; + j["last_insert_id"] = last_insert_id; + j["rows_sent"] = rows_sent; + j["query_digest"] = query_digest; + if (verbose == true) { + std::cout << "affected_rows: " << affected_rows + << ", last_insert_id: " << last_insert_id + << ", rows_sent: " << rows_sent + << ", query_digest: " << query_digest << std::endl; + } + } + // Query: first read its length then raw query bytes. + uint64_t query_len = 0; + if (!decodeInt(in, query_len)) + throw std::runtime_error("Error decoding query length"); + std::string query = readString(in, query_len); + if (et != PROXYSQL_METADATA) { + j["query"] = query; + if (verbose == true) { + std::cout << "query: " << query << std::endl; + } + } + // If the event is COM_STMT_EXECUTE then read parameter block. + if (et == PROXYSQL_COM_STMT_EXECUTE) { + // Read parameters count + uint64_t num_params; + if (!decodeInt(in, num_params)) + throw std::runtime_error("Error decoding parameter count"); + if (verbose == true) { + std::cout << "num_params: " << num_params << std::endl; + } - // If hid != UINT64_MAX then read server string. - if (hid != UINT64_MAX) { - uint64_t server_len = 0; - if (!decodeInt(in, server_len)) - throw std::runtime_error("Error decoding server length"); - std::string server = readString(in, server_len); - j["server"] = server; - if (verbose == true) { - std::cout << "server: " << server << std::endl; - } - } + json jparams = json::array(); + // Calculate null bitmap size + size_t bitmap_size = (num_params + 7) / 8; + std::vector null_bitmap(bitmap_size); + if (!in.read(reinterpret_cast(null_bitmap.data()), bitmap_size)) + throw std::runtime_error("Error reading null bitmap"); + if (verbose == true) { + std::cout << "null_bitmap size: " << bitmap_size << std::endl; + } - // Start time and End time - uint64_t start_time = 0, end_time = 0; - if (!decodeInt(in, start_time)) - throw std::runtime_error("Error decoding start_time"); - if (!decodeInt(in, end_time)) - throw std::runtime_error("Error decoding end_time"); - j["start_time"] = start_time; - j["end_time"] = end_time; - if (verbose == true) { - std::cout << "start_time: " << start_time << ", end_time: " << end_time << std::endl; - } - - // Client statement id (only for COM_STMT_PREPARE/EXECUTE events) - uint64_t client_stmt_id = 0; - if (et == PROXYSQL_COM_STMT_PREPARE || et == PROXYSQL_COM_STMT_EXECUTE) { - if (!decodeInt(in, client_stmt_id)) - throw std::runtime_error("Error decoding client_stmt_id"); - j["client_stmt_id"] = client_stmt_id; - if (verbose == true) { - std::cout << "client_stmt_id: " << client_stmt_id << std::endl; - } - } - - // affected_rows, last_insert_id, rows_sent, query_digest - uint64_t affected_rows, last_insert_id, rows_sent, query_digest; - if (!decodeInt(in, affected_rows)) - throw std::runtime_error("Error decoding affected_rows"); - if (!decodeInt(in, last_insert_id)) - throw std::runtime_error("Error decoding last_insert_id"); - if (!decodeInt(in, rows_sent)) - throw std::runtime_error("Error decoding rows_sent"); - if (!decodeInt(in, query_digest)) - throw std::runtime_error("Error decoding query_digest"); - j["affected_rows"] = affected_rows; - j["last_insert_id"] = last_insert_id; - j["rows_sent"] = rows_sent; - j["query_digest"] = query_digest; - if (verbose == true) { - std::cout << "affected_rows: " << affected_rows - << ", last_insert_id: " << last_insert_id - << ", rows_sent: " << rows_sent - << ", query_digest: " << query_digest << std::endl; - } - - // Query: first read its length then raw query bytes. - uint64_t query_len = 0; - if (!decodeInt(in, query_len)) - throw std::runtime_error("Error decoding query length"); - std::string query = readString(in, query_len); - j["query"] = query; - if (verbose == true) { - std::cout << "query: " << query << std::endl; - } - - // If the event is COM_STMT_EXECUTE then read parameter block. - if (et == PROXYSQL_COM_STMT_EXECUTE) { - // Read parameters count - uint64_t num_params; - if (!decodeInt(in, num_params)) - throw std::runtime_error("Error decoding parameter count"); - if (verbose == true) { - std::cout << "num_params: " << num_params << std::endl; - } - - json jparams = json::array(); - // Calculate null bitmap size - size_t bitmap_size = (num_params + 7) / 8; - std::vector null_bitmap(bitmap_size); - if (!in.read(reinterpret_cast(null_bitmap.data()), bitmap_size)) - throw std::runtime_error("Error reading null bitmap"); - if (verbose == true) { - std::cout << "null_bitmap size: " << bitmap_size << std::endl; - } - - for (uint16_t i = 0; i < num_params; i++) { - json jparam; - // Read parameter type (2 bytes) - uint16_t param_type; - if (!readValue(in, param_type)) - throw std::runtime_error("Error reading parameter type"); - jparam["type"] = param_type; - if (verbose == true) { - std::cout << "param[" << i << "] type: " << param_type << std::endl; - } - // Check if parameter is NULL using null bitmap. - bool isNull = false; - if (i < num_params) { - isNull = (null_bitmap[i / 8] & (1 << (i % 8))) != 0; - } - if (isNull) { - jparam["value"] = nullptr; - if (verbose == true) { - std::cout << "param[" << i << "] is NULL" << std::endl; - } - } else { - // Read encoded length of parameter value - uint64_t param_value_len; - if (!decodeInt(in, param_value_len)) - throw std::runtime_error("Error decoding param value length"); - if (verbose == true) { - std::cout << "param[" << i << "] value length: " << param_value_len << std::endl; - } - // Read raw parameter value bytes. - std::string param_value = readString(in, param_value_len); - jparam["value"] = param_value; - if (verbose == true) { - std::cout << "param[" << i << "] value: " << param_value << std::endl; - } - } - jparams.push_back(jparam); - } - j["parameters"] = jparams; - } - return j; + for (uint16_t i = 0; i < num_params; i++) { + json jparam; + // Read parameter type (2 bytes) + uint16_t param_type; + if (!readValue(in, param_type)) + throw std::runtime_error("Error reading parameter type"); + jparam["type"] = param_type; + if (verbose == true) { + std::cout << "param[" << i << "] type: " << param_type << std::endl; + } + // Check if parameter is NULL using null bitmap. + bool isNull = false; + if (i < num_params) { + isNull = (null_bitmap[i / 8] & (1 << (i % 8))) != 0; + } + if (isNull) { + jparam["value"] = nullptr; + if (verbose == true) { + std::cout << "param[" << i << "] is NULL" << std::endl; + } + } else { + // Read encoded length of parameter value + uint64_t param_value_len; + if (!decodeInt(in, param_value_len)) + throw std::runtime_error("Error decoding param value length"); + if (verbose == true) { + std::cout << "param[" << i << "] value length: " << param_value_len << std::endl; + } + // Read raw parameter value bytes. + std::string param_value = readString(in, param_value_len); + jparam["value"] = param_value; + if (verbose == true) { + std::cout << "param[" << i << "] value: " << param_value << std::endl; + } + } + jparams.push_back(jparam); + } + j["parameters"] = jparams; + } + return j; } int main(int argc, char* argv[]) { - if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " " << std::endl; - return 1; - } - std::ifstream infile(argv[1], std::ios::binary); - if (!infile.is_open()) { - std::cerr << "Failed to open file: " << argv[1] << std::endl; - return 1; - } + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + std::ifstream infile(argv[1], std::ios::binary); + if (!infile.is_open()) { + std::cerr << "Failed to open file: " << argv[1] << std::endl; + return 1; + } - //const bool verbose = true; - const bool verbose = false; - json jEvents = json::array(); - // Read events until end-of-file. - while (infile.peek() != EOF) { - try { - json jEvent = parseEvent(infile, verbose); - jEvents.push_back(jEvent); - } catch (const std::exception &ex) { - // Break on error or end of file. - break; - } - } - std::cout << jEvents.dump(4) << std::endl; - return 0; + //const bool verbose = true; + const bool verbose = false; + json jEvents = json::array(); + // Read events until end-of-file. + while (infile.peek() != EOF) { + try { + json jEvent = parseEvent(infile, verbose); + jEvents.push_back(jEvent); + } catch (const std::exception &ex) { + // Break on error or end of file. + break; + } + } + std::cout << jEvents.dump(4) << std::endl; + return 0; }