feat(plugin-chassis): gate chassis ABI additions behind PROXYSQL40

The four-phase plugin lifecycle (Step 2.2), pre-execution query-hook ABI
(Step 2.1), shared Prometheus registry access (Step 2.3), and generic
admin-command alias dispatch (Step 2.5) introduce new plugin ABI surface
area — a new descriptor field (register_schemas), new services-struct
entries (register_query_hook, get_prometheus_registry,
register_command_alias), new public manager methods
(invoke_register_schemas_phase, dispatch_query_hook, etc.), and a new
startup ordering in main().

These changes should not appear in the v3.x stable/innovative tiers.
Wrap every chassis-only declaration, definition, and dispatch path in
`#ifdef PROXYSQL40` so the v3.0/v3.1 builds retain the pre-chassis
behavior: a single init()-only descriptor layout, two-phase load+init
lifecycle, and the hardcoded MYSQLX alias ladder in Admin_Handler.cpp.

Changes:

* Top-level Makefile: introduce PROXYSQL40 as a distinct tier above
  PROXYSQL31. Cascade rule: PROXYSQLGENAI=1 implies PROXYSQL40=1
  implies PROXYSQL31=1. Bump major version number on PROXYSQL40=1
  (previously tied to PROXYSQLGENAI). Export PROXYSQL40 for recursive
  submakes.

* lib/Makefile, src/Makefile: add -DPROXYSQL40 via $(PSQL40) so the
  compile commands match the top-level tier selection.

* include/ProxySQL_Plugin.h: gate the register_schemas descriptor
  field, query-hook types/enums/typedefs, the alias-registration
  callback, and the trailing services-struct fields
  (register_query_hook, get_prometheus_registry,
  register_command_alias). The pre-chassis descriptor remains six
  fields (name, abi_version, init, start, stop, status_json) so
  plugins built against v3.x headers still link and load.

* include/ProxySQL_PluginManager.h: gate the chassis-exclusive public
  methods and private members (schemas_registered, aliases,
  services_phase_b_, query hook pointers). Provide two declarations
  of proxysql_load_configured_plugins — the four-phase variant (paired
  with proxysql_init_configured_plugins) under PROXYSQL40, the legacy
  single-call variant otherwise.

* lib/ProxySQL_PluginManager.cpp: gate every method that belongs to
  the chassis (invoke_register_schemas_phase, register_command_alias,
  resolve_alias_to_canonical, register_query_hook, has_query_hook,
  dispatch_query_hook), plus the services_phase_b_ initialization
  (nullptr DB handle stubs), the proxysql_dispatch_configured_plugin_
  query_hook entry point, and the alternate four-phase body of
  proxysql_load_configured_plugins / proxysql_init_configured_plugins.
  The legacy two-phase loader is restored under !PROXYSQL40.
  get_admindb_service / get_configdb_service / get_statsdb_service
  stay ungated because they back the unconditional services_ member.

* lib/Admin_Handler.cpp: gate the `#include \"ProxySQL_PluginManager.h\"`
  and restore under !PROXYSQL40 the 16 hardcoded MYSQLX alias vectors
  plus the 16-deep if-ladder in admin_handler_command_*(). Under
  PROXYSQL40 the ladder is replaced by a single call to
  proxysql_resolve_configured_plugin_admin_alias(), which is
  plugin-agnostic.

* src/main.cpp: gate InitConfiguredPlugins and branch the startup
  sequence in ProxySQL_Main_init_phase2___not_started:
  - PROXYSQL40: LoadConfiguredPlugins (Phase A+B) -> admin init (C) ->
    materialize_plugin_tables -> InitConfiguredPlugins (D) ->
    StartConfiguredPlugins (E).
  - !PROXYSQL40: admin init -> LoadConfiguredPlugins (combined
    load+init) -> materialize_plugin_tables -> StartConfiguredPlugins,
    matching the pre-chassis ordering.

* plugins/mysqlx/src/mysqlx_plugin.cpp: split init under PROXYSQL40
  into mysqlx_register_schemas (schema + commands) and a minimal
  mysqlx_init (context only). Under !PROXYSQL40 the original single
  mysqlx_init remains. Descriptor conditionally wires
  register_schemas.

