From cd48c5613e1201330b168f4b61a27969935c0456 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Sun, 19 Apr 2026 10:51:12 +0000 Subject: [PATCH] feat(plugin-chassis): gate chassis ABI additions behind PROXYSQL40 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Makefile | 25 +++- include/ProxySQL_Plugin.h | 14 +++ include/ProxySQL_PluginManager.h | 21 ++++ lib/Admin_Handler.cpp | 110 ++++++++++++++++-- lib/Makefile | 7 +- lib/ProxySQL_PluginManager.cpp | 56 +++++++++ plugins/mysqlx/src/mysqlx_admin_schema.cpp | 22 ++++ plugins/mysqlx/src/mysqlx_plugin.cpp | 18 +++ src/Makefile | 7 +- src/main.cpp | 10 ++ test/tap/test_helpers/fake_plugin.cpp | 10 ++ test/tap/tests/unit/Makefile | 31 +++-- test/tap/tests/unit/plugin_config_unit-t.cpp | 50 +++++--- .../tap/tests/unit/plugin_dispatch_unit-t.cpp | 32 ++++- 14 files changed, 373 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 062f7c91b..e2a24f0e7 100644 --- a/Makefile +++ b/Makefile @@ -53,10 +53,25 @@ lint: lint-generate-cdb lint-run ### * Advanced Anomaly Detection ### - 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) + PROXYSQL40 := 1 +endif + +# If PROXYSQL40 is enabled, it automatically enables PROXYSQL31 +ifeq ($(PROXYSQL40),1) PROXYSQL31 := 1 endif @@ -71,8 +86,9 @@ GIT_VERSION ?= $(GIT_VERSION_BASE) ifeq ($(MAKELEVEL),0) # Normalize GIT_VERSION by stripping leading 'v' for arithmetic GIT_VERSION_NORM := $(shell echo "$(GIT_VERSION_BASE)" | sed 's/^v//') -# If PROXYSQLGENAI is enabled, increment the major version number by 1 -ifeq ($(PROXYSQLGENAI),1) +# If PROXYSQL40 (or PROXYSQLGENAI, which implies it) is enabled, +# 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)}') else # If PROXYSQL31 is enabled, increment the minor version number by 1 @@ -96,6 +112,7 @@ endif export CURVER export PROXYSQLGENAI +export PROXYSQL40 export PROXYSQL31 export PROXYSQLFFTO export PROXYSQLTSDB diff --git a/include/ProxySQL_Plugin.h b/include/ProxySQL_Plugin.h index f32244c1b..238a21882 100644 --- a/include/ProxySQL_Plugin.h +++ b/include/ProxySQL_Plugin.h @@ -6,7 +6,9 @@ class SQLite3DB; class SQLite3_result; +#ifdef PROXYSQL40 namespace prometheus { class Registry; } +#endif /* PROXYSQL40 */ enum class ProxySQL_PluginDBKind : uint8_t { admin_db = 0, @@ -50,6 +52,7 @@ using proxysql_plugin_register_table_cb = using proxysql_plugin_register_command_cb = void (*)(const char *, proxysql_plugin_admin_command_cb); +#ifdef PROXYSQL40 // Register an alternative spelling (alias) of an already-registered command. // The `canonical` argument MUST match the exact SQL passed to a prior // `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. using proxysql_plugin_register_command_alias_cb = void (*)(const char *canonical, const char *alias); +#endif /* PROXYSQL40 */ using proxysql_plugin_snapshot_cb = SQLite3_result *(*)(); @@ -73,6 +77,7 @@ using proxysql_plugin_db_handle_cb = using proxysql_plugin_log_message_cb = void (*)(int, const char *); +#ifdef PROXYSQL40 // Pre-execution query hook (Step 2 ABI extension). // // 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. using proxysql_plugin_get_prometheus_registry_cb = prometheus::Registry* (*)(); +#endif /* PROXYSQL40 */ // 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_configdb; proxysql_plugin_db_handle_cb get_statsdb; +#ifdef PROXYSQL40 // Step 2 ABI extensions. Both fields are additive at the end of // the struct -- older plugins that were built against the previous // 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 // previously lived in lib/Admin_Handler.cpp. proxysql_plugin_register_command_alias_cb register_command_alias; +#endif /* PROXYSQL40 */ }; using proxysql_plugin_init_cb = @@ -197,6 +205,7 @@ using proxysql_plugin_stop_cb = using proxysql_plugin_status_json_cb = const char *(*)(); +#ifdef PROXYSQL40 // Phase B entry point: "declare your schema before admin bootstrap." // // Four-phase plugin lifecycle: @@ -214,6 +223,7 @@ using proxysql_plugin_status_json_cb = // work (the mysqlx plugin does this today). using proxysql_plugin_register_schemas_cb = bool (*)(ProxySQL_PluginServices *); +#endif /* PROXYSQL40 */ struct ProxySQL_PluginDescriptor { const char *name; @@ -222,6 +232,7 @@ struct ProxySQL_PluginDescriptor { proxysql_plugin_start_cb start; proxysql_plugin_stop_cb stop; proxysql_plugin_status_json_cb status_json; +#ifdef PROXYSQL40 /* Optional: called between load() and init(). * `services` will have register_table available but DB handle getters * (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 * leave this field null keep the pre-existing two-phase behavior. */ proxysql_plugin_register_schemas_cb register_schemas; +#endif /* PROXYSQL40 */ }; using proxysql_plugin_descriptor_v1_t = const ProxySQL_PluginDescriptor *(*)(); +#ifdef PROXYSQL40 // --------------------------------------------------------------------------- // ABI guidance: disk/memory/runtime sync — empty-source MUST still clear // 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 // transaction and check each execute() return. // --------------------------------------------------------------------------- +#endif /* PROXYSQL40 */ #endif /* PROXYSQL_PLUGIN_H */ diff --git a/include/ProxySQL_PluginManager.h b/include/ProxySQL_PluginManager.h index 0ca826696..63f1e905b 100644 --- a/include/ProxySQL_PluginManager.h +++ b/include/ProxySQL_PluginManager.h @@ -18,6 +18,7 @@ public: ProxySQL_PluginManager &operator=(const ProxySQL_PluginManager &) = delete; bool load(const std::string &path, std::string &err); +#ifdef PROXYSQL40 // Phase B of the four-phase plugin lifecycle: after all plugins have // been dlopen'd but BEFORE admin module bootstrap. Invokes each // 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. // Returns false on the first register_schemas callback that fails. bool invoke_register_schemas_phase(std::string &err); +#endif /* PROXYSQL40 */ bool init_all(std::string &err); bool start_all(std::string &err); bool stop_all(); @@ -36,6 +38,7 @@ public: bool has_command_for_test(const std::string& sql) const; bool register_table(const ProxySQL_PluginTableDef& def); bool register_command(const char* sql, proxysql_plugin_admin_command_cb cb); +#ifdef PROXYSQL40 // Register an alternate spelling (alias) of an already-registered // command. Returns true on successful registration, false 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, const ProxySQL_PluginQueryHookPayload& payload, ProxySQL_PluginQueryHookResult& result) const; +#endif /* PROXYSQL40 */ size_t size() const; @@ -62,7 +66,9 @@ private: void *handle{nullptr}; const ProxySQL_PluginDescriptor *descriptor{nullptr}; std::string path {}; +#ifdef PROXYSQL40 bool schemas_registered{false}; +#endif /* PROXYSQL40 */ bool initialized{false}; bool started{false}; bool stopped{false}; @@ -71,10 +77,12 @@ private: struct registered_command_t { std::string sql {}; proxysql_plugin_admin_command_cb cb { nullptr }; +#ifdef PROXYSQL40 // User-friendly alternate spellings for this canonical command. // Admin's dispatcher resolves any of these to `sql` before // invoking `cb`. Normalized (case + whitespace) on insertion. std::vector aliases {}; +#endif /* PROXYSQL40 */ }; struct registered_table_storage_t { @@ -84,18 +92,22 @@ private: std::vector plugins_; ProxySQL_PluginServices services_; +#ifdef PROXYSQL40 // Phase-B variant handed to register_schemas; DB-handle getters are // stubbed, everything else mirrors services_. See the contract in // ProxySQL_Plugin.h next to ProxySQL_PluginServices. ProxySQL_PluginServices services_phase_b_; +#endif /* PROXYSQL40 */ std::vector tables_admin_; std::vector tables_config_; std::vector tables_stats_; std::deque table_storage_; std::vector commands_; +#ifdef PROXYSQL40 // At most one hook per protocol; nullptr means "no hook". proxysql_plugin_query_hook_cb mysql_query_hook_ { nullptr }; proxysql_plugin_query_hook_cb pgsql_query_hook_ { nullptr }; +#endif /* PROXYSQL40 */ }; ProxySQL_PluginManager* proxysql_get_plugin_manager(); @@ -104,6 +116,7 @@ bool proxysql_dispatch_configured_plugin_admin_command( const std::string& sql, ProxySQL_PluginCommandResult& result ); +#ifdef PROXYSQL40 bool proxysql_dispatch_configured_plugin_query_hook( ProxySQL_PluginProtocol proto, const ProxySQL_PluginQueryHookPayload& payload, @@ -138,6 +151,14 @@ bool proxysql_init_configured_plugins( ProxySQL_PluginManager* manager, std::string& err ); +#else /* !PROXYSQL40 */ +// Pre-chassis two-phase load: dlopen + init_all in one call. +bool proxysql_load_configured_plugins( + std::unique_ptr& manager, + const std::vector& plugin_modules, + std::string& err +); +#endif /* PROXYSQL40 */ bool proxysql_start_configured_plugins( ProxySQL_PluginManager* manager, std::string& err diff --git a/lib/Admin_Handler.cpp b/lib/Admin_Handler.cpp index 85f545424..fc5e4e655 100644 --- a/lib/Admin_Handler.cpp +++ b/lib/Admin_Handler.cpp @@ -40,7 +40,9 @@ using json = nlohmann::json; #include "MySQL_PreparedStatement.h" #include "ProxySQL_Cluster.hpp" #include "ProxySQL_Statistics.hpp" +#ifdef PROXYSQL40 #include "ProxySQL_PluginManager.h" +#endif /* PROXYSQL40 */ #include "MySQL_Logger.hpp" #include "PgSQL_Logger.hpp" #include "MCP_Thread.h" @@ -315,14 +317,76 @@ const std::vector SAVE_TSDB_VARIABLES_TO_MEMORY = { "SAVE TSDB VARIABLES FROM RUNTIME" , "SAVE TSDB VARIABLES FROM RUN" }; -// MYSQLX plugin-owned admin commands used to live here as hardcoded -// alias vectors plus a 16-deep if-ladder in admin_handler_command_*(). -// They now live in the mysqlx plugin itself (plugins/mysqlx/src/ -// mysqlx_admin_schema.cpp) via the register_command + -// register_command_alias ABI; admin dispatch resolves via -// proxysql_resolve_configured_plugin_admin_alias(), which is -// plugin-agnostic and requires no core change for a new plugin. - +#ifndef PROXYSQL40 +// MySQLX plugin — hardcoded alias groups for admin-command dispatch. +// Under PROXYSQL40 these move into the mysqlx plugin itself (via +// register_command + register_command_alias) and the generic +// proxysql_resolve_configured_plugin_admin_alias() helper replaces the +// if-ladder below. This block exists for backward compatibility with +// builds that don't enable the plugin chassis. +const std::vector 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 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 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 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 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 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 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 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 LOAD_MYSQLX_USERS_FROM_DISK = { + "LOAD MYSQLX USERS FROM DISK" }; +const std::vector SAVE_MYSQLX_USERS_TO_DISK = { + "SAVE MYSQLX USERS TO DISK" }; +const std::vector LOAD_MYSQLX_ROUTES_FROM_DISK = { + "LOAD MYSQLX ROUTES FROM DISK" }; +const std::vector SAVE_MYSQLX_ROUTES_TO_DISK = { + "SAVE MYSQLX ROUTES TO DISK" }; +const std::vector LOAD_MYSQLX_BACKEND_ENDPOINTS_FROM_DISK = { + "LOAD MYSQLX BACKEND ENDPOINTS FROM DISK" }; +const std::vector SAVE_MYSQLX_BACKEND_ENDPOINTS_TO_DISK = { + "SAVE MYSQLX BACKEND ENDPOINTS TO DISK" }; +const std::vector LOAD_MYSQLX_VARIABLES_FROM_DISK = { + "LOAD MYSQLX VARIABLES FROM DISK" }; +const std::vector SAVE_MYSQLX_VARIABLES_TO_DISK = { + "SAVE MYSQLX VARIABLES TO DISK" }; +#endif /* !PROXYSQL40 */ +// const std::vector LOAD_COREDUMP_FROM_MEMORY = { "LOAD COREDUMP FROM MEMORY" , "LOAD COREDUMP FROM MEM" , @@ -3990,6 +4054,7 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) { //pthread_mutex_unlock(&admin_mutex); goto __run_query; } +#ifdef PROXYSQL40 // Generic plugin admin-command dispatch. Each loaded plugin // registers its commands (plus user-friendly aliases) with the // 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; } } +#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))) ) { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received LOAD or SAVE command\n"); diff --git a/lib/Makefile b/lib/Makefile index 79e59b773..60f3c5b6e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -56,6 +56,11 @@ ifeq ($(PROXYSQLGENAI),1) PSQLGA := -DPROXYSQLGENAI endif +PSQL40 := +ifeq ($(PROXYSQL40),1) + PSQL40 := -DPROXYSQL40 +endif + PSQL31 := ifeq ($(PROXYSQL31),1) PSQL31 := -DPROXYSQL31 @@ -81,7 +86,7 @@ endif 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 .PHONY: default diff --git a/lib/ProxySQL_PluginManager.cpp b/lib/ProxySQL_PluginManager.cpp index b3ccd76d3..f727144d5 100644 --- a/lib/ProxySQL_PluginManager.cpp +++ b/lib/ProxySQL_PluginManager.cpp @@ -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) { if (g_registry_target == nullptr) { proxy_warning("Plugin command-alias registration attempted outside init phase " @@ -119,6 +120,7 @@ bool register_query_hook_service(ProxySQL_PluginProtocol proto, } return true; } +#endif /* PROXYSQL40 */ SQLite3DB* get_admindb_service() { return proxysql_plugin_get_admindb(); @@ -132,6 +134,7 @@ SQLite3DB* get_statsdb_service() { return proxysql_plugin_get_statsdb(); } +#ifdef PROXYSQL40 // Phase-B stubs: during register_schemas the admin module has not yet // materialized the SQLite schema, so DB handles are deliberately nullptr. // 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() { return GloVars.prometheus_registry.get(); } +#endif /* PROXYSQL40 */ void log_message_service(int level, const char* message) { if (message == nullptr) { @@ -225,6 +229,7 @@ ProxySQL_PluginManager::ProxySQL_PluginManager() { services_.get_configdb = &get_configdb_service; services_.get_statsdb = &get_statsdb_service; services_.log_message = &log_message_service; +#ifdef PROXYSQL40 services_.register_query_hook = ®ister_query_hook_service; services_.get_prometheus_registry = &get_prometheus_registry_service; services_.register_command_alias = ®ister_command_alias_service; @@ -248,6 +253,7 @@ ProxySQL_PluginManager::ProxySQL_PluginManager() { // register_command() first, then register aliases. Since register_command // is also available during Phase B, so is register_command_alias. services_phase_b_.register_command_alias = ®ister_command_alias_service; +#endif /* PROXYSQL40 */ } ProxySQL_PluginManager::~ProxySQL_PluginManager() { @@ -316,6 +322,7 @@ bool ProxySQL_PluginManager::load(const std::string &path, std::string &err) { return true; } +#ifdef PROXYSQL40 bool ProxySQL_PluginManager::invoke_register_schemas_phase(std::string &err) { // Phase B of the four-phase lifecycle. Called after all plugins have // 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; } +#endif /* PROXYSQL40 */ bool ProxySQL_PluginManager::init_all(std::string &err) { // 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_) { bool matches = sql_equals_ci(command.sql, normalized_sql); +#ifdef PROXYSQL40 if (!matches) { for (const auto& alias : command.aliases) { if (sql_equals_ci(alias, normalized_sql)) { @@ -487,6 +496,7 @@ bool ProxySQL_PluginManager::dispatch_admin_command(const ProxySQL_PluginCommand } } } +#endif /* PROXYSQL40 */ if (!matches) { 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", command.sql.c_str(), normalized_sql.c_str()); +#ifdef PROXYSQL40 // Pass the CANONICAL form to the callback so plugins can ignore // which alias the user typed — they match on their own canonical // strings only. result = command.cb(ctx, command.sql.c_str()); +#else + result = command.cb(ctx, normalized_sql.c_str()); +#endif /* PROXYSQL40 */ return true; } @@ -575,6 +589,7 @@ bool ProxySQL_PluginManager::register_table(const ProxySQL_PluginTableDef& def) return true; } +#ifdef PROXYSQL40 bool ProxySQL_PluginManager::register_query_hook(ProxySQL_PluginProtocol proto, proxysql_plugin_query_hook_cb cb) { if (cb == nullptr) { @@ -615,6 +630,7 @@ bool ProxySQL_PluginManager::dispatch_query_hook(ProxySQL_PluginProtocol proto, result = cb(payload); return true; } +#endif /* PROXYSQL40 */ bool ProxySQL_PluginManager::register_command(const char* sql, proxysql_plugin_admin_command_cb cb) { if (sql == nullptr || *sql == '\0' || cb == nullptr) { @@ -636,6 +652,7 @@ bool ProxySQL_PluginManager::register_command(const char* sql, proxysql_plugin_a return true; } +#ifdef PROXYSQL40 bool ProxySQL_PluginManager::register_command_alias(const char* canonical_sql, const char* alias_sql) { if (canonical_sql == nullptr || *canonical_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; } +#endif /* PROXYSQL40 */ ProxySQL_PluginManager* proxysql_get_plugin_manager() { 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); } +#ifdef PROXYSQL40 const char* proxysql_resolve_configured_plugin_admin_alias(const std::string& sql) { std::lock_guard lock(g_active_plugin_manager_mutex); 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); } +#endif /* PROXYSQL40 */ bool proxysql_load_configured_plugins( std::unique_ptr& manager, const std::vector& plugin_modules, std::string& err ) { +#ifdef PROXYSQL40 // Phase A + Phase B of the four-phase lifecycle. Executed BEFORE // ProxySQL_Main_init_Admin_module so that plugin-declared schemas are // 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); } 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 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(); + 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 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( ProxySQL_PluginManager* manager, std::string& err @@ -821,6 +876,7 @@ bool proxysql_init_configured_plugins( } return manager->init_all(err); } +#endif /* PROXYSQL40 */ bool proxysql_start_configured_plugins( ProxySQL_PluginManager* manager, diff --git a/plugins/mysqlx/src/mysqlx_admin_schema.cpp b/plugins/mysqlx/src/mysqlx_admin_schema.cpp index 8921ab5b9..a0a0a6508 100644 --- a/plugins/mysqlx/src/mysqlx_admin_schema.cpp +++ b/plugins/mysqlx/src/mysqlx_admin_schema.cpp @@ -488,6 +488,7 @@ bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) { services.register_table(stats_processlist); } +#ifdef PROXYSQL40 // User-friendly alias groups that used to live as hardcoded vectors // in lib/Admin_Handler.cpp. Every group maps aliases to the same // 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("LOAD MYSQLX VARIABLES FROM DISK", &load_variables_from_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; } diff --git a/plugins/mysqlx/src/mysqlx_plugin.cpp b/plugins/mysqlx/src/mysqlx_plugin.cpp index 9cfa0d5c3..0c52d856c 100644 --- a/plugins/mysqlx/src/mysqlx_plugin.cpp +++ b/plugins/mysqlx/src/mysqlx_plugin.cpp @@ -9,6 +9,7 @@ namespace { +#ifdef PROXYSQL40 // Phase B: declare admin-schema tables only. Runs BEFORE the admin // module is initialized, so `services` has register_table live but the // DB handle getters return nullptr. mysqlx_register_admin_schema only @@ -35,6 +36,21 @@ bool mysqlx_init(ProxySQL_PluginServices* services) { ctx.started = false; 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(); + ctx.started = false; + return mysqlx_register_admin_schema(*services); +} +#endif /* PROXYSQL40 */ bool parse_bind_addr(const std::string& bind, std::string& host, int& port) { if (!bind.empty() && bind[0] == '[') { @@ -236,7 +252,9 @@ const ProxySQL_PluginDescriptor mysqlx_descriptor = { &mysqlx_start, &mysqlx_stop, &mysqlx_status_json, +#ifdef PROXYSQL40 &mysqlx_register_schemas, +#endif /* PROXYSQL40 */ }; } // namespace diff --git a/src/Makefile b/src/Makefile index 1a6f59cd7..0851a1a38 100644 --- a/src/Makefile +++ b/src/Makefile @@ -76,6 +76,11 @@ ifeq ($(PROXYSQLGENAI),1) PSQLGA := -DPROXYSQLGENAI endif +PSQL40 := +ifeq ($(PROXYSQL40),1) + PSQL40 := -DPROXYSQL40 +endif + PSQL31 := ifeq ($(PROXYSQL31),1) PSQL31 := -DPROXYSQL31 @@ -97,7 +102,7 @@ MYCXXFLAGS := $(STDCPP) ifeq ($(CXX),clang++) MYCXXFLAGS += -fuse-ld=lld 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 \ diff --git a/src/main.cpp b/src/main.cpp index 4610915f6..48fba903a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1497,6 +1497,7 @@ static void LoadConfiguredPlugins() { } } +#ifdef PROXYSQL40 static void InitConfiguredPlugins() { std::string plugin_error {}; if (!proxysql_init_configured_plugins(GloPluginManager.get(), plugin_error)) { @@ -1504,6 +1505,7 @@ static void InitConfiguredPlugins() { exit(EXIT_FAILURE); } } +#endif /* PROXYSQL40 */ static void StartConfiguredPlugins() { 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(); #endif /* PROXYSQLGENAI */ +#ifdef PROXYSQL40 // Four-phase plugin lifecycle: // Phase A+B: dlopen + register_schemas (plugin-declared schemas // 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(); InitConfiguredPlugins(); 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(); { diff --git a/test/tap/test_helpers/fake_plugin.cpp b/test/tap/test_helpers/fake_plugin.cpp index cb6b5a848..cdfe4a3e2 100644 --- a/test/tap/test_helpers/fake_plugin.cpp +++ b/test/tap/test_helpers/fake_plugin.cpp @@ -26,6 +26,7 @@ ProxySQL_PluginCommandResult fake_command(const ProxySQL_PluginCommandContext&, return {0, 1, "fake command executed"}; } +#ifdef PROXYSQL40 ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPayload& payload) { // 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 @@ -40,6 +41,7 @@ ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPay } return {ProxySQL_PluginQueryHookAction::allow, msg}; } +#endif /* PROXYSQL40 */ const char* env(const char* suffix) { static char name[128]; @@ -62,6 +64,7 @@ void fake_log_event(const char *event) { std::fclose(log_file); } +#ifdef PROXYSQL40 // Phase-B callback (Step 2 chassis ABI extension). Only wired into the // descriptor when PROXYSQL_FAKE_PLUGIN_ENABLE_PHASE_B (or the plugin2 // variant) is set. Toggles via env vars: @@ -101,6 +104,7 @@ bool fake_register_schemas(ProxySQL_PluginServices *services) { fake_log_event("phase_b"); return true; } +#endif /* PROXYSQL40 */ bool fake_init(ProxySQL_PluginServices *services) { fake_services = services; @@ -134,6 +138,7 @@ bool fake_init(ProxySQL_PluginServices *services) { }; services->register_table(table); } +#ifdef PROXYSQL40 if (env("REGISTER_QUERY_HOOK") != nullptr && services != nullptr && services->register_query_hook != nullptr) { @@ -144,6 +149,7 @@ bool fake_init(ProxySQL_PluginServices *services) { } services->register_query_hook(proto, &fake_query_hook); } +#endif /* PROXYSQL40 */ fake_log_event("init"); return true; } @@ -192,6 +198,7 @@ const ProxySQL_PluginDescriptor fake_descriptor = { &fake_status_json, }; +#ifdef PROXYSQL40 // Phase-B-aware descriptor: same as above but wires the register_schemas // entry. Selected at plugin-discovery time when the env toggle is set. const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = { @@ -203,12 +210,15 @@ const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = { &fake_status_json, &fake_register_schemas, }; +#endif /* PROXYSQL40 */ } // namespace extern "C" const ProxySQL_PluginDescriptor *proxysql_plugin_descriptor_v1() { +#ifdef PROXYSQL40 if (env("ENABLE_PHASE_B") != nullptr) { return &fake_descriptor_with_phase_b; } +#endif /* PROXYSQL40 */ return &fake_descriptor; } diff --git a/test/tap/tests/unit/Makefile b/test/tap/tests/unit/Makefile index 03fe9c5ec..de3cbb62c 100644 --- a/test/tap/tests/unit/Makefile +++ b/test/tap/tests/unit/Makefile @@ -196,6 +196,15 @@ ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c MySQLFFTO),0) PROXYSQL31 := 1 PSQL31 := -DPROXYSQL31 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 := ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -c init_tsdb_variables),0) PROXYSQLTSDB := 1 @@ -210,12 +219,12 @@ ifneq ($(shell nm $(LIBPROXYSQLAR) 2>/dev/null | grep -cw 'init_debug_struct'),0 PSQLDEBUG := -DDEBUG 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) \ -Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR) 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) endif @@ -251,7 +260,7 @@ $(ODIR)/test_init.o: $(TEST_HELPERS_DIR)/test_init.cpp $(PROXYSQL_TEST_HEADERS) $(CXX) -c -o $@ $< $(OPT) $(IDIRS) -Wall $(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 mysqlx_plugin_build: @@ -262,7 +271,7 @@ mysqlx_plugin_build: # 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. $(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_ENV_PREFIX=\"PROXYSQL_FAKE_PLUGIN2_\" @@ -277,7 +286,7 @@ $(LIBPROXYSQLAR): FORCE $(MAKE) -C $(PROXYSQL_PATH)/lib libproxysql.a \ PROXYSQLCLICKHOUSE=1 PROXYSQLGENAI=$(PROXYSQLGENAI) \ 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 \ plugin_manager_unit-t \ plugin_registry_unit-t \ - plugin_query_hook_unit-t \ - plugin_prometheus_unit-t \ - plugin_lifecycle_unit-t \ test_mysqlx_plugin_load-t \ mysqlx_config_store_unit-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 \ 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 all: $(UNIT_TESTS) diff --git a/test/tap/tests/unit/plugin_config_unit-t.cpp b/test/tap/tests/unit/plugin_config_unit-t.cpp index 1df395d05..6a8cb3d6e 100644 --- a/test/tap/tests/unit/plugin_config_unit-t.cpp +++ b/test/tap/tests/unit/plugin_config_unit-t.cpp @@ -68,6 +68,18 @@ SQLite3DB* proxysql_plugin_get_statsdb() { return reinterpret_cast(&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() { GloVars.plugin_modules.clear(); Config cfg; @@ -145,7 +157,7 @@ static void test_lifecycle_logs_in_order() { std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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_stop_configured_plugins(mgr, err), "single-plugin lifecycle helpers all succeed"); @@ -164,7 +176,7 @@ static void test_multi_lifecycle_logs_in_order() { PROXYSQL_FAKE_PLUGIN2_PATH }; 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_stop_configured_plugins(mgr, err), "two-plugin lifecycle helpers all succeed"); @@ -185,7 +197,7 @@ static void test_active_manager_visibility() { std::string err; ok(proxysql_get_plugin_manager() == nullptr, "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"); ok(proxysql_get_plugin_manager() == mgr.get(), "after load, the active pointer matches the unique_ptr"); @@ -202,12 +214,12 @@ static void test_reload_replaces_previous_manager() { std::vector second { PROXYSQL_FAKE_PLUGIN2_PATH }; 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"); // Reload with a different set; the helper must tear down the previous // 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"); ok(proxysql_start_configured_plugins(mgr.get(), err), "second start"); ok(proxysql_stop_configured_plugins(mgr, err), "stop helper"); @@ -230,9 +242,9 @@ static void test_reload_to_empty() { std::vector empty {}; 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_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"); ok(mgr.get() == nullptr, "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() { - // 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); std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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), "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"); 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"); } @@ -283,7 +303,7 @@ static void test_dispatch_via_active_manager() { std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), @@ -316,7 +336,11 @@ static void test_dispatch_with_no_active_manager() { } 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(); test_config_parse_single(); diff --git a/test/tap/tests/unit/plugin_dispatch_unit-t.cpp b/test/tap/tests/unit/plugin_dispatch_unit-t.cpp index a31968b56..51b187593 100644 --- a/test/tap/tests/unit/plugin_dispatch_unit-t.cpp +++ b/test/tap/tests/unit/plugin_dispatch_unit-t.cpp @@ -30,6 +30,20 @@ SQLite3DB* proxysql_plugin_get_admindb() { return reinterpret_cast(& SQLite3DB* proxysql_plugin_get_configdb() { return reinterpret_cast(&g_fake_config_db); } SQLite3DB* proxysql_plugin_get_statsdb() { return reinterpret_cast(&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() { std::unique_ptr mgr; std::string err; @@ -52,7 +66,7 @@ static void test_dispatch_after_stop() { std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); ok(proxysql_start_configured_plugins(mgr.get(), err), "start helper succeeds"); @@ -81,7 +95,7 @@ static void test_dispatch_unknown_command_with_active_manager() { std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), @@ -102,7 +116,7 @@ static void test_dispatch_canonicalises_input() { std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); ProxySQL_PluginCommandContext ctx { proxysql_plugin_get_admindb(), @@ -135,7 +149,7 @@ static void test_dispatch_propagates_context() { std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); // Use a context with all-null DB pointers; dispatch should still work. @@ -157,7 +171,7 @@ static void test_dispatch_concurrency() { std::unique_ptr mgr; std::vector paths { PROXYSQL_FAKE_PLUGIN_PATH }; 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"); 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"); } +#ifdef PROXYSQL40 // Count invocations per plugin to prove the alias-to-canonical resolver // routed each alias spelling to the correct plugin's callback. 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"), "register_command_alias is idempotent for the same canonical+alias pair"); } +#endif /* PROXYSQL40 */ int main() { +#ifdef PROXYSQL40 plan(50); +#else + plan(32); +#endif test_dispatch_no_active_manager(); test_dispatch_after_stop(); @@ -322,7 +342,9 @@ int main() { test_dispatch_concurrency(); test_stop_when_not_loaded(); test_start_when_null_manager(); +#ifdef PROXYSQL40 test_alias_dispatch_two_non_conflicting_plugins(); +#endif return exit_status(); }