#ifndef PROXYSQL_PLUGIN_MANAGER_H #define PROXYSQL_PLUGIN_MANAGER_H // Plugin chassis is a v4.0 feature. Including this header from a v3.x // (no PROXYSQL40) translation unit is a no-op: no class, no free // functions, nothing. Callers that intend to use the plugin manager // must guard their own code on PROXYSQL40 too. #ifdef PROXYSQL40 #include "ProxySQL_Plugin.h" #include #include #include #include #include class ProxySQL_PluginManager { public: ProxySQL_PluginManager(); ~ProxySQL_PluginManager(); ProxySQL_PluginManager(const ProxySQL_PluginManager &) = delete; 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 // that has register_table live but DB-handle getters stubbed to // 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(); const std::vector& tables(ProxySQL_PluginDBKind kind) const; bool dispatch_admin_command(const ProxySQL_PluginCommandContext& ctx, const std::string& sql, ProxySQL_PluginCommandResult& result) const; void register_table_for_test(const ProxySQL_PluginTableDef& def); bool register_command_for_test(const std::string& sql); 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 // alias would collide with another (canonical or alias) of a // different command. Duplicate registrations of the same // (canonical, alias) pair are idempotent and return true. bool register_command_alias(const char* canonical_sql, const char* alias_sql); // Resolve an incoming admin-command spelling to its canonical form. // Returns an owned copy of the canonical SQL if the query matches a // registered command or any of its aliases; an empty string // otherwise. Whitespace and case are normalized on both sides. // Returns by value (not const char*) so callers can release the // manager lock before dispatching without risking pointer // invalidation on concurrent reload. std::string resolve_alias_to_canonical(const std::string& sql) const; bool register_query_hook(ProxySQL_PluginProtocol proto, proxysql_plugin_query_hook_cb cb); bool has_query_hook(ProxySQL_PluginProtocol proto) const; bool dispatch_query_hook(ProxySQL_PluginProtocol proto, const ProxySQL_PluginQueryHookPayload& payload, ProxySQL_PluginQueryHookResult& result) const; // Runtime-view (admin-side projection of module state) plumbing. // register_runtime_view returns false if the table is already // registered or if the refresh callback is null. bool register_runtime_view(const ProxySQL_PluginRuntimeView& view); // Refresh every registered view whose table_name appears as a // case-insensitive substring of `sql`. Each refresh callback is // invoked exactly once per call, regardless of how many times its // table is mentioned. Best-effort: a callback that throws or // otherwise misbehaves is logged but does not stop other views from // refreshing. Caller supplies admindb so the chassis does not have // to reach into the global admin module. void refresh_runtime_views_for_query(const std::string& sql, SQLite3DB* admindb) const; #endif /* PROXYSQL40 */ size_t size() const; private: struct plugin_handle_t { 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}; }; 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 { std::string table_name {}; std::string table_def {}; }; 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 }; // Runtime-view registry: one entry per admin-side projection of // module state. Stored alongside an owned table_name copy so // callers may free the input string after registration. The // refresh callback pointer and opaque are plugin-owned with // static lifetime (the .so isn't unloaded while a view is live). struct registered_runtime_view_t { std::string table_name {}; void (*refresh)(SQLite3DB*, void*) { nullptr }; void* opaque { nullptr }; }; std::vector runtime_views_ {}; #endif /* PROXYSQL40 */ }; ProxySQL_PluginManager* proxysql_get_plugin_manager(); bool proxysql_dispatch_configured_plugin_admin_command( const ProxySQL_PluginCommandContext& ctx, const std::string& sql, ProxySQL_PluginCommandResult& result ); bool proxysql_dispatch_configured_plugin_query_hook( ProxySQL_PluginProtocol proto, const ProxySQL_PluginQueryHookPayload& payload, ProxySQL_PluginQueryHookResult& result ); // Fast path for hot code: returns true when the active manager has a hook // registered for the given protocol. No locks taken. Callers should still // invoke proxysql_dispatch_configured_plugin_query_hook to actually run the // hook (which takes the manager lock). Use this to elide the dispatch call // entirely on the no-plugin path. bool proxysql_has_configured_plugin_query_hook(ProxySQL_PluginProtocol proto); // Admin-side helper: consult the active plugin manager's command table and // return the canonical spelling of `sql` if it's a registered command or // alias, or an empty string otherwise. Returns by value so callers can // release the manager lock before dispatching without risking pointer // invalidation on concurrent reload. std::string proxysql_resolve_configured_plugin_admin_alias(const std::string& sql); // Admin-side helper: invoke every plugin runtime-view refresh callback // whose registered table is referenced by `sql`. Used by Admin's // pre-SELECT path, mirroring the way runtime_mysql_users is refreshed // before its SELECTs. No-op if no plugin manager is active or no views // match. Caller supplies admindb (typically the same handle Admin uses // for its own runtime_mysql_users refresh). void proxysql_refresh_configured_plugin_runtime_views(const std::string& sql, SQLite3DB* admindb); // Phase A + B of the four-phase lifecycle: dlopen() each module, read its // descriptor, then call register_schemas() on plugins that opted in. On // success, `manager` is populated AND installed as the active manager so // that ProxySQL_Admin::init() can see the declared tables and merge them // into tables_defs_{admin,config,stats} for the existing // check_and_build_standard_tables DDL pass. Phase D (init) must be // invoked separately — after admin module bootstrap — via // proxysql_init_configured_plugins. bool proxysql_load_configured_plugins( std::unique_ptr& manager, const std::vector& plugin_modules, std::string& err ); // Phase D: call each plugin's init() with full services (live DB handles). // Must run after ProxySQL_Main_init_Admin_module so init() sees live // admindb/configdb/statsdb with plugin-owned tables already materialized. bool proxysql_init_configured_plugins( ProxySQL_PluginManager* manager, std::string& err ); bool proxysql_start_configured_plugins( ProxySQL_PluginManager* manager, std::string& err ); bool proxysql_stop_configured_plugins( std::unique_ptr& manager, std::string& err ); #endif /* PROXYSQL40 */ #endif /* PROXYSQL_PLUGIN_MANAGER_H */