@@ -120,9 +120,6 @@ extern struct MHD_Daemon *Admin_HTTP_Server;
120120
121121extern ProxySQL_Statistics *GloProxyStats;
122122
123- template <enum SERVER_TYPE >
124- int ProxySQL_Test___PurgeDigestTable (bool async_purge, bool parallel, char **msg);
125-
126123extern char *ssl_key_fp;
127124extern char *ssl_cert_fp;
128125extern char *ssl_ca_fp;
@@ -327,6 +324,17 @@ const std::vector<std::string> LOAD_COREDUMP_FROM_MEMORY = {
327324 " LOAD COREDUMP TO RUNTIME" ,
328325 " LOAD COREDUMP TO RUN" };
329326
327+ const std::vector<std::string> CMD_PREFIX_PURGE_QUERY_DIGESTS = {
328+ " PURGE TABLE stats.stats_mysql_query_digest TO " ,
329+ " PURGE TABLE stats_mysql_query_digest TO " ,
330+ " PURGE stats.stats_mysql_query_digest TO " ,
331+ " PURGE stats_mysql_query_digest TO " ,
332+ " PURGE TABLE stats.stats_pgsql_query_digest TO " ,
333+ " PURGE TABLE stats_pgsql_query_digest TO " ,
334+ " PURGE stats.stats_pgsql_query_digest TO " ,
335+ " PURGE stats_pgsql_query_digest TO " ,
336+ };
337+
330338extern unordered_map<string,std::tuple<string, vector<string>, vector<string>>> load_save_disk_commands;
331339
332340// Helper function: Extract pattern from c.relname OPERATOR or LIKE clause
@@ -518,6 +526,17 @@ bool is_admin_command_or_alias(const std::vector<std::string>& cmds, char *query
518526 return false ;
519527}
520528
529+ const char * match_command_prefix (const std::vector<std::string>& cmd_prefix, char *query, int query_len) {
530+ for (auto &prefix : cmd_prefix) {
531+ if ((unsigned int ) query_len >= prefix.length ()
532+ && !strncasecmp (prefix.c_str (), query, prefix.length ()))
533+ {
534+ return prefix.c_str ();
535+ }
536+ }
537+
538+ return nullptr ;
539+ }
521540
522541
523542template <typename S>
@@ -553,6 +572,38 @@ bool FlushCommandWrapper(S* sess, const string& modname, char *query_no_space, i
553572 return false ;
554573}
555574
575+ std::tuple<bool , enum SERVER_TYPE , time_t > parse_command_purge_query_digests (char *query, int query_len) {
576+ bool match = false ;
577+ enum SERVER_TYPE server_type = SERVER_TYPE_MYSQL ;
578+ time_t last_seen = 0 ;
579+
580+ const char *prefix = match_command_prefix (CMD_PREFIX_PURGE_QUERY_DIGESTS , query, query_len);
581+ if (prefix) {
582+ match = true ;
583+
584+ if (strstr (prefix, " _pgsql_" ) != nullptr ) {
585+ server_type = SERVER_TYPE_PGSQL ;
586+ }
587+
588+ // parse timestamp
589+ mf_unique_ptr<char > ts_str (strdup (query + strlen (prefix)));
590+ char *ts_end = nullptr ;
591+ long long ts = strtoll (trim_spaces_in_place (ts_str.get ()), &ts_end, 10 );
592+
593+ // ts_str should only contain digits and respresent a valid timestamp
594+ if ((*ts_end == 0 ) && (ts > 0 )) {
595+ last_seen = realtime_to_monotonic_time (ts);
596+ if (last_seen <= 0 ) {
597+ // valid timestamp, but older than the monotonic clock epoch (system
598+ // boot): no entry can match, make the command a successful no-op
599+ last_seen = 1 ;
600+ }
601+ }
602+ }
603+
604+ return std::make_tuple (match, server_type, last_seen);
605+ }
606+
556607template <typename S>
557608bool admin_handler_command_kill_pgsql_connection (uint32_t session_thd_id, S* sess, ProxySQL_Admin* pa) {
558609 proxy_debug (PROXY_DEBUG_ADMIN , 4 , " Trying to kill pgsql session %u\n " , session_thd_id);
@@ -3442,10 +3493,8 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) {
34423493 SPA ->admindb ->execute (" DELETE FROM stats.stats_mysql_query_digest_reset" );
34433494 SPA ->vacuum_stats (true );
34443495 // purge the digest map, asynchronously, in single thread
3445- char *msg = NULL ;
3446- int r1 = ProxySQL_Test___PurgeDigestTable<SERVER_TYPE_MYSQL >(true , false , &msg);
3447- SPA ->send_ok_msg_to_client (sess, msg, r1, query_no_space);
3448- free (msg);
3496+ int r1 = GloMyQPro->purge_query_digests (true , false );
3497+ SPA ->send_ok_msg_to_client (sess, NULL , r1, query_no_space);
34493498 run_query=false ;
34503499 goto __run_query;
34513500 }
@@ -3478,16 +3527,45 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) {
34783527 SPA ->admindb ->execute (" DELETE FROM stats.stats_pgsql_query_digest_reset" );
34793528 SPA ->vacuum_stats (true );
34803529 // purge the digest map, asynchronously, in single thread
3481- char * msg = NULL ;
3482- int r1 = ProxySQL_Test___PurgeDigestTable<SERVER_TYPE_PGSQL >(true , false , &msg);
3483- SPA ->send_ok_msg_to_client (sess, msg, r1, query_no_space);
3484- free (msg);
3530+ int r1 = GloPgQPro->purge_query_digests (true , false );
3531+ SPA ->send_ok_msg_to_client (sess, NULL , r1, query_no_space);
34853532 run_query = false ;
34863533 goto __run_query;
34873534 }
34883535 }
34893536 }
34903537 }
3538+
3539+ // handles 'PURGE stats_mysql_query_digest TO <value>'.
3540+ // any entry in stats_mysql_query_digest where last_seen is less than <value> will be deleted.
3541+ if (!strncasecmp (" PURGE " , query_no_space, strlen (" PURGE " ))
3542+ && sess->session_type == PROXYSQL_SESSION_ADMIN
3543+ ) {
3544+ auto result = parse_command_purge_query_digests (query_no_space, query_no_space_length);
3545+ bool match = std::get<0 >(result);
3546+
3547+ if (match == true ) {
3548+ int ret = 0 ;
3549+ enum SERVER_TYPE type = std::get<1 >(result);
3550+ time_t last_seen = std::get<2 >(result);
3551+
3552+ if (last_seen > 0 ) {
3553+ if (type == SERVER_TYPE_MYSQL ) {
3554+ ret = GloMyQPro->purge_query_digests (true , false , last_seen);
3555+ } else if (type == SERVER_TYPE_PGSQL ) {
3556+ ret = GloPgQPro->purge_query_digests (true , false , last_seen);
3557+ }
3558+
3559+ pa->send_ok_msg_to_client (sess, NULL , ret, query_no_space);
3560+ } else {
3561+ pa->send_error_msg_to_client (sess, " Invalid timestamp" );
3562+ }
3563+
3564+ run_query = false ;
3565+ goto __run_query;
3566+ }
3567+ }
3568+
34913569#ifdef DEBUG
34923570 /* *
34933571 * @brief Handles the 'PROXYSQL_SIMULATOR' command. Performing the operation specified in the payload
0 commit comments