Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions include/MySQL_HostGroup_Routing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#ifndef __MYSQL_HOSTGROUP_ROUTING_H
#define __MYSQL_HOSTGROUP_ROUTING_H

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The include guard uses a reserved identifier (__MYSQL_HOSTGROUP_ROUTING_H). Identifiers beginning with double underscores are reserved to the implementation and can cause undefined behavior or conflicts with system headers.

Suggested fix: rename the guard to a non-reserved, project-scoped macro (e.g., PROXYSQL_MYSQL_HOSTGROUP_ROUTING_H) and update the closing comment accordingly.

Copilot uses AI. Check for mistakes.

#include <string>

/**
* @struct MySQL_Routing_Session_State
* @brief Represents the session state relevant for hostgroup routing decisions.
*/
struct MySQL_Routing_Session_State {
int current_hostgroup;
int default_hostgroup;
int locked_on_hostgroup;
int transaction_persistent_hostgroup;
int last_HG_affected_rows;
int warning_in_hg;
bool autocommit;
int autocommit_on_hostgroup;
bool mirror;
};

/**
* @struct MySQL_Routing_QPO_State
* @brief Represents the Query Processor Output relevant for hostgroup routing decisions.
*/
struct MySQL_Routing_QPO_State {
int destination_hostgroup;
bool is_set_statement; // Derived from query parsing
bool is_show_warnings; // Derived from query parsing
bool is_last_insert_id; // Derived from query parsing
bool is_version_query; // Derived from query parsing
};
Comment on lines +26 to +32

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this extracted API, MySQL_Routing_QPO_State::is_set_statement is used as the “should lock hostgroup” signal (call sites assign it from the lock_hostgroup flag computed by query parsing). The current name reads like a generic query classification, but its semantics are specifically about hostgroup locking (and PgSQL uses lock_hostgroup for the same concept), which makes the API confusing and easy to misuse.

Suggested fix: rename this field to something semantics-based like lock_hostgroup (or should_lock_hostgroup) to match the actual meaning and align with the PgSQL routing API.

Copilot uses AI. Check for mistakes.

