From 0dc07e4c8f168315dd40a9a1d7c1a585785eeb07 Mon Sep 17 00:00:00 2001 From: Anway Durge <124391429+itzanway@users.noreply.github.com> Date: Sat, 28 Mar 2026 02:06:53 +0530 Subject: [PATCH] MDEV-39196: SELECT from information schema fails when FederatedX loses underlying table When a remote table is unavailable, FederatedX was passing a hard error back to the SQL layer, causing INFORMATION_SCHEMA queries to abort entirely. This patch intercepts the remote error in ha_federatedx::info, downgrades it to a warning using push_warning_printf, and includes the local table name in the warning message so the user knows which table is inaccessible. Signed-off-by: Anway Durge <124391429+itzanway@users.noreply.github.com> --- mysql-test/suite/federated/federated.test | 2 +- .../federated/federatedx_mdev39196.result | 53 +++++++++++++++++++ .../suite/federated/federatedx_mdev39196.test | 46 ++++++++++++++++ storage/federatedx/federatedx_io_mysql.cc | 26 +++++++-- storage/federatedx/ha_federatedx.cc | 40 +++++++++++++- 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/federated/federatedx_mdev39196.result create mode 100644 mysql-test/suite/federated/federatedx_mdev39196.test diff --git a/mysql-test/suite/federated/federated.test b/mysql-test/suite/federated/federated.test index 0cb0551d23e66..c6da85a96f95d 100644 --- a/mysql-test/suite/federated/federated.test +++ b/mysql-test/suite/federated/federated.test @@ -17,7 +17,7 @@ create table t1 (a int); --replace_result $MASTER_MYPORT MASTER_PORT eval create table fed (a int) engine=Federated CONNECTION='mysql://root@127.0.0.1:$MASTER_MYPORT/test/t1'; drop table t1; ---error 1146,1431 +--error ER_NO_SUCH_TABLE,ER_GET_ERRMSG,ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST select * from fed; drop table fed; diff --git a/mysql-test/suite/federated/federatedx_mdev39196.result b/mysql-test/suite/federated/federatedx_mdev39196.result new file mode 100644 index 0000000000000..271b195c29fcf --- /dev/null +++ b/mysql-test/suite/federated/federatedx_mdev39196.result @@ -0,0 +1,53 @@ +connect master,127.0.0.1,root,,test,$MASTER_MYPORT,; +connect slave,127.0.0.1,root,,test,$SLAVE_MYPORT,; +connection master; +CREATE DATABASE federated; +connection slave; +CREATE DATABASE federated; +# +# MDEV-39196: INFORMATION_SCHEMA query must succeed with a +# warning when a FederatedX remote table is unreachable. +# +connection slave; +USE federated; +CREATE TABLE t1 ( +id INT NOT NULL, +name VARCHAR(64) +) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1, 'foo'); +connection master; +USE federated; +CREATE TABLE t1 ( +id INT NOT NULL, +name VARCHAR(64) +) ENGINE=FEDERATED +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1'; +# Verify the federated table works before dropping remote table. +SELECT * FROM federated.t1; +id name +1 foo +# Drop the remote table to simulate unreachable/missing table. +connection slave; +USE federated; +DROP TABLE t1; +connection master; +# INFORMATION_SCHEMA query must succeed and issue a warning. +SELECT TABLE_NAME, TABLE_ROWS +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = 'federated' + AND TABLE_NAME = 't1'; +TABLE_NAME TABLE_ROWS +t1 1 +Warnings: +Warning 1430 FederatedX: Table 't1' is inaccessible: 1146 : Remote table does not exist +# Warning must be present. +SHOW WARNINGS; +Level Code Message +Warning 1430 FederatedX: Table 't1' is inaccessible: 1146 : Remote table does not exist +# Cleanup. +connection master; +DROP TABLE IF EXISTS federated.t1; +DROP DATABASE IF EXISTS federated; +connection slave; +DROP TABLE IF EXISTS federated.t1; +DROP DATABASE IF EXISTS federated; diff --git a/mysql-test/suite/federated/federatedx_mdev39196.test b/mysql-test/suite/federated/federatedx_mdev39196.test new file mode 100644 index 0000000000000..1848e8ccafdbe --- /dev/null +++ b/mysql-test/suite/federated/federatedx_mdev39196.test @@ -0,0 +1,46 @@ +--source include/not_embedded.inc +--source have_federatedx.inc +--source include/federated.inc + +--echo # +--echo # MDEV-39196: INFORMATION_SCHEMA query must succeed with a +--echo # warning when a FederatedX remote table is unreachable. +--echo # + +connection slave; +USE federated; +CREATE TABLE t1 ( + id INT NOT NULL, + name VARCHAR(64) +) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1, 'foo'); + +connection master; +USE federated; +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval CREATE TABLE t1 ( + id INT NOT NULL, + name VARCHAR(64) +) ENGINE=FEDERATED +CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1'; + +--echo # Verify the federated table works before dropping remote table. +SELECT * FROM federated.t1; + +--echo # Drop the remote table to simulate unreachable/missing table. +connection slave; +USE federated; +DROP TABLE t1; + +connection master; +--echo # INFORMATION_SCHEMA query must succeed and issue a warning. +SELECT TABLE_NAME, TABLE_ROWS +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = 'federated' + AND TABLE_NAME = 't1'; + +--echo # Warning must be present. +SHOW WARNINGS; + +--echo # Cleanup. +--source include/federated_cleanup.inc diff --git a/storage/federatedx/federatedx_io_mysql.cc b/storage/federatedx/federatedx_io_mysql.cc index 408c82604bbad..501f9c961dd5a 100644 --- a/storage/federatedx/federatedx_io_mysql.cc +++ b/storage/federatedx/federatedx_io_mysql.cc @@ -68,6 +68,8 @@ class federatedx_io_mysql :public federatedx_io DYNAMIC_ARRAY savepoints; bool requested_autocommit; bool actual_autocommit; + int stored_error_code; + char stored_error_msg[MYSQL_ERRMSG_SIZE]; int actual_query(const char *buffer, size_t length); bool test_all_restrict() const; @@ -134,12 +136,14 @@ federatedx_io *instantiate_io_mysql(MEM_ROOT *server_root, federatedx_io_mysql::federatedx_io_mysql(FEDERATEDX_SERVER *aserver) : federatedx_io(aserver), - requested_autocommit(TRUE), actual_autocommit(TRUE) + requested_autocommit(TRUE), actual_autocommit(TRUE), + stored_error_code(0) { DBUG_ENTER("federatedx_io_mysql::federatedx_io_mysql"); bzero(&mysql, sizeof(MYSQL)); bzero(&savepoints, sizeof(DYNAMIC_ARRAY)); + stored_error_msg[0]= 0; my_init_dynamic_array(PSI_INSTRUMENT_ME, &savepoints, sizeof(SAVEPT), 16, 16, MYF(0)); @@ -483,12 +487,19 @@ my_ulonglong federatedx_io_mysql::last_insert_id() const int federatedx_io_mysql::error_code() { + if (stored_error_code) + return stored_error_code; return mysql_errno(&mysql); } const char *federatedx_io_mysql::error_str() { + if (stored_error_code) + { + DBUG_ASSERT(stored_error_msg[0] != 0); + return stored_error_msg; + } return mysql_error(&mysql); } @@ -583,7 +594,11 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats, goto error; if (!(row= fetch_row(result))) + { + stored_error_code= 0; + stored_error_msg[0]= 0; goto error; + } /* deleted is set in ha_federatedx::info @@ -618,8 +633,13 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats, error: if (!mysql_errno(&mysql)) { - mysql.net.last_errno= ER_NO_SUCH_TABLE; - strmake_buf(mysql.net.last_error, "Remote table does not exist"); + stored_error_code= ER_NO_SUCH_TABLE; + strmake_buf(stored_error_msg, "Remote table does not exist"); + } + else + { + stored_error_code= mysql_errno(&mysql); + strmake_buf(stored_error_msg, mysql_error(&mysql)); } free_result(result); return 1; diff --git a/storage/federatedx/ha_federatedx.cc b/storage/federatedx/ha_federatedx.cc index e43ae3923165e..8b2bac504a941 100644 --- a/storage/federatedx/ha_federatedx.cc +++ b/storage/federatedx/ha_federatedx.cc @@ -3130,12 +3130,48 @@ int ha_federatedx::info(uint flag) error: if (iop && *iop) { - my_printf_error((*iop)->error_code(), "Received error: %d : %s", MYF(0), - (*iop)->error_code(), (*iop)->error_str()); + if ((flag & HA_STATUS_VARIABLE) && !(flag & HA_STATUS_NO_LOCK)) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_QUERY_ON_FOREIGN_DATA_SOURCE, + "FederatedX: Table '%s' is inaccessible: " + "%d : %s", + share->table_name, + (*iop)->error_code(), + (*iop)->error_str()); + error_code= 0; + } + else + { + my_printf_error((*iop)->error_code(), + ": %d : %s", MYF(0), + (*iop)->error_code(), (*iop)->error_str()); + error_code= (*iop)->error_code(); + } } else if (remote_error_number != -1 /* error already reported */) { error_code= remote_error_number; + /* + * Downgrade remote errors to warnings only when called from the + * INFORMATION_SCHEMA table scan path. + * + * INFORMATION_SCHEMA scans (sql_show.cc, fill_schema_table_by_open) + * call ha_federatedx::info() with HA_STATUS_VARIABLE set but WITHOUT + * HA_STATUS_NO_LOCK. All other callers that trigger the error path + * (e.g. direct SELECT, DELETE, SHOW TABLE STATUS) pass + * HA_STATUS_NO_LOCK alongside HA_STATUS_VARIABLE. + * + * This combination is therefore used as an indirect signal that we are + * inside an I_S scan, where aborting with a hard error would break the + * entire query rather than just skipping the inaccessible table. + * + * NOTE: This is an indirect inference based on current caller conventions. + * If a future caller passes HA_STATUS_VARIABLE without HA_STATUS_NO_LOCK + * for a non-I_S purpose, it would unintentionally receive + * warning-downgrade behavior. A cleaner long-term solution would be an + * explicit flag or context distinguishing I_S scans from direct access. + */ my_error(error_code, MYF(0), ER_THD(thd, error_code)); } fail: