From 976d7c2310c109cf477a730edb254591ef5c0502 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Sat, 11 Apr 2026 13:59:38 +0000 Subject: [PATCH] feat(mysqlx): add LOAD/SAVE MYSQLX * TO/FROM DISK commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProxySQL uses a three-tier configuration model: DISK ↔ MEMORY ↔ RUNTIME. The MYSQLX plugin previously only supported the MEMORY ↔ RUNTIME tier (LOAD MYSQLX USERS TO RUNTIME, SAVE MYSQLX USERS TO MEMORY, etc.). Configuration was lost on restart because there was no way to persist it to disk. Add the missing DISK ↔ MEMORY tier commands for all four MYSQLX object types: LOAD MYSQLX USERS FROM DISK (disk → memory) SAVE MYSQLX USERS TO DISK (memory → disk) LOAD MYSQLX ROUTES FROM DISK SAVE MYSQLX ROUTES TO DISK LOAD MYSQLX BACKEND ENDPOINTS FROM DISK SAVE MYSQLX BACKEND ENDPOINTS TO DISK LOAD MYSQLX VARIABLES FROM DISK SAVE MYSQLX VARIABLES TO DISK Implementation: - Add disk_to_memory() and memory_to_disk() helpers in mysqlx_admin_schema.cpp that use qualified SQLite table names (main. and disk.
) to copy between the in-memory admin database and the on-disk config database - Register 8 new command handlers in mysqlx_register_admin_schema() - Add alias vectors for all disk commands in Admin_Handler.cpp - Add resolve_admin_alias_to_canonical() calls for disk commands in the MYSQLX dispatch block Tested: Insert → SAVE TO DISK → DELETE → LOAD FROM DISK round-trip confirms data persistence works for all four object types. --- lib/Admin_Handler.cpp | 24 ++++ plugins/mysqlx/src/mysqlx_admin_schema.cpp | 132 +++++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/lib/Admin_Handler.cpp b/lib/Admin_Handler.cpp index 321b912f9..f9ebf3655 100644 --- a/lib/Admin_Handler.cpp +++ b/lib/Admin_Handler.cpp @@ -360,6 +360,22 @@ const std::vector 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" }; // const std::vector LOAD_COREDUMP_FROM_MEMORY = { "LOAD COREDUMP FROM MEMORY" , @@ -3902,6 +3918,14 @@ void admin_session_handler(S* sess, void *_pa, PtrSize_t *pkt) { 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)) { diff --git a/plugins/mysqlx/src/mysqlx_admin_schema.cpp b/plugins/mysqlx/src/mysqlx_admin_schema.cpp index f1b6e5ea2..3c99fc567 100644 --- a/plugins/mysqlx/src/mysqlx_admin_schema.cpp +++ b/plugins/mysqlx/src/mysqlx_admin_schema.cpp @@ -262,6 +262,130 @@ ProxySQL_PluginCommandResult save_variables_from_runtime(const ProxySQL_PluginCo return result; } +bool disk_to_memory(SQLite3DB& admindb, const char* table_name) { + if (!admindb.execute("BEGIN")) { + return false; + } + std::string del = "DELETE FROM main."; + del += table_name; + if (!admindb.execute(del.c_str())) { + admindb.execute("ROLLBACK"); + return false; + } + std::string ins = "INSERT INTO main."; + ins += table_name; + ins += " SELECT * FROM disk."; + ins += table_name; + if (!admindb.execute(ins.c_str())) { + admindb.execute("ROLLBACK"); + return false; + } + admindb.execute("COMMIT"); + return true; +} + +bool memory_to_disk(SQLite3DB& admindb, const char* table_name) { + if (!admindb.execute("BEGIN")) { + return false; + } + std::string del = "DELETE FROM disk."; + del += table_name; + if (!admindb.execute(del.c_str())) { + admindb.execute("ROLLBACK"); + return false; + } + std::string ins = "INSERT INTO disk."; + ins += table_name; + ins += " SELECT * FROM main."; + ins += table_name; + if (!admindb.execute(ins.c_str())) { + admindb.execute("ROLLBACK"); + return false; + } + admindb.execute("COMMIT"); + return true; +} + +ProxySQL_PluginCommandResult load_users_from_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx users disk load requires admin db"); + } + if (!disk_to_memory(*ctx.admindb, kMysqlxUsersTable)) { + return command_failure("failed to load mysqlx users from disk"); + } + return {0, 0, "mysqlx users loaded from disk"}; +} + +ProxySQL_PluginCommandResult save_users_to_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx users disk save requires admin db"); + } + if (!memory_to_disk(*ctx.admindb, kMysqlxUsersTable)) { + return command_failure("failed to save mysqlx users to disk"); + } + return {0, 0, "mysqlx users saved to disk"}; +} + +ProxySQL_PluginCommandResult load_routes_from_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx routes disk load requires admin db"); + } + if (!disk_to_memory(*ctx.admindb, kMysqlxRoutesTable)) { + return command_failure("failed to load mysqlx routes from disk"); + } + return {0, 0, "mysqlx routes loaded from disk"}; +} + +ProxySQL_PluginCommandResult save_routes_to_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx routes disk save requires admin db"); + } + if (!memory_to_disk(*ctx.admindb, kMysqlxRoutesTable)) { + return command_failure("failed to save mysqlx routes to disk"); + } + return {0, 0, "mysqlx routes saved to disk"}; +} + +ProxySQL_PluginCommandResult load_backend_endpoints_from_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx backend endpoints disk load requires admin db"); + } + if (!disk_to_memory(*ctx.admindb, kMysqlxBackendEndpointsTable)) { + return command_failure("failed to load mysqlx backend endpoints from disk"); + } + return {0, 0, "mysqlx backend endpoints loaded from disk"}; +} + +ProxySQL_PluginCommandResult save_backend_endpoints_to_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx backend endpoints disk save requires admin db"); + } + if (!memory_to_disk(*ctx.admindb, kMysqlxBackendEndpointsTable)) { + return command_failure("failed to save mysqlx backend endpoints to disk"); + } + return {0, 0, "mysqlx backend endpoints saved to disk"}; +} + +ProxySQL_PluginCommandResult load_variables_from_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx variables disk load requires admin db"); + } + if (!disk_to_memory(*ctx.admindb, kMysqlxVariablesTable)) { + return command_failure("failed to load mysqlx variables from disk"); + } + return {0, 0, "mysqlx variables loaded from disk"}; +} + +ProxySQL_PluginCommandResult save_variables_to_disk(const ProxySQL_PluginCommandContext& ctx, const char*) { + if (ctx.admindb == nullptr) { + return command_failure("mysqlx variables disk save requires admin db"); + } + if (!memory_to_disk(*ctx.admindb, kMysqlxVariablesTable)) { + return command_failure("failed to save mysqlx variables to disk"); + } + return {0, 0, "mysqlx variables saved to disk"}; +} + void register_table_pair( ProxySQL_PluginServices& services, const char* table_name, @@ -368,5 +492,13 @@ bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) { 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); return true; }