/**
* @struct MySQL_Routing_Result
* @brief Represents the output of the hostgroup routing decision.
*/
struct MySQL_Routing_Result {
int new_current_hostgroup;
int new_locked_on_hostgroup;
bool lock_hostgroup;
bool error;
std::string error_msg;
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @brief Resolves the target hostgroup and locking decisions based on session and QPO state.
*
* This is a pure function designed to be easily testable.
*
* @param sess_state Current session state.
* @param qpo_state Query Processor Output state.
* @param set_query_lock_on_hostgroup Global configuration (mysql-set_query_lock_on_hostgroup).
* @return MySQL_Routing_Result The routing decision.
*/
MySQL_Routing_Result resolve_hostgroup_routing(
const MySQL_Routing_Session_State& sess_state,
const MySQL_Routing_QPO_State& qpo_state,
int set_query_lock_on_hostgroup
);

#endif // __MYSQL_HOSTGROUP_ROUTING_H
54 changes: 54 additions & 0 deletions include/PgSQL_HostGroup_Routing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef __PGSQL_HOSTGROUP_ROUTING_H
#define __PGSQL_HOSTGROUP_ROUTING_H

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The include guard uses a reserved identifier (__PGSQL_HOSTGROUP_ROUTING_H). Identifiers beginning with double underscores are reserved to the implementation and can cause undefined behavior or conflicts with system headers.

Suggested fix: rename the guard to a non-reserved, project-scoped macro (e.g., PROXYSQL_PGSQL_HOSTGROUP_ROUTING_H) and update the closing comment accordingly.

Copilot uses AI. Check for mistakes.

#include <string>

/**
* @struct PgSQL_Routing_Session_State
* @brief Represents the session state relevant for hostgroup routing decisions in PostgreSQL.
*/
struct PgSQL_Routing_Session_State {
int current_hostgroup;
int default_hostgroup;
int locked_on_hostgroup;
int transaction_persistent_hostgroup;
};

/**
* @struct PgSQL_Routing_QPO_State
* @brief Represents the Query Processor Output relevant for hostgroup routing decisions in PostgreSQL.
*/
struct PgSQL_Routing_QPO_State {
int destination_hostgroup;
bool lock_hostgroup; // Derived from query parsing
};

/**
* @struct PgSQL_Routing_Result
* @brief Represents the output of the hostgroup routing decision for PostgreSQL.
*/
struct PgSQL_Routing_Result {
int new_current_hostgroup;
int new_locked_on_hostgroup;
bool lock_hostgroup;
bool error;
std::string error_msg;
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @brief Resolves the target hostgroup and locking decisions based on session and QPO state.
*
* This is a pure function designed to be easily testable.
*
* @param sess_state Current session state.
* @param qpo_state Query Processor Output state.
* @param set_query_lock_on_hostgroup Global configuration (pgsql-set_query_lock_on_hostgroup).
* @return PgSQL_Routing_Result The routing decision.
*/
PgSQL_Routing_Result resolve_pgsql_hostgroup_routing(
const PgSQL_Routing_Session_State& sess_state,
const PgSQL_Routing_QPO_State& qpo_state,
int set_query_lock_on_hostgroup
);

#endif // __PGSQL_HOSTGROUP_ROUTING_H
2 changes: 1 addition & 1 deletion lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ MYCXXFLAGS := $(STDCPP) $(MYCFLAGS) $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(
default: libproxysql.a
.PHONY: default

_OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo SpookyV2.oo MySQL_Authentication.oo gen_utils.oo sqlite3db.oo mysql_connection.oo MySQL_HostGroups_Manager.oo mysql_data_stream.oo MySQL_Thread.oo MySQL_Session.oo MySQL_Protocol.oo mysql_backend.oo Query_Processor.oo MySQL_Query_Processor.oo PgSQL_Query_Processor.oo ProxySQL_Admin.oo ProxySQL_Config.oo ProxySQL_Restapi.oo MySQL_Monitor.oo MySQL_Logger.oo log_utils.oo thread.oo MySQL_PreparedStatement.oo ProxySQL_Cluster.oo ClickHouse_Authentication.oo ClickHouse_Server.oo ProxySQL_Statistics.oo Chart_bundle_js.oo ProxySQL_HTTP_Server.oo ProxySQL_RESTAPI_Server.oo font-awesome.min.css.oo main-bundle.min.css.oo MySQL_Variables.oo c_tokenizer.oo proxysql_utils.oo proxysql_coredump.oo proxysql_sslkeylog.oo \
_OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo SpookyV2.oo MySQL_Authentication.oo gen_utils.oo sqlite3db.oo mysql_connection.oo MySQL_HostGroups_Manager.oo mysql_data_stream.oo MySQL_Thread.oo MySQL_Session.oo MySQL_HostGroup_Routing.oo PgSQL_Session.oo PgSQL_HostGroup_Routing.oo MySQL_Protocol.oo mysql_backend.oo Query_Processor.oo MySQL_Query_Processor.oo PgSQL_Query_Processor.oo ProxySQL_Admin.oo ProxySQL_Config.oo ProxySQL_Restapi.oo MySQL_Monitor.oo MySQL_Logger.oo log_utils.oo thread.oo MySQL_PreparedStatement.oo ProxySQL_Cluster.oo ClickHouse_Authentication.oo ClickHouse_Server.oo ProxySQL_Statistics.oo Chart_bundle_js.oo ProxySQL_HTTP_Server.oo ProxySQL_RESTAPI_Server.oo font-awesome.min.css.oo main-bundle.min.css.oo MySQL_Variables.oo c_tokenizer.oo proxysql_utils.oo proxysql_coredump.oo proxysql_sslkeylog.oo \
sha256crypt.oo \
BaseSrvList.oo BaseHGC.oo Base_HostGroups_Manager.oo \
QP_rule_text.oo QP_query_digest_stats.oo \
Expand Down
1 change: 1 addition & 0 deletions lib/MonitorHealthDecision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

#include "MonitorHealthDecision.h"
#include <cstdint>

bool should_shun_on_connect_errors(
unsigned int errors_this_second,
Expand Down
76 changes: 76 additions & 0 deletions lib/MySQL_HostGroup_Routing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "MySQL_HostGroup_Routing.h"

MySQL_Routing_Result resolve_hostgroup_routing(

Check failure on line 3 in lib/MySQL_HostGroup_Routing.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=sysown_proxysql&issues=AZ0aiXQUdSwJe1TtIbmf&open=AZ0aiXQUdSwJe1TtIbmf&pullRequest=5529
const MySQL_Routing_Session_State& sess_state,
const MySQL_Routing_QPO_State& qpo_state,
int set_query_lock_on_hostgroup
) {
MySQL_Routing_Result result;
result.new_current_hostgroup = sess_state.current_hostgroup;
result.new_locked_on_hostgroup = sess_state.locked_on_hostgroup;
result.lock_hostgroup = false;
result.error = false;
result.error_msg = "";

// 1. Mirroring
if (sess_state.mirror) {
result.new_current_hostgroup = qpo_state.destination_hostgroup;
return result;
}

// 2. SHOW WARNINGS / SHOW COUNT(*) WARNINGS
if (qpo_state.is_show_warnings) {
if (sess_state.warning_in_hg > -1) {
result.new_current_hostgroup = sess_state.warning_in_hg;
}
return result;
}

// 3. LAST_INSERT_ID / @@IDENTITY
if (qpo_state.is_last_insert_id) {
if (sess_state.last_HG_affected_rows >= 0) {
result.new_current_hostgroup = sess_state.last_HG_affected_rows;
return result;
}
}

// 4. Default routing from QPO
if (qpo_state.destination_hostgroup >= 0) {
if (sess_state.transaction_persistent_hostgroup == -1) {
result.new_current_hostgroup = qpo_state.destination_hostgroup;
}
}

// 5. Hostgroup Locking Decisions (mysql-set_query_lock_on_hostgroup)
if (set_query_lock_on_hostgroup == 1) {
// Algorithm introduced in ProxySQL 2.0.6
if (result.new_locked_on_hostgroup < 0) {
if (qpo_state.is_set_statement) {
// If it's a SET statement that caused locking (determined by parser)
// In a pure function, we assume qpo_state.is_set_statement implies it should lock
result.lock_hostgroup = true;
result.new_locked_on_hostgroup = result.new_current_hostgroup;
}
}

if (result.new_locked_on_hostgroup >= 0) {
if (result.new_current_hostgroup != result.new_locked_on_hostgroup) {
result.error = true;
result.error_msg = "ProxySQL Error: connection is locked to hostgroup " +
std::to_string(result.new_locked_on_hostgroup) +
" but trying to reach hostgroup " +
std::to_string(result.new_current_hostgroup);
return result;
}
}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new pure routing function doesn’t reproduce the existing STATE_SLEEP hostgroup reset behavior. When set_query_lock_on_hostgroup==1 and qpo_state.destination_hostgroup<0, this function leaves new_current_hostgroup unchanged (and can even return an error if current_hostgroup != locked_on_hostgroup). In MySQL_Session::STATE_SLEEP the result is used to set current_hostgroup without checking res.error, so sessions can keep a stale hostgroup instead of resetting to default_hostgroup/locked_on_hostgroup as before.

Suggested fix: add an explicit “sleep/reset” mode/parameter (or a dedicated helper) that implements the old STATE_SLEEP logic: if transaction_persistent_hostgroup==-1 then new_current_hostgroup = (locked_on_hostgroup>=0 ? locked_on_hostgroup : default_hostgroup) when locking is enabled, otherwise default_hostgroup. Use that at the STATE_SLEEP call site(s).

Copilot uses AI. Check for mistakes.
} else {
// Legacy behavior before 2.0.6
if (sess_state.transaction_persistent_hostgroup == -1) {
if (qpo_state.destination_hostgroup < 0) {
result.new_current_hostgroup = sess_state.default_hostgroup;
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return result;
}
Loading
Loading