From 7b6d29be5f6c4d226592fc0de2b5edbda5bcdea7 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Sat, 29 Aug 2026 16:19:03 +0530 Subject: [PATCH] perf: optimize ParserSQL digest and classification overhead Signed-off-by: Snehil Shah --- include/Query_Processor_ParserSQL.h | 19 +++++++++++ include/proxysql_structs.h | 1 + lib/MySQL_Query_Processor.cpp | 4 +++ lib/PgSQL_Query_Processor.cpp | 4 +++ lib/Query_Processor.cpp | 1 + lib/Query_Processor_ParserSQL.cpp | 52 +++++++++++++++++++++++------ 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/include/Query_Processor_ParserSQL.h b/include/Query_Processor_ParserSQL.h index 58e473982c..6f21201c96 100644 --- a/include/Query_Processor_ParserSQL.h +++ b/include/Query_Processor_ParserSQL.h @@ -106,6 +106,25 @@ enum MYSQL_COM_QUERY_command parsersql_command_type_mysql(const char* query, int */ enum PGSQL_QUERY_command parsersql_command_type_pgsql(const char* query, int query_length); +/** + * @brief Maps a ParserSQL StmtType to a `MYSQL_COM_QUERY_command`. + * + * @param stmt_type A `StmtType` value (see sql_parser/common.h), as an int. + * @return The corresponding command type. + */ +enum MYSQL_COM_QUERY_command parsersql_stmt_type_to_mysql_command(int stmt_type); + +/** + * @brief Maps a ParserSQL StmtType to a `PGSQL_QUERY_command`. + * + * Identical to parsersql_stmt_type_to_mysql_command() but targets the + * PostgreSQL command enum. + * + * @param stmt_type A `StmtType` value (see sql_parser/common.h), as an int. + * @return The corresponding command type. + */ +enum PGSQL_QUERY_command parsersql_stmt_type_to_pgsql_command(int stmt_type); + /** * @brief Parses a MySQL SET statement into variable-value pairs. * diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 07f899c094..0e58a7eb55 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -899,6 +899,7 @@ struct __SQP_query_parser_t { char *digest_text; char *first_comment; char *query_prefix; + int parsersql_stmt_type; }; struct _PtrSize_t { diff --git a/lib/MySQL_Query_Processor.cpp b/lib/MySQL_Query_Processor.cpp index 7ecbff6be4..8a6e295191 100644 --- a/lib/MySQL_Query_Processor.cpp +++ b/lib/MySQL_Query_Processor.cpp @@ -164,6 +164,10 @@ enum MYSQL_COM_QUERY_command MySQL_Query_Processor::query_parser_command_type(SQ enum MYSQL_COM_QUERY_command ret = MYSQL_COM_QUERY_UNKNOWN; if (mysql_thread___query_processor_parser == 1) { + if (qp->parsersql_stmt_type >= 0) { + // already classified during the digest parse + return parsersql_stmt_type_to_mysql_command(qp->parsersql_stmt_type); + } if (text) { return parsersql_command_type_mysql(text, strlen(text)); // NOSONAR } diff --git a/lib/PgSQL_Query_Processor.cpp b/lib/PgSQL_Query_Processor.cpp index 86451685ca..5930815a97 100644 --- a/lib/PgSQL_Query_Processor.cpp +++ b/lib/PgSQL_Query_Processor.cpp @@ -669,6 +669,10 @@ enum PGSQL_QUERY_command PgSQL_Query_Processor::query_parser_command_type(SQP_pa enum PGSQL_QUERY_command ret = PGSQL_QUERY_UNKNOWN; if (pgsql_thread___query_processor_parser == 1) { + if (qp->parsersql_stmt_type >= 0) { + // already classified during the digest parse + return parsersql_stmt_type_to_pgsql_command(qp->parsersql_stmt_type); + } if (text) { return parsersql_command_type_pgsql(text, strlen(text)); // NOSONAR } diff --git a/lib/Query_Processor.cpp b/lib/Query_Processor.cpp index 7b327afc69..2e9ad80da3 100644 --- a/lib/Query_Processor.cpp +++ b/lib/Query_Processor.cpp @@ -2328,6 +2328,7 @@ void Query_Processor::query_parser_init(SQP_par_t *qp, const char *q qp->digest_text=NULL; qp->first_comment=NULL; qp->query_prefix=NULL; + qp->parsersql_stmt_type=-1; if (GET_THREAD_VARIABLE(query_digests)) { if (GET_THREAD_VARIABLE(query_processor_parser) == 1) { qp_digest_parsersql(qp, query, query_length); diff --git a/lib/Query_Processor_ParserSQL.cpp b/lib/Query_Processor_ParserSQL.cpp index 4907ccadd1..605ad7dac9 100644 --- a/lib/Query_Processor_ParserSQL.cpp +++ b/lib/Query_Processor_ParserSQL.cpp @@ -413,26 +413,39 @@ void parsersql_digest_init_mysql(SQP_par_t* qp, const char* query, int query_len qp->first_comment = NULL; qp->query_prefix = NULL; qp->digest = 0; + qp->parsersql_stmt_type = -1; auto result = tl_mysql_parser.parse(query, query_length); if (result.status == ParseResult::OK || result.status == ParseResult::PARTIAL) { - std::string normalized; + // The arena holds 'normalized' until reset() below, so it can be hashed and copied out directly. + const char* normalized = NULL; + size_t normalized_len = 0; if (result.ast) { // Tier 1: full AST available — use Emitter in DIGEST mode Emitter emitter(tl_mysql_parser.arena(), EmitMode::DIGEST); emitter.emit(result.ast); StringRef ref = emitter.result(); - normalized.assign(ref.ptr, ref.len); + normalized = ref.ptr; + normalized_len = ref.len; } else { // Tier 2: token-level fallback for statements without full AST support Digest digest(tl_mysql_parser.arena()); DigestResult dr = digest.compute(query, query_length); - normalized.assign(dr.normalized.ptr, dr.normalized.len); + normalized = dr.normalized.ptr; + normalized_len = dr.normalized.len; } - qp->digest_text = strdup(normalized.c_str()); // SpookyHash is preserved for backward compatibility with existing digest stats - qp->digest = SpookyHash::Hash64(normalized.c_str(), normalized.size(), 0); + qp->digest = SpookyHash::Hash64(normalized, normalized_len, 0); + qp->parsersql_stmt_type = static_cast(result.stmt_type); + // Reuse qp->buf when it fits, as query_parser_free() expects for the legacy tokenizer. + if (normalized_len < QUERY_DIGEST_BUF) { + memcpy(qp->buf, normalized, normalized_len); + qp->buf[normalized_len] = '\0'; + qp->digest_text = qp->buf; + } else { + qp->digest_text = strndup(normalized, normalized_len); + } } tl_mysql_parser.reset(); @@ -444,23 +457,34 @@ void parsersql_digest_init_pgsql(SQP_par_t* qp, const char* query, int query_len qp->first_comment = NULL; qp->query_prefix = NULL; qp->digest = 0; + qp->parsersql_stmt_type = -1; auto result = tl_pgsql_parser.parse(query, query_length); if (result.status == ParseResult::OK || result.status == ParseResult::PARTIAL) { - std::string normalized; + const char* normalized = NULL; + size_t normalized_len = 0; if (result.ast) { Emitter emitter(tl_pgsql_parser.arena(), EmitMode::DIGEST); emitter.emit(result.ast); StringRef ref = emitter.result(); - normalized.assign(ref.ptr, ref.len); + normalized = ref.ptr; + normalized_len = ref.len; } else { Digest digest(tl_pgsql_parser.arena()); DigestResult dr = digest.compute(query, query_length); - normalized.assign(dr.normalized.ptr, dr.normalized.len); + normalized = dr.normalized.ptr; + normalized_len = dr.normalized.len; + } + qp->digest = SpookyHash::Hash64(normalized, normalized_len, 0); + qp->parsersql_stmt_type = static_cast(result.stmt_type); + if (normalized_len < QUERY_DIGEST_BUF) { + memcpy(qp->buf, normalized, normalized_len); + qp->buf[normalized_len] = '\0'; + qp->digest_text = qp->buf; + } else { + qp->digest_text = strndup(normalized, normalized_len); } - qp->digest_text = strdup(normalized.c_str()); - qp->digest = SpookyHash::Hash64(normalized.c_str(), normalized.size(), 0); } tl_pgsql_parser.reset(); @@ -565,6 +589,14 @@ enum MYSQL_COM_QUERY_command parsersql_command_type_mysql(const char* query, int return MYSQL_COM_QUERY_UNKNOWN; } +enum MYSQL_COM_QUERY_command parsersql_stmt_type_to_mysql_command(int stmt_type) { + return stmt_type_to_mysql_command(static_cast(stmt_type)); +} + +enum PGSQL_QUERY_command parsersql_stmt_type_to_pgsql_command(int stmt_type) { + return stmt_type_to_pgsql_command(static_cast(stmt_type)); +} + enum PGSQL_QUERY_command parsersql_command_type_pgsql(const char* query, int query_length) { auto result = tl_pgsql_parser.parse(query, query_length); tl_pgsql_parser.reset();