From aaa02bb4006fe3cec223e18dc5066c8a5fd5e0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Sun, 22 Mar 2026 11:00:14 +0100 Subject: [PATCH] Extract hostgroup routing decision logic (Phase 3.5, #5493) New files: - include/HostgroupRouting.h: HostgroupRoutingDecision struct + resolve_hostgroup_routing() declaration - lib/HostgroupRouting.cpp: implementation mirroring the routing block in get_pkts_from_client() (~lines 5340-5377) Logic covers: QP destination override, transaction affinity, hostgroup lock acquisition/enforcement, error on HG mismatch. Identical for both MySQL and PgSQL protocols. --- include/HostgroupRouting.h | 52 +++++++++++++++++++++++++++++++++ lib/HostgroupRouting.cpp | 60 ++++++++++++++++++++++++++++++++++++++ lib/Makefile | 1 + 3 files changed, 113 insertions(+) create mode 100644 include/HostgroupRouting.h create mode 100644 lib/HostgroupRouting.cpp diff --git a/include/HostgroupRouting.h b/include/HostgroupRouting.h new file mode 100644 index 000000000..9ba9e386f --- /dev/null +++ b/include/HostgroupRouting.h @@ -0,0 +1,52 @@ +/** + * @file HostgroupRouting.h + * @brief Pure hostgroup routing decision logic for unit testability. + * + * Extracted from MySQL_Session::get_pkts_from_client() and + * PgSQL_Session::get_pkts_from_client(). The logic is identical + * for both protocols. + * + * @see Phase 3.5 (GitHub issue #5493) + */ + +#ifndef HOSTGROUP_ROUTING_H +#define HOSTGROUP_ROUTING_H + +/** + * @brief Result of a hostgroup routing decision. + */ +struct HostgroupRoutingDecision { + int target_hostgroup; ///< Resolved hostgroup to route to. + int new_locked_on_hostgroup; ///< Updated lock state (-1 = unlocked). + bool error; ///< True if an illegal HG switch was attempted. +}; + +/** + * @brief Resolve the target hostgroup given session state and QP output. + * + * Decision logic (mirrors get_pkts_from_client()): + * 1. Start with default_hostgroup as the target + * 2. If QP provides a destination (>= 0) and no transaction lock, + * use the QP destination + * 3. If transaction_persistent_hostgroup >= 0, override with transaction HG + * 4. If locking is enabled and lock_hostgroup flag is set, acquire lock + * 5. If already locked, verify target matches lock (error if mismatch) + * + * @param default_hostgroup Session's default hostgroup. + * @param qpo_destination_hostgroup Query Processor output destination (-1 = no override). + * @param transaction_persistent_hostgroup Current transaction HG (-1 = none). + * @param locked_on_hostgroup Current lock state (-1 = unlocked). + * @param lock_hostgroup_flag Whether the QP wants to acquire a lock. + * @param lock_enabled Whether set_query_lock_on_hostgroup is enabled. + * @return HostgroupRoutingDecision with resolved target and updated lock. + */ +HostgroupRoutingDecision resolve_hostgroup_routing( + int default_hostgroup, + int qpo_destination_hostgroup, + int transaction_persistent_hostgroup, + int locked_on_hostgroup, + bool lock_hostgroup_flag, + bool lock_enabled +); + +#endif // HOSTGROUP_ROUTING_H diff --git a/lib/HostgroupRouting.cpp b/lib/HostgroupRouting.cpp new file mode 100644 index 000000000..0c2d0d8d5 --- /dev/null +++ b/lib/HostgroupRouting.cpp @@ -0,0 +1,60 @@ +/** + * @file HostgroupRouting.cpp + * @brief Implementation of pure hostgroup routing decision logic. + * + * Mirrors the routing block in get_pkts_from_client() (MySQL_Session.cpp + * ~lines 5340-5377 and PgSQL_Session.cpp ~lines 2154-2189). + * + * @see HostgroupRouting.h + * @see Phase 3.5 (GitHub issue #5493) + */ + +#include "HostgroupRouting.h" + +HostgroupRoutingDecision resolve_hostgroup_routing( + int default_hostgroup, + int qpo_destination_hostgroup, + int transaction_persistent_hostgroup, + int locked_on_hostgroup, + bool lock_hostgroup_flag, + bool lock_enabled) +{ + HostgroupRoutingDecision d; + d.error = false; + d.new_locked_on_hostgroup = locked_on_hostgroup; + + // Start with default hostgroup + int current_hostgroup = default_hostgroup; + + // If QP provides a valid destination and no transaction lock, use it + if (qpo_destination_hostgroup >= 0 + && transaction_persistent_hostgroup == -1) { + current_hostgroup = qpo_destination_hostgroup; + } + + // Transaction affinity overrides everything + if (transaction_persistent_hostgroup >= 0) { + current_hostgroup = transaction_persistent_hostgroup; + } + + // Hostgroup locking logic (algorithm introduced in 2.0.6) + if (lock_enabled) { + if (locked_on_hostgroup < 0) { + // Not yet locked + if (lock_hostgroup_flag) { + // Acquire lock on the current (already resolved) hostgroup + d.new_locked_on_hostgroup = current_hostgroup; + } + } + if (d.new_locked_on_hostgroup >= 0) { + // Already locked (or just acquired) — enforce + if (current_hostgroup != d.new_locked_on_hostgroup) { + d.error = true; + } + current_hostgroup = d.new_locked_on_hostgroup; + } + } + + d.target_hostgroup = current_hostgroup; + return d; +} diff --git a/lib/Makefile b/lib/Makefile index f7f24075c..6ba6e05bf 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -106,6 +106,7 @@ _OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo PgSQL_PreparedStatement.oo PgSQL_Extended_Query_Message.oo \ pgsql_tokenizer.oo \ MonitorHealthDecision.oo \ + HostgroupRouting.oo \ proxy_sqlite3_symbols.oo # TSDB object files