You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/include/ProxySQL_PluginManager.h

209 lines
9.0 KiB

#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 <cstddef>
#include <deque>
#include <memory>
#include <string>
#include <vector>
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<ProxySQL_PluginTableDef>& 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<std::string> aliases {};
#endif /* PROXYSQL40 */
};
struct registered_table_storage_t {
std::string table_name {};
std::string table_def {};
};
std::vector<plugin_handle_t> 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<ProxySQL_PluginTableDef> tables_admin_;
std::vector<ProxySQL_PluginTableDef> tables_config_;
std::vector<ProxySQL_PluginTableDef> tables_stats_;
std::deque<registered_table_storage_t> table_storage_;
std::vector<registered_command_t> 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<registered_runtime_view_t> 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<ProxySQL_PluginManager>& manager,
const std::vector<std::string>& 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<ProxySQL_PluginManager>& manager,
std::string& err
);
#endif /* PROXYSQL40 */
#endif /* PROXYSQL_PLUGIN_MANAGER_H */