From cf867e46fd8757d5a898bdf4e6796c8658bf42e5 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Thu, 9 Apr 2026 17:20:30 +0000 Subject: [PATCH] test/docs: update admin tables test and docs for canonical MYSQLX syntax Update test_mysqlx_admin_tables-t.cpp to use canonical LOAD/SAVE MYSQLX command syntax and add SAVE roundtrip coverage. Plan count: 23 -> 42. New test coverage: - 3 SAVE command tests verifying runtime->config data copy - 7 LOAD edge cases: empty tables, double LOAD replaces (not appends), modified config reflected after LOAD, null db handle returns error, multiple rows copied correctly - 8 alias dispatch tests: case-insensitive matching and whitespace normalization for all command variants Update wrap-up doc to list canonical command names with alias patterns. --- .../status/2026-04-07-mysqlx-plugin-wrapup.md | 9 +- test/tap/tests/test_mysqlx_admin_tables-t.cpp | 197 +++++++++++++++++- 2 files changed, 196 insertions(+), 10 deletions(-) diff --git a/docs/superpowers/status/2026-04-07-mysqlx-plugin-wrapup.md b/docs/superpowers/status/2026-04-07-mysqlx-plugin-wrapup.md index 4c5113efc..a85205882 100644 --- a/docs/superpowers/status/2026-04-07-mysqlx-plugin-wrapup.md +++ b/docs/superpowers/status/2026-04-07-mysqlx-plugin-wrapup.md @@ -42,9 +42,12 @@ Worktree: `/data/rene/proxysql/.worktrees/mysqlx-plugin-impl` ## Admin Commands -- `PLUGIN MYSQLX LOAD USERS TO RUNTIME` -- `PLUGIN MYSQLX LOAD ROUTES TO RUNTIME` -- `PLUGIN MYSQLX LOAD BACKEND ENDPOINTS TO RUNTIME` +- `LOAD MYSQLX USERS TO RUNTIME` (aliases: `TO RUN`, `FROM MEMORY`, `FROM MEM`) +- `SAVE MYSQLX USERS TO MEMORY` (aliases: `TO MEM`, `FROM RUNTIME`, `FROM RUN`) +- `LOAD MYSQLX ROUTES TO RUNTIME` (aliases: same pattern) +- `SAVE MYSQLX ROUTES TO MEMORY` (aliases: same pattern) +- `LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME` (aliases: same pattern) +- `SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY` (aliases: same pattern) ## Auth Methods Supported diff --git a/test/tap/tests/test_mysqlx_admin_tables-t.cpp b/test/tap/tests/test_mysqlx_admin_tables-t.cpp index f1be80867..c4b4a4397 100644 --- a/test/tap/tests/test_mysqlx_admin_tables-t.cpp +++ b/test/tap/tests/test_mysqlx_admin_tables-t.cpp @@ -78,7 +78,7 @@ SQLite3DB* proxysql_plugin_get_statsdb() { } int main() { - plan(20); + plan(42); ok(test_init_minimal() == 0, "minimal test globals initialize"); @@ -149,21 +149,204 @@ int main() { ProxySQL_PluginCommandContext ctx { &admin_db, &config_db, &stats_db }; ProxySQL_PluginCommandResult result { 1, 0, "" }; - ok(mgr.dispatch_admin_command(ctx, "PLUGIN MYSQLX LOAD USERS TO RUNTIME", result) && + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result) && result.error_code == 0 && admin_db.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users") == 1 && select_string(admin_db, "SELECT backend_auth_mode FROM runtime_mysqlx_users WHERE username='alice'") == "pass_through", - "PLUGIN MYSQLX LOAD USERS TO RUNTIME copies mysqlx user rows"); - ok(mgr.dispatch_admin_command(ctx, "PLUGIN MYSQLX LOAD ROUTES TO RUNTIME", result) && + "LOAD MYSQLX USERS TO RUNTIME copies mysqlx user rows"); + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX ROUTES TO RUNTIME", result) && result.error_code == 0 && admin_db.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_routes") == 1 && admin_db.return_one_int("SELECT destination_hostgroup FROM runtime_mysqlx_routes WHERE name='rw'") == 42, - "PLUGIN MYSQLX LOAD ROUTES TO RUNTIME copies route rows"); - ok(mgr.dispatch_admin_command(ctx, "PLUGIN MYSQLX LOAD BACKEND ENDPOINTS TO RUNTIME", result) && + "LOAD MYSQLX ROUTES TO RUNTIME copies route rows"); + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME", result) && result.error_code == 0 && admin_db.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_backend_endpoints") == 1 && admin_db.return_one_int("SELECT mysqlx_port FROM runtime_mysqlx_backend_endpoints WHERE hostname='db1.internal' AND mysql_port=3306") == 33060, - "PLUGIN MYSQLX LOAD BACKEND ENDPOINTS TO RUNTIME copies endpoint rows"); + "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME copies endpoint rows"); + + ok(mgr.dispatch_admin_command(ctx, "SAVE MYSQLX USERS TO MEMORY", result) && + result.error_code == 0 && + admin_db.return_one_int("SELECT COUNT(*) FROM mysqlx_users") == 1, + "SAVE MYSQLX USERS TO MEMORY copies runtime rows back to config"); + ok(mgr.dispatch_admin_command(ctx, "SAVE MYSQLX ROUTES TO MEMORY", result) && + result.error_code == 0 && + admin_db.return_one_int("SELECT COUNT(*) FROM mysqlx_routes") == 1, + "SAVE MYSQLX ROUTES TO MEMORY copies runtime rows back to config"); + ok(mgr.dispatch_admin_command(ctx, "SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY", result) && + result.error_code == 0 && + admin_db.return_one_int("SELECT COUNT(*) FROM mysqlx_backend_endpoints") == 1, + "SAVE MYSQLX BACKEND ENDPOINTS TO MEMORY copies runtime rows back to config"); + + // ---- LOAD edge cases (7) ---- + + { + SQLite3DB fresh_admin {}; + SQLite3DB fresh_config {}; + SQLite3DB fresh_stats {}; + fresh_admin.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + fresh_config.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + fresh_stats.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + build_registered_tables(fresh_admin, admin_tables); + build_registered_tables(fresh_config, config_tables); + ProxySQL_PluginCommandContext fresh_ctx { &fresh_admin, &fresh_config, &fresh_stats }; + ProxySQL_PluginCommandResult fresh_result { 1, 0, "" }; + ok(mgr.dispatch_admin_command(fresh_ctx, "LOAD MYSQLX USERS TO RUNTIME", fresh_result) && + fresh_result.error_code == 0 && + fresh_admin.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users") == 0, + "LOAD MYSQLX USERS TO RUNTIME with empty config table — runtime has 0 rows, error_code==0"); + ok(mgr.dispatch_admin_command(fresh_ctx, "LOAD MYSQLX ROUTES TO RUNTIME", fresh_result) && + fresh_result.error_code == 0 && + fresh_admin.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_routes") == 0, + "LOAD MYSQLX ROUTES TO RUNTIME with empty config table — runtime has 0 rows, error_code==0"); + ok(mgr.dispatch_admin_command(fresh_ctx, "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME", fresh_result) && + fresh_result.error_code == 0 && + fresh_admin.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_backend_endpoints") == 0, + "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME with empty config table — runtime has 0 rows, error_code==0"); + } + + admin_db.execute("INSERT INTO mysqlx_users (username, active, allowed_auth_methods, default_route, backend_auth_mode, attributes, comment) " + "VALUES ('bob', 1, 'MYSQL41', 'ro', 'mapped', '', 'second user')"); + mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result); + ok(result.error_code == 0 && + admin_db.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users") == 2, + "LOAD twice — second LOAD replaces runtime data (count is 2 not 1+2=3)"); + + admin_db.execute("UPDATE mysqlx_users SET backend_auth_mode='native' WHERE username='alice'"); + mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result); + ok(result.error_code == 0 && + select_string(admin_db, "SELECT backend_auth_mode FROM runtime_mysqlx_users WHERE username='alice'") == "native", + "LOAD after modifying config — runtime reflects new value"); + + { + ProxySQL_PluginCommandContext null_ctx { nullptr, &config_db, &stats_db }; + ProxySQL_PluginCommandResult null_result { 0, 0, "" }; + mgr.dispatch_admin_command(null_ctx, "LOAD MYSQLX USERS TO RUNTIME", null_result); + ok(null_result.error_code != 0, + "LOAD with null admindb in context — returns error_code != 0"); + } + + { + SQLite3DB multi_admin {}; + SQLite3DB multi_config {}; + SQLite3DB multi_stats {}; + multi_admin.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + multi_config.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + multi_stats.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + build_registered_tables(multi_admin, admin_tables); + build_registered_tables(multi_config, config_tables); + multi_admin.execute("INSERT INTO mysqlx_users (username, active, allowed_auth_methods, backend_auth_mode, attributes, comment) " + "VALUES ('u1', 1, 'PLAIN', 'mapped', '', '')"); + multi_admin.execute("INSERT INTO mysqlx_users (username, active, allowed_auth_methods, backend_auth_mode, attributes, comment) " + "VALUES ('u2', 1, 'MYSQL41', 'mapped', '', '')"); + multi_admin.execute("INSERT INTO mysqlx_users (username, active, allowed_auth_methods, backend_auth_mode, attributes, comment) " + "VALUES ('u3', 1, 'PLAIN', 'native', '', '')"); + ProxySQL_PluginCommandContext multi_ctx { &multi_admin, &multi_config, &multi_stats }; + ProxySQL_PluginCommandResult multi_result { 1, 0, "" }; + ok(mgr.dispatch_admin_command(multi_ctx, "LOAD MYSQLX USERS TO RUNTIME", multi_result) && + multi_result.error_code == 0 && + multi_admin.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users") == 3, + "LOAD with multiple rows — seed 3 rows, LOAD, verify runtime count == 3"); + } + + // ---- SAVE data integrity (4) ---- + + mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result); + admin_db.execute("UPDATE runtime_mysqlx_users SET backend_auth_mode='sha256' WHERE username='alice'"); + mgr.dispatch_admin_command(ctx, "SAVE MYSQLX USERS TO MEMORY", result); + ok(result.error_code == 0 && + select_string(admin_db, "SELECT backend_auth_mode FROM mysqlx_users WHERE username='alice'") == "sha256", + "SAVE after modifying runtime row — config table reflects modified value"); + + admin_db.execute("DELETE FROM mysqlx_routes"); + mgr.dispatch_admin_command(ctx, "LOAD MYSQLX ROUTES TO RUNTIME", result); + admin_db.execute("INSERT INTO runtime_mysqlx_routes (name, bind, destination_hostgroup, strategy, active, comment) " + "VALUES ('ro', '127.0.0.1:6604', 44, 'round_robin', 1, 'new route')"); + mgr.dispatch_admin_command(ctx, "SAVE MYSQLX ROUTES TO MEMORY", result); + ok(result.error_code == 0 && + admin_db.return_one_int("SELECT COUNT(*) FROM mysqlx_routes") >= 1 && + select_string(admin_db, "SELECT name FROM mysqlx_routes WHERE name='ro'") == "ro", + "SAVE after INSERT into runtime — config table has the new row"); + + { + SQLite3DB empty_admin {}; + SQLite3DB empty_config {}; + SQLite3DB empty_stats {}; + empty_admin.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + empty_config.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + empty_stats.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + build_registered_tables(empty_admin, admin_tables); + build_registered_tables(empty_config, config_tables); + empty_admin.execute("INSERT INTO mysqlx_routes (name, bind, destination_hostgroup, strategy, active, comment) " + "VALUES ('x', '0.0.0.0:1', 1, 'first_available', 1, 'temp')"); + ProxySQL_PluginCommandContext empty_ctx { &empty_admin, &empty_config, &empty_stats }; + ProxySQL_PluginCommandResult empty_result { 1, 0, "" }; + mgr.dispatch_admin_command(empty_ctx, "LOAD MYSQLX ROUTES TO RUNTIME", empty_result); + empty_admin.execute("DELETE FROM runtime_mysqlx_routes"); + mgr.dispatch_admin_command(empty_ctx, "SAVE MYSQLX ROUTES TO MEMORY", empty_result); + ok(empty_result.error_code == 0 && + empty_admin.return_one_int("SELECT COUNT(*) FROM mysqlx_routes") == 0, + "SAVE with empty runtime table — config table becomes empty (count=0)"); + } + + { + SQLite3DB rt_admin {}; + SQLite3DB rt_config {}; + SQLite3DB rt_stats {}; + rt_admin.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + rt_config.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + rt_stats.open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + build_registered_tables(rt_admin, admin_tables); + build_registered_tables(rt_config, config_tables); + rt_admin.execute("INSERT INTO mysqlx_routes (name, bind, destination_hostgroup, strategy, active, comment) " + "VALUES ('rt1', '0.0.0.0:3307', 10, 'first_available', 1, 'rt test')"); + ProxySQL_PluginCommandContext rt_ctx { &rt_admin, &rt_config, &rt_stats }; + ProxySQL_PluginCommandResult rt_result { 1, 0, "" }; + mgr.dispatch_admin_command(rt_ctx, "LOAD MYSQLX ROUTES TO RUNTIME", rt_result); + mgr.dispatch_admin_command(rt_ctx, "SAVE MYSQLX ROUTES TO MEMORY", rt_result); + rt_admin.execute("DELETE FROM mysqlx_routes"); + rt_admin.execute("INSERT INTO mysqlx_routes (name, bind, destination_hostgroup, strategy, active, comment) " + "VALUES ('rt2', '0.0.0.0:3308', 20, 'round_robin', 0, 'changed')"); + mgr.dispatch_admin_command(rt_ctx, "LOAD MYSQLX ROUTES TO RUNTIME", rt_result); + ok(rt_result.error_code == 0 && + rt_admin.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_routes") == 1 && + select_string(rt_admin, "SELECT name FROM runtime_mysqlx_routes") == "rt2", + "SAVE then LOAD roundtrip — SAVE, change config, LOAD, runtime matches re-loaded data"); + } + + // ---- Alias dispatch (8) ---- + + ok(mgr.dispatch_admin_command(ctx, "load mysqlx users to runtime", result) && + result.error_code == 0, + "lowercase 'load mysqlx users to runtime' dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result) && + result.error_code == 0, + "extra whitespace 'LOAD MYSQLX USERS TO RUNTIME' dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, " load mysqlx users to runtime ", result) && + result.error_code == 0, + "leading/trailing whitespace dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "save mysqlx users to memory", result) && + result.error_code == 0, + "lowercase 'save mysqlx users to memory' dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX ROUTES TO RUNTIME", result) && + result.error_code == 0, + "LOAD MYSQLX ROUTES TO RUNTIME dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "load mysqlx routes to runtime", result) && + result.error_code == 0, + "lowercase 'load mysqlx routes to runtime' dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME", result) && + result.error_code == 0, + "LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME dispatches correctly"); + + ok(mgr.dispatch_admin_command(ctx, "save mysqlx backend endpoints to memory", result) && + result.error_code == 0, + "whitespace-normalized 'save mysqlx backend endpoints to memory' dispatches correctly"); g_admin_db = nullptr; g_config_db = nullptr;