* plugins/mysqlx/src/mysqlx_admin_schema.cpp: under PROXYSQL40 use
  the reg() helper to register canonical+aliases via the new ABI;
  under !PROXYSQL40 fall back to 16 direct register_command() calls
  (aliases handled by Admin_Handler's hardcoded ladder).

* test/tap/test_helpers/fake_plugin.cpp: gate fake_query_hook,
  fake_register_schemas, fake_descriptor_with_phase_b, the
  REGISTER_QUERY_HOOK block in fake_init, and the ENABLE_PHASE_B
  descriptor selection. The pre-chassis descriptor-returning stub is
  the sole return path under !PROXYSQL40.

* test/tap/tests/unit/Makefile: autodetect PROXYSQL40 via
  invoke_register_schemas_phase (the chassis-exclusive symbol) in the
  library archive; add -DPROXYSQL40 to fake_plugin .so compilation so
  the descriptor layout matches the loader's expectations; append the
  three chassis-only tests (plugin_lifecycle, plugin_prometheus,
  plugin_query_hook) to UNIT_TESTS only when PROXYSQL40=1.

* test/tap/tests/unit/plugin_config_unit-t.cpp,
  test/tap/tests/unit/plugin_dispatch_unit-t.cpp: compile-time-gate
  the chassis-only subtests, adjust plan() counts per mode
  (48/50 with PROXYSQL40, 47/32 without), and provide a
  proxysql_init_configured_plugins_compat() helper that is a no-op
  returning true under !PROXYSQL40 so shared test helpers work in
  both modes.

Verification — all plugin unit tests pass in both modes:

  PROXYSQL40=1 build (chassis enabled):
    plugin_config_unit-t:    48/48
    plugin_dispatch_unit-t:  50/50
    plugin_lifecycle_unit-t: 14/14
    plugin_query_hook_unit-t: 41/41

  PROXYSQL40=0 build (v3.x behavior):
    plugin_config_unit-t:    47/47
    plugin_dispatch_unit-t:  32/32
    (plugin_lifecycle, plugin_query_hook not built)
ProtocolX
Rene Cannao 1 month ago
parent 41918a42df
commit cd48c5613e

@ -53,10 +53,25 @@ lint: lint-generate-cdb lint-run
### * Advanced Anomaly Detection ### * Advanced Anomaly Detection
### - Automatically increments the major version (e.g., 3.0.6 -> 4.0.6). ### - Automatically increments the major version (e.g., 3.0.6 -> 4.0.6).
### ###
### HIERARCHY: `PROXYSQLGENAI=1` implies `PROXYSQL31=1`. ### HIERARCHY: `PROXYSQLGENAI=1` implies `PROXYSQL40=1` implies `PROXYSQL31=1`.
###
### 4. ProxySQL v4.0.x (Plugin Chassis Tier)
### - Enabled by setting `PROXYSQL40=1`.
### - Includes v3.1 features plus:
### * Four-phase plugin lifecycle (register_schemas + init split)
### * Pre-execution query-hook plugin ABI
### * Shared Prometheus registry access for plugins
### * Generic admin-command alias dispatch
### - Automatically increments the major version (e.g., 3.0.6 -> 4.0.6).
### - `PROXYSQLGENAI=1` implies `PROXYSQL40=1`.
# If PROXYSQLGENAI is enabled, it automatically enables PROXYSQL31 # If PROXYSQLGENAI is enabled, it automatically enables PROXYSQL40
ifeq ($(PROXYSQLGENAI),1) ifeq ($(PROXYSQLGENAI),1)
PROXYSQL40 := 1
endif
# If PROXYSQL40 is enabled, it automatically enables PROXYSQL31
ifeq ($(PROXYSQL40),1)
PROXYSQL31 := 1 PROXYSQL31 := 1
endif endif
@ -71,8 +86,9 @@ GIT_VERSION ?= $(GIT_VERSION_BASE)
ifeq ($(MAKELEVEL),0) ifeq ($(MAKELEVEL),0)
# Normalize GIT_VERSION by stripping leading 'v' for arithmetic # Normalize GIT_VERSION by stripping leading 'v' for arithmetic
GIT_VERSION_NORM := $(shell echo "$(GIT_VERSION_BASE)" | sed 's/^v//') GIT_VERSION_NORM := $(shell echo "$(GIT_VERSION_BASE)" | sed 's/^v//')
# If PROXYSQLGENAI is enabled, increment the major version number by 1 # If PROXYSQL40 (or PROXYSQLGENAI, which implies it) is enabled,
ifeq ($(PROXYSQLGENAI),1) # increment the major version number by 1
ifeq ($(PROXYSQL40),1)
GIT_VERSION := $(shell echo "$(GIT_VERSION_NORM)" | awk -F. '{printf "%d.%s", $$1+1, substr($$0, length($$1)+2)}') GIT_VERSION := $(shell echo "$(GIT_VERSION_NORM)" | awk -F. '{printf "%d.%s", $$1+1, substr($$0, length($$1)+2)}')
else else
# If PROXYSQL31 is enabled, increment the minor version number by 1 # If PROXYSQL31 is enabled, increment the minor version number by 1
@ -96,6 +112,7 @@ endif
export CURVER export CURVER
export PROXYSQLGENAI export PROXYSQLGENAI
export PROXYSQL40
export PROXYSQL31 export PROXYSQL31
export PROXYSQLFFTO export PROXYSQLFFTO
export PROXYSQLTSDB export PROXYSQLTSDB

@ -6,7 +6,9 @@
class SQLite3DB; class SQLite3DB;
class SQLite3_result; class SQLite3_result;
#ifdef PROXYSQL40
namespace prometheus { class Registry; } namespace prometheus { class Registry; }
#endif /* PROXYSQL40 */
enum class ProxySQL_PluginDBKind : uint8_t { enum class ProxySQL_PluginDBKind : uint8_t {
admin_db = 0, admin_db = 0,
@ -50,6 +52,7 @@ using proxysql_plugin_register_table_cb =
using proxysql_plugin_register_command_cb = using proxysql_plugin_register_command_cb =
void (*)(const char *, proxysql_plugin_admin_command_cb); void (*)(const char *, proxysql_plugin_admin_command_cb);
#ifdef PROXYSQL40
// Register an alternative spelling (alias) of an already-registered command. // Register an alternative spelling (alias) of an already-registered command.
// The `canonical` argument MUST match the exact SQL passed to a prior // The `canonical` argument MUST match the exact SQL passed to a prior
// `register_command()` call (after whitespace normalization). Plugins use // `register_command()` call (after whitespace normalization). Plugins use
@ -63,6 +66,7 @@ using proxysql_plugin_register_command_cb =
// command BEFORE its aliases). Silently skips duplicate aliases. // command BEFORE its aliases). Silently skips duplicate aliases.
using proxysql_plugin_register_command_alias_cb = using proxysql_plugin_register_command_alias_cb =
void (*)(const char *canonical, const char *alias); void (*)(const char *canonical, const char *alias);
#endif /* PROXYSQL40 */
using proxysql_plugin_snapshot_cb = using proxysql_plugin_snapshot_cb =
SQLite3_result *(*)(); SQLite3_result *(*)();
@ -73,6 +77,7 @@ using proxysql_plugin_db_handle_cb =
using proxysql_plugin_log_message_cb = using proxysql_plugin_log_message_cb =
void (*)(int, const char *); void (*)(int, const char *);
#ifdef PROXYSQL40
// Pre-execution query hook (Step 2 ABI extension). // Pre-execution query hook (Step 2 ABI extension).
// //
// Wire protocol the hook is being invoked for. A plugin can register // Wire protocol the hook is being invoked for. A plugin can register
@ -140,6 +145,7 @@ using proxysql_plugin_register_query_hook_cb =
// scraped immediately. // scraped immediately.
using proxysql_plugin_get_prometheus_registry_cb = using proxysql_plugin_get_prometheus_registry_cb =
prometheus::Registry* (*)(); prometheus::Registry* (*)();
#endif /* PROXYSQL40 */
// Services provided to plugins across the four-phase lifecycle. // Services provided to plugins across the four-phase lifecycle.
// //
@ -169,6 +175,7 @@ struct ProxySQL_PluginServices {
proxysql_plugin_db_handle_cb get_admindb; proxysql_plugin_db_handle_cb get_admindb;
proxysql_plugin_db_handle_cb get_configdb; proxysql_plugin_db_handle_cb get_configdb;
proxysql_plugin_db_handle_cb get_statsdb; proxysql_plugin_db_handle_cb get_statsdb;
#ifdef PROXYSQL40
// Step 2 ABI extensions. Both fields are additive at the end of // Step 2 ABI extensions. Both fields are additive at the end of
// the struct -- older plugins that were built against the previous // the struct -- older plugins that were built against the previous
// layout don't read past the previous member; new plugins must // layout don't read past the previous member; new plugins must
@ -181,6 +188,7 @@ struct ProxySQL_PluginServices {
// plugins avoid the MYSQLX-specific hardcoded alias ladder that // plugins avoid the MYSQLX-specific hardcoded alias ladder that
// previously lived in lib/Admin_Handler.cpp. // previously lived in lib/Admin_Handler.cpp.
proxysql_plugin_register_command_alias_cb register_command_alias; proxysql_plugin_register_command_alias_cb register_command_alias;
#endif /* PROXYSQL40 */
}; };
using proxysql_plugin_init_cb = using proxysql_plugin_init_cb =
@ -197,6 +205,7 @@ using proxysql_plugin_stop_cb =
using proxysql_plugin_status_json_cb = using proxysql_plugin_status_json_cb =
const char *(*)(); const char *(*)();
#ifdef PROXYSQL40
// Phase B entry point: "declare your schema before admin bootstrap." // Phase B entry point: "declare your schema before admin bootstrap."
// //
// Four-phase plugin lifecycle: // Four-phase plugin lifecycle:
@ -214,6 +223,7 @@ using proxysql_plugin_status_json_cb =
// work (the mysqlx plugin does this today). // work (the mysqlx plugin does this today).
using proxysql_plugin_register_schemas_cb = using proxysql_plugin_register_schemas_cb =
bool (*)(ProxySQL_PluginServices *); bool (*)(ProxySQL_PluginServices *);
#endif /* PROXYSQL40 */
struct ProxySQL_PluginDescriptor { struct ProxySQL_PluginDescriptor {
const char *name; const char *name;
@ -222,6 +232,7 @@ struct ProxySQL_PluginDescriptor {
proxysql_plugin_start_cb start; proxysql_plugin_start_cb start;
proxysql_plugin_stop_cb stop; proxysql_plugin_stop_cb stop;
proxysql_plugin_status_json_cb status_json; proxysql_plugin_status_json_cb status_json;
#ifdef PROXYSQL40
/* Optional: called between load() and init(). /* Optional: called between load() and init().
* `services` will have register_table available but DB handle getters * `services` will have register_table available but DB handle getters
* (get_admindb/get_configdb/get_statsdb) will return nullptr. Plugins * (get_admindb/get_configdb/get_statsdb) will return nullptr. Plugins
@ -229,10 +240,12 @@ struct ProxySQL_PluginDescriptor {
* init() after admin module bootstrap materializes schema. Plugins that * init() after admin module bootstrap materializes schema. Plugins that
* leave this field null keep the pre-existing two-phase behavior. */ * leave this field null keep the pre-existing two-phase behavior. */
proxysql_plugin_register_schemas_cb register_schemas; proxysql_plugin_register_schemas_cb register_schemas;
#endif /* PROXYSQL40 */
}; };
using proxysql_plugin_descriptor_v1_t = const ProxySQL_PluginDescriptor *(*)(); using proxysql_plugin_descriptor_v1_t = const ProxySQL_PluginDescriptor *(*)();
#ifdef PROXYSQL40
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// ABI guidance: disk/memory/runtime sync — empty-source MUST still clear // ABI guidance: disk/memory/runtime sync — empty-source MUST still clear
// the destination. // the destination.
@ -268,5 +281,6 @@ using proxysql_plugin_descriptor_v1_t = const ProxySQL_PluginDescriptor *(*)();
// empty source — run the DELETE+INSERT unconditionally inside a single // empty source — run the DELETE+INSERT unconditionally inside a single
// transaction and check each execute() return. // transaction and check each execute() return.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#endif /* PROXYSQL40 */
#endif /* PROXYSQL_PLUGIN_H */ #endif /* PROXYSQL_PLUGIN_H */

@ -18,6 +18,7 @@ public:
ProxySQL_PluginManager &operator=(const ProxySQL_PluginManager &) = delete; ProxySQL_PluginManager &operator=(const ProxySQL_PluginManager &) = delete;
bool load(const std::string &path, std::string &err); bool load(const std::string &path, std::string &err);
#ifdef PROXYSQL40
// Phase B of the four-phase plugin lifecycle: after all plugins have // Phase B of the four-phase plugin lifecycle: after all plugins have
// been dlopen'd but BEFORE admin module bootstrap. Invokes each // been dlopen'd but BEFORE admin module bootstrap. Invokes each
// plugin's optional register_schemas callback with a services struct // plugin's optional register_schemas callback with a services struct
@ -25,6 +26,7 @@ public:
// nullptr. Plugins that left the descriptor field null are skipped. // nullptr. Plugins that left the descriptor field null are skipped.
// Returns false on the first register_schemas callback that fails. // Returns false on the first register_schemas callback that fails.
bool invoke_register_schemas_phase(std::string &err); bool invoke_register_schemas_phase(std::string &err);
#endif /* PROXYSQL40 */
bool init_all(std::string &err); bool init_all(std::string &err);
bool start_all(std::string &err); bool start_all(std::string &err);
bool stop_all(); bool stop_all();
@ -36,6 +38,7 @@ public:
bool has_command_for_test(const std::string& sql) const; bool has_command_for_test(const std::string& sql) const;
bool register_table(const ProxySQL_PluginTableDef& def); bool register_table(const ProxySQL_PluginTableDef& def);
bool register_command(const char* sql, proxysql_plugin_admin_command_cb cb); bool register_command(const char* sql, proxysql_plugin_admin_command_cb cb);
#ifdef PROXYSQL40
// Register an alternate spelling (alias) of an already-registered // Register an alternate spelling (alias) of an already-registered
// command. Returns true on successful registration, false if the // command. Returns true on successful registration, false if the
// canonical SQL isn't registered, if the alias is empty, or if the // canonical SQL isn't registered, if the alias is empty, or if the
@ -54,6 +57,7 @@ public:
bool dispatch_query_hook(ProxySQL_PluginProtocol proto, bool dispatch_query_hook(ProxySQL_PluginProtocol proto,
const ProxySQL_PluginQueryHookPayload& payload, const ProxySQL_PluginQueryHookPayload& payload,
ProxySQL_PluginQueryHookResult& result) const; ProxySQL_PluginQueryHookResult& result) const;
#endif /* PROXYSQL40 */
size_t size() const; size_t size() const;
@ -62,7 +66,9 @@ private:
void *handle{nullptr}; void *handle{nullptr};
const ProxySQL_PluginDescriptor *descriptor{nullptr}; const ProxySQL_PluginDescriptor *descriptor{nullptr};
std::string path {}; std::string path {};
#ifdef PROXYSQL40
bool schemas_registered{false}; bool schemas_registered{false};
#endif /* PROXYSQL40 */
bool initialized{false}; bool initialized{false};
bool started{false}; bool started{false};
bool stopped{false}; bool stopped{false};
@ -71,10 +77,12 @@ private:
struct registered_command_t { struct registered_command_t {
std::string sql {}; std::string sql {};
proxysql_plugin_admin_command_cb cb { nullptr }; proxysql_plugin_admin_command_cb cb { nullptr };
#ifdef PROXYSQL40
// User-friendly alternate spellings for this canonical command. // User-friendly alternate spellings for this canonical command.
// Admin's dispatcher resolves any of these to `sql` before // Admin's dispatcher resolves any of these to `sql` before
// invoking `cb`. Normalized (case + whitespace) on insertion. // invoking `cb`. Normalized (case + whitespace) on insertion.
std::vector<std::string> aliases {}; std::vector<std::string> aliases {};
#endif /* PROXYSQL40 */
}; };
struct registered_table_storage_t { struct registered_table_storage_t {
@ -84,18 +92,22 @@ private:
std::vector<plugin_handle_t> plugins_; std::vector<plugin_handle_t> plugins_;
ProxySQL_PluginServices services_; ProxySQL_PluginServices services_;
#ifdef PROXYSQL40
// Phase-B variant handed to register_schemas; DB-handle getters are // Phase-B variant handed to register_schemas; DB-handle getters are
// stubbed, everything else mirrors services_. See the contract in // stubbed, everything else mirrors services_. See the contract in
// ProxySQL_Plugin.h next to ProxySQL_PluginServices. // ProxySQL_Plugin.h next to ProxySQL_PluginServices.
ProxySQL_PluginServices services_phase_b_; ProxySQL_PluginServices services_phase_b_;
#endif /* PROXYSQL40 */
std::vector<ProxySQL_PluginTableDef> tables_admin_; std::vector<ProxySQL_PluginTableDef> tables_admin_;
std::vector<ProxySQL_PluginTableDef> tables_config_; std::vector<ProxySQL_PluginTableDef> tables_config_;
std::vector<ProxySQL_PluginTableDef> tables_stats_; std::vector<ProxySQL_PluginTableDef> tables_stats_;
std::deque<registered_table_storage_t> table_storage_; std::deque<registered_table_storage_t> table_storage_;
std::vector<registered_command_t> commands_; std::vector<registered_command_t> commands_;
#ifdef PROXYSQL40
// At most one hook per protocol; nullptr means "no hook". // At most one hook per protocol; nullptr means "no hook".
proxysql_plugin_query_hook_cb mysql_query_hook_ { nullptr }; proxysql_plugin_query_hook_cb mysql_query_hook_ { nullptr };
proxysql_plugin_query_hook_cb pgsql_query_hook_ { nullptr }; proxysql_plugin_query_hook_cb pgsql_query_hook_ { nullptr };
#endif /* PROXYSQL40 */
}; };
ProxySQL_PluginManager* proxysql_get_plugin_manager(); ProxySQL_PluginManager* proxysql_get_plugin_manager();
@ -104,6 +116,7 @@ bool proxysql_dispatch_configured_plugin_admin_command(
const std::string& sql, const std::string& sql,
ProxySQL_PluginCommandResult& result ProxySQL_PluginCommandResult& result
); );
#ifdef PROXYSQL40
bool proxysql_dispatch_configured_plugin_query_hook( bool proxysql_dispatch_configured_plugin_query_hook(
ProxySQL_PluginProtocol proto, ProxySQL_PluginProtocol proto,
const ProxySQL_PluginQueryHookPayload& payload, const ProxySQL_PluginQueryHookPayload& payload,
@ -138,6 +151,14 @@ bool proxysql_init_configured_plugins(
ProxySQL_PluginManager* manager, ProxySQL_PluginManager* manager,
std::string& err std::string& err
); );
#else /* !PROXYSQL40 */
// Pre-chassis two-phase load: dlopen + init_all in one call.
bool proxysql_load_configured_plugins(
std::unique_ptr<ProxySQL_PluginManager>& manager,
const std::vector<std::string>& plugin_modules,
std::string& err
);
#endif /* PROXYSQL40 */
bool proxysql_start_configured_plugins( bool proxysql_start_configured_plugins(
ProxySQL_PluginManager* manager, ProxySQL_PluginManager* manager,
std::string& err std::string& err

@ -40,7 +40,9 @@ using json = nlohmann::json;
#include "MySQL_PreparedStatement.h" #include "MySQL_PreparedStatement.h"
#include "ProxySQL_Cluster.hpp" #include "ProxySQL_Cluster.hpp"
#include "ProxySQL_Statistics.hpp" #include "ProxySQL_Statistics.hpp"
#ifdef PROXYSQL40
#include "ProxySQL_PluginManager.h" #include "ProxySQL_PluginManager.h"
#endif /* PROXYSQL40 */
#include "MySQL_Logger.hpp" #include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp" #include "PgSQL_Logger.hpp"
#include "MCP_Thread.h" #include "MCP_Thread.h"
@ -315,14 +317,76 @@ const std::vector<std::string> SAVE_TSDB_VARIABLES_TO_MEMORY = {
"SAVE TSDB VARIABLES FROM RUNTIME" , "SAVE TSDB VARIABLES FROM RUNTIME" ,
"SAVE TSDB VARIABLES FROM RUN" }; "SAVE TSDB VARIABLES FROM RUN" };
// MYSQLX plugin-owned admin commands used to live here as hardcoded #ifndef PROXYSQL40
// alias vectors plus a 16-deep if-ladder in admin_handler_command_*(). // MySQLX plugin — hardcoded alias groups for admin-command dispatch.
// They now live in the mysqlx plugin itself (plugins/mysqlx/src/ // Under PROXYSQL40 these move into the mysqlx plugin itself (via
// mysqlx_admin_schema.cpp) via the register_command + // register_command + register_command_alias) and the generic
// register_command_alias ABI; admin dispatch resolves via // proxysql_resolve_configured_plugin_admin_alias() helper replaces the
// proxysql_resolve_configured_plugin_admin_alias(), which is // if-ladder below. This block exists for backward compatibility with
// plugin-agnostic and requires no core change for a new plugin. // builds that don't enable the plugin chassis.
const std::vector<std::string> LOAD_MYSQLX_USERS_FROM_MEMORY = {
"LOAD MYSQLX USERS FROM MEMORY" ,
"LOAD MYSQLX USERS FROM MEM" ,
"LOAD MYSQLX USERS TO RUNTIME" ,
"LOAD MYSQLX USERS TO RUN" };
const std::vector<std::string> SAVE_MYSQLX_USERS_TO_MEMORY = {
"SAVE MYSQLX USERS TO MEMORY" ,
"SAVE MYSQLX USERS TO MEM" ,
"SAVE MYSQLX USERS FROM RUNTIME" ,
"SAVE MYSQLX USERS FROM RUN" };
const std::vector<std::string> LOAD_MYSQLX_ROUTES_FROM_MEMORY = {
"LOAD MYSQLX ROUTES FROM MEMORY" ,
"LOAD MYSQLX ROUTES FROM MEM" ,
"LOAD MYSQLX ROUTES TO RUNTIME" ,
"LOAD MYSQLX ROUTES TO RUN" };
const std::vector<std::string> SAVE_MYSQLX_ROUTES_TO_MEMORY = {
"SAVE MYSQLX ROUTES TO MEMORY" ,
"SAVE MYSQLX ROUTES TO MEM" ,
"SAVE MYSQLX ROUTES FROM RUNTIME" ,
"SAVE MYSQLX ROUTES FROM RUN" };
const std::vector<std::string> LOAD_MYSQLX_BACKEND_ENDPOINTS_FROM_MEMORY = {
"LOAD MYSQLX BACKEND ENDPOINTS FROM MEMORY" ,
"LOAD MYSQLX BACKEND ENDPOINTS FROM MEM" ,
"LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME" ,
"LOAD MYSQLX BACKEND ENDPOINTS TO RUN" };
const std::vector<std::string> SAVE_MYSQLX_BACKEND_ENDPOINTS_TO_MEMORY = {
"SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY" ,
"SAVE MYSQLX BACKEND ENDPOINTS TO MEM" ,
"SAVE MYSQLX BACKEND ENDPOINTS FROM RUNTIME" ,
"SAVE MYSQLX BACKEND ENDPOINTS FROM RUN" };
const std::vector<std::string> LOAD_MYSQLX_VARIABLES_FROM_MEMORY = {
"LOAD MYSQLX VARIABLES FROM MEMORY" ,
"LOAD MYSQLX VARIABLES FROM MEM" ,
"LOAD MYSQLX VARIABLES TO RUNTIME" ,
"LOAD MYSQLX VARIABLES TO RUN" };
const std::vector<std::string> SAVE_MYSQLX_VARIABLES_TO_MEMORY = {
"SAVE MYSQLX VARIABLES TO MEMORY" ,
"SAVE MYSQLX VARIABLES TO MEM" ,
"SAVE MYSQLX VARIABLES FROM RUNTIME" ,
"SAVE MYSQLX VARIABLES FROM RUN" };
const std::vector<std::string> LOAD_MYSQLX_USERS_FROM_DISK = {
"LOAD MYSQLX USERS FROM DISK" };
const std::vector<std::string> SAVE_MYSQLX_USERS_TO_DISK = {
"SAVE MYSQLX USERS TO DISK" };
const std::vector<std::string> LOAD_MYSQLX_ROUTES_FROM_DISK = {
"LOAD MYSQLX ROUTES FROM DISK" };
const std::vector<std::string> SAVE_MYSQLX_ROUTES_TO_DISK = {
"SAVE MYSQLX ROUTES TO DISK" };
const std::vector<std::string> LOAD_MYSQLX_BACKEND_ENDPOINTS_FROM_DISK = {
"LOAD MYSQLX BACKEND ENDPOINTS FROM DISK" };
const std::vector<std::string> SAVE_MYSQLX_BACKEND_ENDPOINTS_TO_DISK = {
"SAVE MYSQLX BACKEND ENDPOINTS TO DISK" };
const std::vector<std::string> LOAD_MYSQLX_VARIABLES_FROM_DISK = {
"LOAD MYSQLX VARIABLES FROM DISK" };
const std::vector<std::string> SAVE_MYSQLX_VARIABLES_TO_DISK = {
"SAVE MYSQLX VARIABLES TO DISK" };
#endif /* !PROXYSQL40 */
//
const std::vector<std::string> LOAD_COREDUMP_FROM_MEMORY = { const std::vector<std::string> LOAD_COREDUMP_FROM_MEMORY = {
"LOAD COREDUMP FROM MEMORY" , "LOAD COREDUMP FROM MEMORY" ,
"LOAD COREDUMP FROM MEM" , "LOAD COREDUMP FROM MEM" ,
@ -3990,6 +4054,7 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) {
//pthread_mutex_unlock(&admin_mutex); //pthread_mutex_unlock(&admin_mutex);
goto __run_query; goto __run_query;
} }
#ifdef PROXYSQL40
// Generic plugin admin-command dispatch. Each loaded plugin // Generic plugin admin-command dispatch. Each loaded plugin
// registers its commands (plus user-friendly aliases) with the // registers its commands (plus user-friendly aliases) with the
// plugin manager during init; the manager resolves an incoming // plugin manager during init; the manager resolves an incoming
@ -4013,6 +4078,35 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) {
goto __run_query; goto __run_query;
} }
} }
#else /* !PROXYSQL40 */
const char* mysqlx_canonical = nullptr;
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_USERS_FROM_MEMORY, "LOAD MYSQLX USERS TO RUNTIME", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_USERS_TO_MEMORY, "SAVE MYSQLX USERS TO MEMORY", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_ROUTES_FROM_MEMORY, "LOAD MYSQLX ROUTES TO RUNTIME", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_ROUTES_TO_MEMORY, "SAVE MYSQLX ROUTES TO MEMORY", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_BACKEND_ENDPOINTS_FROM_MEMORY, "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_BACKEND_ENDPOINTS_TO_MEMORY, "SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_VARIABLES_FROM_MEMORY, "LOAD MYSQLX VARIABLES TO RUNTIME", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_VARIABLES_TO_MEMORY, "SAVE MYSQLX VARIABLES TO MEMORY", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_USERS_FROM_DISK, "LOAD MYSQLX USERS FROM DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_USERS_TO_DISK, "SAVE MYSQLX USERS TO DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_ROUTES_FROM_DISK, "LOAD MYSQLX ROUTES FROM DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_ROUTES_TO_DISK, "SAVE MYSQLX ROUTES TO DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_BACKEND_ENDPOINTS_FROM_DISK, "LOAD MYSQLX BACKEND ENDPOINTS FROM DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_BACKEND_ENDPOINTS_TO_DISK, "SAVE MYSQLX BACKEND ENDPOINTS TO DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(LOAD_MYSQLX_VARIABLES_FROM_DISK, "LOAD MYSQLX VARIABLES FROM DISK", query_no_space, query_no_space_length);
if (!mysqlx_canonical) mysqlx_canonical = resolve_admin_alias_to_canonical(SAVE_MYSQLX_VARIABLES_TO_DISK, "SAVE MYSQLX VARIABLES TO DISK", query_no_space, query_no_space_length);
if (mysqlx_canonical) {
ProxySQL_Admin *SPA=(ProxySQL_Admin *)pa;
if (SPA->dispatch_plugin_admin_command(sess, mysqlx_canonical)) {
run_query = false;
goto __run_query;
}
SPA->send_error_msg_to_client(sess, (char*)"MYSQLX plugin is not loaded");
run_query = false;
goto __run_query;
}
#endif /* PROXYSQL40 */
if ((query_no_space_length>5) && ( (!strncasecmp("SAVE ", query_no_space, 5)) || (!strncasecmp("LOAD ", query_no_space, 5))) ) { if ((query_no_space_length>5) && ( (!strncasecmp("SAVE ", query_no_space, 5)) || (!strncasecmp("LOAD ", query_no_space, 5))) ) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received LOAD or SAVE command\n"); proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received LOAD or SAVE command\n");

@ -56,6 +56,11 @@ ifeq ($(PROXYSQLGENAI),1)
PSQLGA := -DPROXYSQLGENAI PSQLGA := -DPROXYSQLGENAI
endif endif
PSQL40 :=
ifeq ($(PROXYSQL40),1)
PSQL40 := -DPROXYSQL40
endif
PSQL31 := PSQL31 :=
ifeq ($(PROXYSQL31),1) ifeq ($(PROXYSQL31),1)
PSQL31 := -DPROXYSQL31 PSQL31 := -DPROXYSQL31
@ -81,7 +86,7 @@ endif
MYCFLAGS := $(IDIRS) $(OPTZ) $(DEBUG) -Wall -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) MYCFLAGS := $(IDIRS) $(OPTZ) $(DEBUG) -Wall -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN)
MYCXXFLAGS := $(STDCPP) $(MYCFLAGS) $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(ENABLE_EPOLL) MYCXXFLAGS := $(STDCPP) $(MYCFLAGS) $(PSQLCH) $(PSQLGA) $(PSQL40) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(ENABLE_EPOLL)
default: libproxysql.a default: libproxysql.a
.PHONY: default .PHONY: default

@ -87,6 +87,7 @@ void register_command_service(const char* sql, proxysql_plugin_admin_command_cb
} }
} }
#ifdef PROXYSQL40
void register_command_alias_service(const char* canonical, const char* alias) { void register_command_alias_service(const char* canonical, const char* alias) {
if (g_registry_target == nullptr) { if (g_registry_target == nullptr) {
proxy_warning("Plugin command-alias registration attempted outside init phase " proxy_warning("Plugin command-alias registration attempted outside init phase "
@ -119,6 +120,7 @@ bool register_query_hook_service(ProxySQL_PluginProtocol proto,
} }
return true; return true;
} }
#endif /* PROXYSQL40 */
SQLite3DB* get_admindb_service() { SQLite3DB* get_admindb_service() {
return proxysql_plugin_get_admindb(); return proxysql_plugin_get_admindb();
@ -132,6 +134,7 @@ SQLite3DB* get_statsdb_service() {
return proxysql_plugin_get_statsdb(); return proxysql_plugin_get_statsdb();
} }
#ifdef PROXYSQL40
// Phase-B stubs: during register_schemas the admin module has not yet // Phase-B stubs: during register_schemas the admin module has not yet
// materialized the SQLite schema, so DB handles are deliberately nullptr. // materialized the SQLite schema, so DB handles are deliberately nullptr.
// Plugins are documented to never call these during Phase B, but returning // Plugins are documented to never call these during Phase B, but returning
@ -154,6 +157,7 @@ bool register_query_hook_phase_b_stub(ProxySQL_PluginProtocol,
prometheus::Registry* get_prometheus_registry_service() { prometheus::Registry* get_prometheus_registry_service() {
return GloVars.prometheus_registry.get(); return GloVars.prometheus_registry.get();
} }
#endif /* PROXYSQL40 */
void log_message_service(int level, const char* message) { void log_message_service(int level, const char* message) {
if (message == nullptr) { if (message == nullptr) {
@ -225,6 +229,7 @@ ProxySQL_PluginManager::ProxySQL_PluginManager() {
services_.get_configdb = &get_configdb_service; services_.get_configdb = &get_configdb_service;
services_.get_statsdb = &get_statsdb_service; services_.get_statsdb = &get_statsdb_service;
services_.log_message = &log_message_service; services_.log_message = &log_message_service;
#ifdef PROXYSQL40
services_.register_query_hook = &register_query_hook_service; services_.register_query_hook = &register_query_hook_service;
services_.get_prometheus_registry = &get_prometheus_registry_service; services_.get_prometheus_registry = &get_prometheus_registry_service;
services_.register_command_alias = &register_command_alias_service; services_.register_command_alias = &register_command_alias_service;
@ -248,6 +253,7 @@ ProxySQL_PluginManager::ProxySQL_PluginManager() {
// register_command() first, then register aliases. Since register_command // register_command() first, then register aliases. Since register_command
// is also available during Phase B, so is register_command_alias. // is also available during Phase B, so is register_command_alias.
services_phase_b_.register_command_alias = &register_command_alias_service; services_phase_b_.register_command_alias = &register_command_alias_service;
#endif /* PROXYSQL40 */
} }
ProxySQL_PluginManager::~ProxySQL_PluginManager() { ProxySQL_PluginManager::~ProxySQL_PluginManager() {
@ -316,6 +322,7 @@ bool ProxySQL_PluginManager::load(const std::string &path, std::string &err) {
return true; return true;
} }
#ifdef PROXYSQL40
bool ProxySQL_PluginManager::invoke_register_schemas_phase(std::string &err) { bool ProxySQL_PluginManager::invoke_register_schemas_phase(std::string &err) {
// Phase B of the four-phase lifecycle. Called after all plugins have // Phase B of the four-phase lifecycle. Called after all plugins have
// been dlopen'd but before admin module bootstrap, so plugins can // been dlopen'd but before admin module bootstrap, so plugins can
@ -365,6 +372,7 @@ bool ProxySQL_PluginManager::invoke_register_schemas_phase(std::string &err) {
return true; return true;
} }
#endif /* PROXYSQL40 */
bool ProxySQL_PluginManager::init_all(std::string &err) { bool ProxySQL_PluginManager::init_all(std::string &err) {
// Only called during single-threaded startup; g_registry_target and // Only called during single-threaded startup; g_registry_target and
@ -479,6 +487,7 @@ bool ProxySQL_PluginManager::dispatch_admin_command(const ProxySQL_PluginCommand
for (const auto& command : commands_) { for (const auto& command : commands_) {
bool matches = sql_equals_ci(command.sql, normalized_sql); bool matches = sql_equals_ci(command.sql, normalized_sql);
#ifdef PROXYSQL40
if (!matches) { if (!matches) {
for (const auto& alias : command.aliases) { for (const auto& alias : command.aliases) {
if (sql_equals_ci(alias, normalized_sql)) { if (sql_equals_ci(alias, normalized_sql)) {
@ -487,6 +496,7 @@ bool ProxySQL_PluginManager::dispatch_admin_command(const ProxySQL_PluginCommand
} }
} }
} }
#endif /* PROXYSQL40 */
if (!matches) { if (!matches) {
continue; continue;
} }
@ -495,10 +505,14 @@ bool ProxySQL_PluginManager::dispatch_admin_command(const ProxySQL_PluginCommand
} }
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Dispatching plugin command: %s (via %s)\n", proxy_debug(PROXY_DEBUG_ADMIN, 4, "Dispatching plugin command: %s (via %s)\n",
command.sql.c_str(), normalized_sql.c_str()); command.sql.c_str(), normalized_sql.c_str());
#ifdef PROXYSQL40
// Pass the CANONICAL form to the callback so plugins can ignore // Pass the CANONICAL form to the callback so plugins can ignore
// which alias the user typed — they match on their own canonical // which alias the user typed — they match on their own canonical
// strings only. // strings only.
result = command.cb(ctx, command.sql.c_str()); result = command.cb(ctx, command.sql.c_str());
#else
result = command.cb(ctx, normalized_sql.c_str());
#endif /* PROXYSQL40 */
return true; return true;
} }
@ -575,6 +589,7 @@ bool ProxySQL_PluginManager::register_table(const ProxySQL_PluginTableDef& def)
return true; return true;
} }
#ifdef PROXYSQL40
bool ProxySQL_PluginManager::register_query_hook(ProxySQL_PluginProtocol proto, bool ProxySQL_PluginManager::register_query_hook(ProxySQL_PluginProtocol proto,
proxysql_plugin_query_hook_cb cb) { proxysql_plugin_query_hook_cb cb) {
if (cb == nullptr) { if (cb == nullptr) {
@ -615,6 +630,7 @@ bool ProxySQL_PluginManager::dispatch_query_hook(ProxySQL_PluginProtocol proto,
result = cb(payload); result = cb(payload);
return true; return true;
} }
#endif /* PROXYSQL40 */
bool ProxySQL_PluginManager::register_command(const char* sql, proxysql_plugin_admin_command_cb cb) { bool ProxySQL_PluginManager::register_command(const char* sql, proxysql_plugin_admin_command_cb cb) {
if (sql == nullptr || *sql == '\0' || cb == nullptr) { if (sql == nullptr || *sql == '\0' || cb == nullptr) {
@ -636,6 +652,7 @@ bool ProxySQL_PluginManager::register_command(const char* sql, proxysql_plugin_a
return true; return true;
} }
#ifdef PROXYSQL40
bool ProxySQL_PluginManager::register_command_alias(const char* canonical_sql, const char* alias_sql) { bool ProxySQL_PluginManager::register_command_alias(const char* canonical_sql, const char* alias_sql) {
if (canonical_sql == nullptr || *canonical_sql == '\0' || if (canonical_sql == nullptr || *canonical_sql == '\0' ||
alias_sql == nullptr || *alias_sql == '\0') { alias_sql == nullptr || *alias_sql == '\0') {
@ -699,6 +716,7 @@ const char* ProxySQL_PluginManager::resolve_alias_to_canonical(const std::string
} }
return nullptr; return nullptr;
} }
#endif /* PROXYSQL40 */
ProxySQL_PluginManager* proxysql_get_plugin_manager() { ProxySQL_PluginManager* proxysql_get_plugin_manager() {
return g_active_plugin_manager.load(std::memory_order_acquire); return g_active_plugin_manager.load(std::memory_order_acquire);
@ -717,6 +735,7 @@ bool proxysql_dispatch_configured_plugin_admin_command(
return g_active_plugin_manager.load()->dispatch_admin_command(ctx, sql, result); return g_active_plugin_manager.load()->dispatch_admin_command(ctx, sql, result);
} }
#ifdef PROXYSQL40
const char* proxysql_resolve_configured_plugin_admin_alias(const std::string& sql) { const char* proxysql_resolve_configured_plugin_admin_alias(const std::string& sql) {
std::lock_guard<std::mutex> lock(g_active_plugin_manager_mutex); std::lock_guard<std::mutex> lock(g_active_plugin_manager_mutex);
ProxySQL_PluginManager* mgr = g_active_plugin_manager.load(); ProxySQL_PluginManager* mgr = g_active_plugin_manager.load();
@ -752,12 +771,14 @@ bool proxysql_has_configured_plugin_query_hook(ProxySQL_PluginProtocol proto) {
} }
return mgr->has_query_hook(proto); return mgr->has_query_hook(proto);
} }
#endif /* PROXYSQL40 */
bool proxysql_load_configured_plugins( bool proxysql_load_configured_plugins(
std::unique_ptr<ProxySQL_PluginManager>& manager, std::unique_ptr<ProxySQL_PluginManager>& manager,
const std::vector<std::string>& plugin_modules, const std::vector<std::string>& plugin_modules,
std::string& err std::string& err
) { ) {
#ifdef PROXYSQL40
// Phase A + Phase B of the four-phase lifecycle. Executed BEFORE // Phase A + Phase B of the four-phase lifecycle. Executed BEFORE
// ProxySQL_Main_init_Admin_module so that plugin-declared schemas are // ProxySQL_Main_init_Admin_module so that plugin-declared schemas are
// available when merge_plugin_tables materializes the SQLite schema. // available when merge_plugin_tables materializes the SQLite schema.
@ -805,8 +826,42 @@ bool proxysql_load_configured_plugins(
g_active_plugin_manager.store(manager.get(), std::memory_order_release); g_active_plugin_manager.store(manager.get(), std::memory_order_release);
} }
return true; return true;
#else /* !PROXYSQL40 */
// Pre-chassis two-phase: load + init_all in one call, installed as
// active manager only on full success.
err.clear();
{
std::lock_guard<std::mutex> lock(g_active_plugin_manager_mutex);
g_active_plugin_manager.store(nullptr, std::memory_order_release);
}
manager.reset();
if (plugin_modules.empty()) {
return true;
}
auto next_manager = std::make_unique<ProxySQL_PluginManager>();
for (const auto& path : plugin_modules) {
if (!next_manager->load(path, err)) {
err = path + ": " + err;
return false;
}
}
if (!next_manager->init_all(err)) {
return false;
}
{
std::lock_guard<std::mutex> lock(g_active_plugin_manager_mutex);
manager = std::move(next_manager);
g_active_plugin_manager.store(manager.get(), std::memory_order_release);
}
return true;
#endif /* PROXYSQL40 */
} }
#ifdef PROXYSQL40
bool proxysql_init_configured_plugins( bool proxysql_init_configured_plugins(
ProxySQL_PluginManager* manager, ProxySQL_PluginManager* manager,
std::string& err std::string& err
@ -821,6 +876,7 @@ bool proxysql_init_configured_plugins(
} }
return manager->init_all(err); return manager->init_all(err);
} }
#endif /* PROXYSQL40 */
bool proxysql_start_configured_plugins( bool proxysql_start_configured_plugins(
ProxySQL_PluginManager* manager, ProxySQL_PluginManager* manager,

@ -488,6 +488,7 @@ bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) {
services.register_table(stats_processlist); services.register_table(stats_processlist);
} }
#ifdef PROXYSQL40
// User-friendly alias groups that used to live as hardcoded vectors // User-friendly alias groups that used to live as hardcoded vectors
// in lib/Admin_Handler.cpp. Every group maps aliases to the same // in lib/Admin_Handler.cpp. Every group maps aliases to the same
// canonical the command was registered under. // canonical the command was registered under.
@ -553,5 +554,26 @@ bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) {
reg("SAVE MYSQLX BACKEND ENDPOINTS TO DISK", &save_backend_endpoints_to_disk, {}); reg("SAVE MYSQLX BACKEND ENDPOINTS TO DISK", &save_backend_endpoints_to_disk, {});
reg("LOAD MYSQLX VARIABLES FROM DISK", &load_variables_from_disk, {}); reg("LOAD MYSQLX VARIABLES FROM DISK", &load_variables_from_disk, {});
reg("SAVE MYSQLX VARIABLES TO DISK", &save_variables_to_disk, {}); reg("SAVE MYSQLX VARIABLES TO DISK", &save_variables_to_disk, {});
#else /* !PROXYSQL40 */
// Pre-chassis: aliases are resolved by Admin_Handler's hardcoded
// ladder; plugin only needs to register the canonical form of each
// command.
services.register_command("LOAD MYSQLX USERS TO RUNTIME", &load_users_to_runtime);
services.register_command("SAVE MYSQLX USERS TO MEMORY", &save_users_from_runtime);
services.register_command("LOAD MYSQLX ROUTES TO RUNTIME", &load_routes_to_runtime);
services.register_command("SAVE MYSQLX ROUTES TO MEMORY", &save_routes_from_runtime);
services.register_command("LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME", &load_backend_endpoints_to_runtime);
services.register_command("SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY", &save_backend_endpoints_from_runtime);
services.register_command("LOAD MYSQLX VARIABLES TO RUNTIME", &load_variables_to_runtime);
services.register_command("SAVE MYSQLX VARIABLES TO MEMORY", &save_variables_from_runtime);
services.register_command("LOAD MYSQLX USERS FROM DISK", &load_users_from_disk);
services.register_command("SAVE MYSQLX USERS TO DISK", &save_users_to_disk);
services.register_command("LOAD MYSQLX ROUTES FROM DISK", &load_routes_from_disk);
services.register_command("SAVE MYSQLX ROUTES TO DISK", &save_routes_to_disk);
services.register_command("LOAD MYSQLX BACKEND ENDPOINTS FROM DISK", &load_backend_endpoints_from_disk);
services.register_command("SAVE MYSQLX BACKEND ENDPOINTS TO DISK", &save_backend_endpoints_to_disk);
services.register_command("LOAD MYSQLX VARIABLES FROM DISK", &load_variables_from_disk);
services.register_command("SAVE MYSQLX VARIABLES TO DISK", &save_variables_to_disk);
#endif /* PROXYSQL40 */
return true; return true;
} }

@ -9,6 +9,7 @@
namespace { namespace {
#ifdef PROXYSQL40
// Phase B: declare admin-schema tables only. Runs BEFORE the admin // Phase B: declare admin-schema tables only. Runs BEFORE the admin
// module is initialized, so `services` has register_table live but the // module is initialized, so `services` has register_table live but the
// DB handle getters return nullptr. mysqlx_register_admin_schema only // DB handle getters return nullptr. mysqlx_register_admin_schema only
@ -35,6 +36,21 @@ bool mysqlx_init(ProxySQL_PluginServices* services) {
ctx.started = false; ctx.started = false;
return true; return true;
} }
#else /* !PROXYSQL40 */
// Legacy two-phase init: schema + commands registered here in one shot,
// ran at the same point in startup as pre-chassis plugin init.
bool mysqlx_init(ProxySQL_PluginServices* services) {
if (services == nullptr) {
return false;
}
MysqlxPluginContext& ctx = mysqlx_context();
ctx.services = services;
ctx.config_store = std::make_unique<MysqlxConfigStore>();
ctx.started = false;
return mysqlx_register_admin_schema(*services);
}
#endif /* PROXYSQL40 */
bool parse_bind_addr(const std::string& bind, std::string& host, int& port) { bool parse_bind_addr(const std::string& bind, std::string& host, int& port) {
if (!bind.empty() && bind[0] == '[') { if (!bind.empty() && bind[0] == '[') {
@ -236,7 +252,9 @@ const ProxySQL_PluginDescriptor mysqlx_descriptor = {
&mysqlx_start, &mysqlx_start,
&mysqlx_stop, &mysqlx_stop,
&mysqlx_status_json, &mysqlx_status_json,
#ifdef PROXYSQL40
&mysqlx_register_schemas, &mysqlx_register_schemas,
#endif /* PROXYSQL40 */
}; };
} // namespace } // namespace

@ -76,6 +76,11 @@ ifeq ($(PROXYSQLGENAI),1)
PSQLGA := -DPROXYSQLGENAI PSQLGA := -DPROXYSQLGENAI
endif endif
PSQL40 :=
ifeq ($(PROXYSQL40),1)
PSQL40 := -DPROXYSQL40
endif
PSQL31 := PSQL31 :=
ifeq ($(PROXYSQL31),1) ifeq ($(PROXYSQL31),1)
PSQL31 := -DPROXYSQL31 PSQL31 := -DPROXYSQL31
@ -97,7 +102,7 @@ MYCXXFLAGS := $(STDCPP)
ifeq ($(CXX),clang++) ifeq ($(CXX),clang++)
MYCXXFLAGS += -fuse-ld=lld MYCXXFLAGS += -fuse-ld=lld
endif endif
MYCXXFLAGS += $(IDIRS) $(OPTZ) $(DEBUG) $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) MYCXXFLAGS += $(IDIRS) $(OPTZ) $(DEBUG) $(PSQLCH) $(PSQLGA) $(PSQL40) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN)
STATICMYLIBS := -Wl,-Bstatic \ STATICMYLIBS := -Wl,-Bstatic \

@ -1497,6 +1497,7 @@ static void LoadConfiguredPlugins() {
} }
} }
#ifdef PROXYSQL40
static void InitConfiguredPlugins() { static void InitConfiguredPlugins() {
std::string plugin_error {}; std::string plugin_error {};
if (!proxysql_init_configured_plugins(GloPluginManager.get(), plugin_error)) { if (!proxysql_init_configured_plugins(GloPluginManager.get(), plugin_error)) {
@ -1504,6 +1505,7 @@ static void InitConfiguredPlugins() {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
#endif /* PROXYSQL40 */
static void StartConfiguredPlugins() { static void StartConfiguredPlugins() {
std::string plugin_error {}; std::string plugin_error {};
@ -1544,6 +1546,7 @@ void ProxySQL_Main_init_phase2___not_started(const bootstrap_info_t& boostrap_in
ProxySQL_Main_init_MCP_module(); ProxySQL_Main_init_MCP_module();
#endif /* PROXYSQLGENAI */ #endif /* PROXYSQLGENAI */
#ifdef PROXYSQL40
// Four-phase plugin lifecycle: // Four-phase plugin lifecycle:
// Phase A+B: dlopen + register_schemas (plugin-declared schemas // Phase A+B: dlopen + register_schemas (plugin-declared schemas
// populate the pending-tables list). // populate the pending-tables list).
@ -1558,6 +1561,13 @@ void ProxySQL_Main_init_phase2___not_started(const bootstrap_info_t& boostrap_in
GloAdmin->materialize_plugin_tables(); GloAdmin->materialize_plugin_tables();
InitConfiguredPlugins(); InitConfiguredPlugins();
StartConfiguredPlugins(); StartConfiguredPlugins();
#else
// Pre-chassis two-phase: admin init first, then load+init+materialize.
ProxySQL_Main_init_Admin_module(boostrap_info);
LoadConfiguredPlugins();
GloAdmin->materialize_plugin_tables();
StartConfiguredPlugins();
#endif /* PROXYSQL40 */
GloMTH->print_version(); GloMTH->print_version();
{ {

@ -26,6 +26,7 @@ ProxySQL_PluginCommandResult fake_command(const ProxySQL_PluginCommandContext&,
return {0, 1, "fake command executed"}; return {0, 1, "fake command executed"};
} }
#ifdef PROXYSQL40
ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPayload& payload) { ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPayload& payload) {
// Echo the SQL back through the message field so tests can verify the // Echo the SQL back through the message field so tests can verify the
// payload was wired through. DENY-vs-ALLOW is selected by env var so // payload was wired through. DENY-vs-ALLOW is selected by env var so
@ -40,6 +41,7 @@ ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPay
} }
return {ProxySQL_PluginQueryHookAction::allow, msg}; return {ProxySQL_PluginQueryHookAction::allow, msg};
} }
#endif /* PROXYSQL40 */
const char* env(const char* suffix) { const char* env(const char* suffix) {
static char name[128]; static char name[128];
@ -62,6 +64,7 @@ void fake_log_event(const char *event) {
std::fclose(log_file); std::fclose(log_file);
} }
#ifdef PROXYSQL40
// Phase-B callback (Step 2 chassis ABI extension). Only wired into the // Phase-B callback (Step 2 chassis ABI extension). Only wired into the
// descriptor when PROXYSQL_FAKE_PLUGIN_ENABLE_PHASE_B (or the plugin2 // descriptor when PROXYSQL_FAKE_PLUGIN_ENABLE_PHASE_B (or the plugin2
// variant) is set. Toggles via env vars: // variant) is set. Toggles via env vars:
@ -101,6 +104,7 @@ bool fake_register_schemas(ProxySQL_PluginServices *services) {
fake_log_event("phase_b"); fake_log_event("phase_b");
return true; return true;
} }
#endif /* PROXYSQL40 */
bool fake_init(ProxySQL_PluginServices *services) { bool fake_init(ProxySQL_PluginServices *services) {
fake_services = services; fake_services = services;
@ -134,6 +138,7 @@ bool fake_init(ProxySQL_PluginServices *services) {
}; };
services->register_table(table); services->register_table(table);
} }
#ifdef PROXYSQL40
if (env("REGISTER_QUERY_HOOK") != nullptr && if (env("REGISTER_QUERY_HOOK") != nullptr &&
services != nullptr && services != nullptr &&
services->register_query_hook != nullptr) { services->register_query_hook != nullptr) {
@ -144,6 +149,7 @@ bool fake_init(ProxySQL_PluginServices *services) {
} }
services->register_query_hook(proto, &fake_query_hook); services->register_query_hook(proto, &fake_query_hook);
} }
#endif /* PROXYSQL40 */
fake_log_event("init"); fake_log_event("init");
return true; return true;
} }
@ -192,6 +198,7 @@ const ProxySQL_PluginDescriptor fake_descriptor = {
&fake_status_json, &fake_status_json,
}; };
#ifdef PROXYSQL40
// Phase-B-aware descriptor: same as above but wires the register_schemas // Phase-B-aware descriptor: same as above but wires the register_schemas
// entry. Selected at plugin-discovery time when the env toggle is set. // entry. Selected at plugin-discovery time when the env toggle is set.
const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = { const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = {
@ -203,12 +210,15 @@ const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = {
&fake_status_json, &fake_status_json,
&fake_register_schemas, &fake_register_schemas,
}; };
#endif /* PROXYSQL40 */
} // namespace } // namespace
extern "C" const ProxySQL_PluginDescriptor *proxysql_plugin_descriptor_v1() { extern "C" const ProxySQL_PluginDescriptor *proxysql_plugin_descriptor_v1() {
#ifdef PROXYSQL40
if (env("ENABLE_PHASE_B") != nullptr) { if (env("ENABLE_PHASE_B") != nullptr) {
return &fake_descriptor_with_phase_b; return &fake_descriptor_with_phase_b;
} }
#endif /* PROXYSQL40 */
return &fake_descriptor; return &fake_descriptor;
} }

@ -196,6 +196,15 @@ ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c MySQLFFTO),0)
PROXYSQL31 := 1 PROXYSQL31 := 1
PSQL31 := -DPROXYSQL31 PSQL31 := -DPROXYSQL31
endif endif
# PROXYSQL40 — detected via a chassis-exclusive symbol that only exists
# when libproxysql.a was built with -DPROXYSQL40 (the four-phase plugin
# lifecycle). `invoke_register_schemas_phase` is the canonical probe.
PSQL40 :=
ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c invoke_register_schemas_phase),0)
PROXYSQL40 := 1
PSQL40 := -DPROXYSQL40
endif
PSQLTSDB := PSQLTSDB :=
ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c init_tsdb_variables),0) ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c init_tsdb_variables),0)
PROXYSQLTSDB := 1 PROXYSQLTSDB := 1
@ -210,12 +219,12 @@ ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -cw 'init_debug_struct'),0
PSQLDEBUG := -DDEBUG PSQLDEBUG := -DDEBUG
endif endif
OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(PSQLDEBUG) \ OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL40) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(PSQLDEBUG) \
-DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) \ -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) \
-Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR) -Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR)
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(PSQLDEBUG) \ OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL40) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) $(PSQLDEBUG) \
-DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN)
endif endif
@ -251,7 +260,7 @@ $(ODIR)/test_init.o: $(TEST_HELPERS_DIR)/test_init.cpp $(PROXYSQL_TEST_HEADERS)
$(CXX) -c -o $@ $< $(OPT) $(IDIRS) -Wall $(CXX) -c -o $@ $< $(OPT) $(IDIRS) -Wall
$(FAKE_PLUGIN_SO): $(TEST_HELPERS_DIR)/fake_plugin.cpp $(PROXYSQL_PATH)/include/ProxySQL_Plugin.h | $(TEST_HELPERS_DIR) $(FAKE_PLUGIN_SO): $(TEST_HELPERS_DIR)/fake_plugin.cpp $(PROXYSQL_PATH)/include/ProxySQL_Plugin.h | $(TEST_HELPERS_DIR)
$(CXX) -shared -fPIC -o $@ $< $(STDCPP) $(IDIRS) -ldl $(CXX) -shared -fPIC -o $@ $< $(STDCPP) $(PSQL40) $(IDIRS) -ldl
.PHONY: mysqlx_plugin_build .PHONY: mysqlx_plugin_build
mysqlx_plugin_build: mysqlx_plugin_build:
@ -262,7 +271,7 @@ mysqlx_plugin_build:
# Same source compiled with different macros to produce a second plugin .so # Same source compiled with different macros to produce a second plugin .so
# with a distinct plugin name and env-var prefix. Used by multi-plugin tests. # with a distinct plugin name and env-var prefix. Used by multi-plugin tests.
$(FAKE_PLUGIN2_SO): $(TEST_HELPERS_DIR)/fake_plugin.cpp $(PROXYSQL_PATH)/include/ProxySQL_Plugin.h | $(TEST_HELPERS_DIR) $(FAKE_PLUGIN2_SO): $(TEST_HELPERS_DIR)/fake_plugin.cpp $(PROXYSQL_PATH)/include/ProxySQL_Plugin.h | $(TEST_HELPERS_DIR)
$(CXX) -shared -fPIC -o $@ $< $(STDCPP) $(IDIRS) -ldl \ $(CXX) -shared -fPIC -o $@ $< $(STDCPP) $(PSQL40) $(IDIRS) -ldl \
-DFAKE_PLUGIN_NAME=\"fake_plugin2\" \ -DFAKE_PLUGIN_NAME=\"fake_plugin2\" \
-DFAKE_PLUGIN_ENV_PREFIX=\"PROXYSQL_FAKE_PLUGIN2_\" -DFAKE_PLUGIN_ENV_PREFIX=\"PROXYSQL_FAKE_PLUGIN2_\"
@ -277,7 +286,7 @@ $(LIBPROXYSQLAR): FORCE
$(MAKE) -C $(PROXYSQL_PATH)/lib libproxysql.a \ $(MAKE) -C $(PROXYSQL_PATH)/lib libproxysql.a \
PROXYSQLCLICKHOUSE=1 PROXYSQLGENAI=$(PROXYSQLGENAI) \ PROXYSQLCLICKHOUSE=1 PROXYSQLGENAI=$(PROXYSQLGENAI) \
PROXYSQLFFTO=$(PROXYSQLFFTO) PROXYSQLTSDB=$(PROXYSQLTSDB) \ PROXYSQLFFTO=$(PROXYSQLFFTO) PROXYSQLTSDB=$(PROXYSQLTSDB) \
PROXYSQL31=$(PROXYSQL31) CC=$(CC) CXX=$(CXX) PROXYSQL40=$(PROXYSQL40) PROXYSQL31=$(PROXYSQL31) CC=$(CC) CXX=$(CXX)
# =========================================================================== # ===========================================================================
@ -331,9 +340,6 @@ UNIT_TESTS := smoke_test-t query_cache_unit-t query_processor_unit-t \
glovars_unit-t \ glovars_unit-t \
plugin_manager_unit-t \ plugin_manager_unit-t \
plugin_registry_unit-t \ plugin_registry_unit-t \
plugin_query_hook_unit-t \
plugin_prometheus_unit-t \
plugin_lifecycle_unit-t \
test_mysqlx_plugin_load-t \ test_mysqlx_plugin_load-t \
mysqlx_config_store_unit-t \ mysqlx_config_store_unit-t \
test_mysqlx_admin_tables-t \ test_mysqlx_admin_tables-t \
@ -361,6 +367,15 @@ UNIT_TESTS := smoke_test-t query_cache_unit-t query_processor_unit-t \
pgsql_servers_ssl_params_unit-t \ pgsql_servers_ssl_params_unit-t \
plugin_dispatch_unit-t plugin_dispatch_unit-t
# Plugin-chassis-only unit tests — built only when libproxysql.a was
# compiled with -DPROXYSQL40 (autodetected higher up in this Makefile).
ifeq ($(PROXYSQL40),1)
UNIT_TESTS += \
plugin_query_hook_unit-t \
plugin_prometheus_unit-t \
plugin_lifecycle_unit-t
endif
.PHONY: all .PHONY: all
all: $(UNIT_TESTS) all: $(UNIT_TESTS)

@ -68,6 +68,18 @@ SQLite3DB* proxysql_plugin_get_statsdb() {
return reinterpret_cast<SQLite3DB*>(&g_fake_stats_db); return reinterpret_cast<SQLite3DB*>(&g_fake_stats_db);
} }
// PROXYSQL40 splits load+init; without the flag they run as one call.
// This helper keeps test call sites uniform across both builds.
#ifdef PROXYSQL40
static inline bool proxysql_init_configured_plugins_compat(ProxySQL_PluginManager* m, std::string& err) {
return proxysql_init_configured_plugins(m, err);
}
#else
static inline bool proxysql_init_configured_plugins_compat(ProxySQL_PluginManager*, std::string&) {
return true;
}
#endif
static void test_config_parse_single() { static void test_config_parse_single() {
GloVars.plugin_modules.clear(); GloVars.plugin_modules.clear();
Config cfg; Config cfg;
@ -145,7 +157,7 @@ static void test_lifecycle_logs_in_order() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err) && ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err) &&
proxysql_start_configured_plugins(mgr.get(), err) && proxysql_start_configured_plugins(mgr.get(), err) &&
proxysql_stop_configured_plugins(mgr, err), proxysql_stop_configured_plugins(mgr, err),
"single-plugin lifecycle helpers all succeed"); "single-plugin lifecycle helpers all succeed");
@ -164,7 +176,7 @@ static void test_multi_lifecycle_logs_in_order() {
PROXYSQL_FAKE_PLUGIN2_PATH PROXYSQL_FAKE_PLUGIN2_PATH
}; };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err) && ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err) &&
proxysql_start_configured_plugins(mgr.get(), err) && proxysql_start_configured_plugins(mgr.get(), err) &&
proxysql_stop_configured_plugins(mgr, err), proxysql_stop_configured_plugins(mgr, err),
"two-plugin lifecycle helpers all succeed"); "two-plugin lifecycle helpers all succeed");
@ -185,7 +197,7 @@ static void test_active_manager_visibility() {
std::string err; std::string err;
ok(proxysql_get_plugin_manager() == nullptr, ok(proxysql_get_plugin_manager() == nullptr,
"no active manager before load"); "no active manager before load");
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err),
"load succeeds"); "load succeeds");
ok(proxysql_get_plugin_manager() == mgr.get(), ok(proxysql_get_plugin_manager() == mgr.get(),
"after load, the active pointer matches the unique_ptr"); "after load, the active pointer matches the unique_ptr");
@ -202,12 +214,12 @@ static void test_reload_replaces_previous_manager() {
std::vector<std::string> second { PROXYSQL_FAKE_PLUGIN2_PATH }; std::vector<std::string> second { PROXYSQL_FAKE_PLUGIN2_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, first, err) && proxysql_init_configured_plugins(mgr.get(), err), "first load"); ok(proxysql_load_configured_plugins(mgr, first, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "first load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "first start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "first start");
// Reload with a different set; the helper must tear down the previous // Reload with a different set; the helper must tear down the previous
// manager (calling its destructor → no leaked plugin). // manager (calling its destructor → no leaked plugin).
ok(proxysql_load_configured_plugins(mgr, second, err) && proxysql_init_configured_plugins(mgr.get(), err), ok(proxysql_load_configured_plugins(mgr, second, err) && proxysql_init_configured_plugins_compat(mgr.get(), err),
"reload with a different plugin set succeeds"); "reload with a different plugin set succeeds");
ok(proxysql_start_configured_plugins(mgr.get(), err), "second start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "second start");
ok(proxysql_stop_configured_plugins(mgr, err), "stop helper"); ok(proxysql_stop_configured_plugins(mgr, err), "stop helper");
@ -230,9 +242,9 @@ static void test_reload_to_empty() {
std::vector<std::string> empty {}; std::vector<std::string> empty {};
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, first, err) && proxysql_init_configured_plugins(mgr.get(), err), "initial load"); ok(proxysql_load_configured_plugins(mgr, first, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "initial load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "initial start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "initial start");
ok(proxysql_load_configured_plugins(mgr, empty, err) && proxysql_init_configured_plugins(mgr.get(), err), ok(proxysql_load_configured_plugins(mgr, empty, err) && proxysql_init_configured_plugins_compat(mgr.get(), err),
"reload with empty plugin list succeeds"); "reload with empty plugin list succeeds");
ok(mgr.get() == nullptr, ok(mgr.get() == nullptr,
"empty reload nulls out the unique_ptr"); "empty reload nulls out the unique_ptr");
@ -261,18 +273,26 @@ static void test_load_partial_failure() {
} }
static void test_init_registration_failure_aborts_load() { static void test_init_registration_failure_aborts_load() {
// The fake plugin registers an invalid table from its init() callback
// (Phase D), not register_schemas (Phase B). After the four-phase split,
// load (Phase A+B) succeeds and the failure surfaces at init time.
setenv("PROXYSQL_FAKE_PLUGIN_REGISTER_INVALID_TABLE", "1", 1); setenv("PROXYSQL_FAKE_PLUGIN_REGISTER_INVALID_TABLE", "1", 1);
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
#ifdef PROXYSQL40
// The fake plugin registers an invalid table from its init() callback
// (Phase D), not register_schemas (Phase B). After the four-phase split,
// load (Phase A+B) succeeds and the failure surfaces at init time.
ok(proxysql_load_configured_plugins(mgr, paths, err), ok(proxysql_load_configured_plugins(mgr, paths, err),
"Phase A+B succeeds; the registration failure is in init, not register_schemas"); "Phase A+B succeeds; the registration failure is in init, not register_schemas");
ok(!proxysql_init_configured_plugins(mgr.get(), err), ok(!proxysql_init_configured_plugins_compat(mgr.get(), err),
"init fails when plugin's init triggers a service-registration failure"); "init fails when plugin's init triggers a service-registration failure");
ok(!err.empty(), "init helper reports a non-empty error"); ok(!err.empty(), "init helper reports a non-empty error");
#else
// Pre-chassis: init runs inside proxysql_load_configured_plugins, so
// the registration failure surfaces from the load helper directly.
ok(!proxysql_load_configured_plugins(mgr, paths, err),
"load fails when plugin's init triggers a service-registration failure");
ok(!err.empty(), "load helper reports a non-empty error");
#endif
unsetenv("PROXYSQL_FAKE_PLUGIN_REGISTER_INVALID_TABLE"); unsetenv("PROXYSQL_FAKE_PLUGIN_REGISTER_INVALID_TABLE");
} }
@ -283,7 +303,7 @@ static void test_dispatch_via_active_manager() {
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err),
"load with a plugin that registers an admin command"); "load with a plugin that registers an admin command");
ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(),
@ -316,7 +336,11 @@ static void test_dispatch_with_no_active_manager() {
} }
int main() { int main() {
plan(48); #ifdef PROXYSQL40
plan(48); // PROXYSQL40: load succeeds; init fails separately (3 oks)
#else
plan(47); // Pre-chassis: load fails with init-side registration error (2 oks)
#endif
make_log_path(); make_log_path();
test_config_parse_single(); test_config_parse_single();

@ -30,6 +30,20 @@ SQLite3DB* proxysql_plugin_get_admindb() { return reinterpret_cast<SQLite3DB*>(&
SQLite3DB* proxysql_plugin_get_configdb() { return reinterpret_cast<SQLite3DB*>(&g_fake_config_db); } SQLite3DB* proxysql_plugin_get_configdb() { return reinterpret_cast<SQLite3DB*>(&g_fake_config_db); }
SQLite3DB* proxysql_plugin_get_statsdb() { return reinterpret_cast<SQLite3DB*>(&g_fake_stats_db); } SQLite3DB* proxysql_plugin_get_statsdb() { return reinterpret_cast<SQLite3DB*>(&g_fake_stats_db); }
// Under PROXYSQL40 the loader stops at Phase B (register_schemas) and Phase D
// (init) runs separately. Without PROXYSQL40 the pre-chassis loader invokes
// init inside proxysql_load_configured_plugins, so this helper is a no-op
// that keeps the same test call sites compiling against both builds.
#ifdef PROXYSQL40
static inline bool proxysql_init_configured_plugins_compat(ProxySQL_PluginManager* m, std::string& err) {
return proxysql_init_configured_plugins(m, err);
}
#else
static inline bool proxysql_init_configured_plugins_compat(ProxySQL_PluginManager*, std::string&) {
return true;
}
#endif
static void test_dispatch_no_active_manager() { static void test_dispatch_no_active_manager() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::string err; std::string err;
@ -52,7 +66,7 @@ static void test_dispatch_after_stop() {
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err),
"load helper succeeds"); "load helper succeeds");
ok(proxysql_start_configured_plugins(mgr.get(), err), ok(proxysql_start_configured_plugins(mgr.get(), err),
"start helper succeeds"); "start helper succeeds");
@ -81,7 +95,7 @@ static void test_dispatch_unknown_command_with_active_manager() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), "load"); ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "start");
ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(),
@ -102,7 +116,7 @@ static void test_dispatch_canonicalises_input() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), "load"); ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "start");
ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(),
@ -135,7 +149,7 @@ static void test_dispatch_propagates_context() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), "load"); ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "start");
// Use a context with all-null DB pointers; dispatch should still work. // Use a context with all-null DB pointers; dispatch should still work.
@ -157,7 +171,7 @@ static void test_dispatch_concurrency() {
std::unique_ptr<ProxySQL_PluginManager> mgr; std::unique_ptr<ProxySQL_PluginManager> mgr;
std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH }; std::vector<std::string> paths { PROXYSQL_FAKE_PLUGIN_PATH };
std::string err; std::string err;
ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins(mgr.get(), err), "load"); ok(proxysql_load_configured_plugins(mgr, paths, err) && proxysql_init_configured_plugins_compat(mgr.get(), err), "load");
ok(proxysql_start_configured_plugins(mgr.get(), err), "start"); ok(proxysql_start_configured_plugins(mgr.get(), err), "start");
ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(),
@ -209,6 +223,7 @@ static void test_start_when_null_manager() {
ok(err.empty(), "start helper leaves err empty when there's nothing to start"); ok(err.empty(), "start helper leaves err empty when there's nothing to start");
} }
#ifdef PROXYSQL40
// Count invocations per plugin to prove the alias-to-canonical resolver // Count invocations per plugin to prove the alias-to-canonical resolver
// routed each alias spelling to the correct plugin's callback. // routed each alias spelling to the correct plugin's callback.
static int g_plugin_a_calls = 0; static int g_plugin_a_calls = 0;
@ -310,9 +325,14 @@ static void test_alias_dispatch_two_non_conflicting_plugins() {
ok(mgr->register_command_alias("PLUGIN_A SYNC CONFIG", "PLUGIN_A RELOAD"), ok(mgr->register_command_alias("PLUGIN_A SYNC CONFIG", "PLUGIN_A RELOAD"),
"register_command_alias is idempotent for the same canonical+alias pair"); "register_command_alias is idempotent for the same canonical+alias pair");
} }
#endif /* PROXYSQL40 */
int main() { int main() {
#ifdef PROXYSQL40
plan(50); plan(50);
#else
plan(32);
#endif
test_dispatch_no_active_manager(); test_dispatch_no_active_manager();
test_dispatch_after_stop(); test_dispatch_after_stop();
@ -322,7 +342,9 @@ int main() {
test_dispatch_concurrency(); test_dispatch_concurrency();
test_stop_when_not_loaded(); test_stop_when_not_loaded();
test_start_when_null_manager(); test_start_when_null_manager();
#ifdef PROXYSQL40
test_alias_dispatch_two_non_conflicting_plugins(); test_alias_dispatch_two_non_conflicting_plugins();
#endif
return exit_status(); return exit_status();
} }

Loading…
Cancel
Save