Skip to content

Commit 0dc07e4

Browse files
committed
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>
1 parent 56ad23b commit 0dc07e4

5 files changed

Lines changed: 161 additions & 6 deletions

File tree

mysql-test/suite/federated/federated.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ create table t1 (a int);
1717
--replace_result $MASTER_MYPORT MASTER_PORT
1818
eval create table fed (a int) engine=Federated CONNECTION='mysql://root@127.0.0.1:$MASTER_MYPORT/test/t1';
1919
drop table t1;
20-
--error 1146,1431
20+
--error ER_NO_SUCH_TABLE,ER_GET_ERRMSG,ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST
2121
select * from fed;
2222
drop table fed;
2323

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
connect master,127.0.0.1,root,,test,$MASTER_MYPORT,;
2+
connect slave,127.0.0.1,root,,test,$SLAVE_MYPORT,;
3+
connection master;
4+
CREATE DATABASE federated;
5+
connection slave;
6+
CREATE DATABASE federated;
7+
#
8+
# MDEV-39196: INFORMATION_SCHEMA query must succeed with a
9+
# warning when a FederatedX remote table is unreachable.
10+
#
11+
connection slave;
12+
USE federated;
13+
CREATE TABLE t1 (
14+
id INT NOT NULL,
15+
name VARCHAR(64)
16+
) ENGINE=MyISAM;
17+
INSERT INTO t1 VALUES (1, 'foo');
18+
connection master;
19+
USE federated;
20+
CREATE TABLE t1 (
21+
id INT NOT NULL,
22+
name VARCHAR(64)
23+
) ENGINE=FEDERATED
24+
CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1';
25+
# Verify the federated table works before dropping remote table.
26+
SELECT * FROM federated.t1;
27+
id name
28+
1 foo
29+
# Drop the remote table to simulate unreachable/missing table.
30+
connection slave;
31+
USE federated;
32+
DROP TABLE t1;
33+
connection master;
34+
# INFORMATION_SCHEMA query must succeed and issue a warning.
35+
SELECT TABLE_NAME, TABLE_ROWS
36+
FROM information_schema.TABLES
37+
WHERE TABLE_SCHEMA = 'federated'
38+
AND TABLE_NAME = 't1';
39+
TABLE_NAME TABLE_ROWS
40+
t1 1
41+
Warnings:
42+
Warning 1430 FederatedX: Table 't1' is inaccessible: 1146 : Remote table does not exist
43+
# Warning must be present.
44+
SHOW WARNINGS;
45+
Level Code Message
46+
Warning 1430 FederatedX: Table 't1' is inaccessible: 1146 : Remote table does not exist
47+
# Cleanup.
48+
connection master;
49+
DROP TABLE IF EXISTS federated.t1;
50+
DROP DATABASE IF EXISTS federated;
51+
connection slave;
52+
DROP TABLE IF EXISTS federated.t1;
53+
DROP DATABASE IF EXISTS federated;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--source include/not_embedded.inc
2+
--source have_federatedx.inc
3+
--source include/federated.inc
4+
5+
--echo #
6+
--echo # MDEV-39196: INFORMATION_SCHEMA query must succeed with a
7+
--echo # warning when a FederatedX remote table is unreachable.
8+
--echo #
9+
10+
connection slave;
11+
USE federated;
12+
CREATE TABLE t1 (
13+
id INT NOT NULL,
14+
name VARCHAR(64)
15+
) ENGINE=MyISAM;
16+
INSERT INTO t1 VALUES (1, 'foo');
17+
18+
connection master;
19+
USE federated;
20+
--replace_result $SLAVE_MYPORT SLAVE_PORT
21+
eval CREATE TABLE t1 (
22+
id INT NOT NULL,
23+
name VARCHAR(64)
24+
) ENGINE=FEDERATED
25+
CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1';
26+
27+
--echo # Verify the federated table works before dropping remote table.
28+
SELECT * FROM federated.t1;
29+
30+
--echo # Drop the remote table to simulate unreachable/missing table.
31+
connection slave;
32+
USE federated;
33+
DROP TABLE t1;
34+
35+
connection master;
36+
--echo # INFORMATION_SCHEMA query must succeed and issue a warning.
37+
SELECT TABLE_NAME, TABLE_ROWS
38+
FROM information_schema.TABLES
39+
WHERE TABLE_SCHEMA = 'federated'
40+
AND TABLE_NAME = 't1';
41+
42+
--echo # Warning must be present.
43+
SHOW WARNINGS;
44+
45+
--echo # Cleanup.
46+
--source include/federated_cleanup.inc

storage/federatedx/federatedx_io_mysql.cc

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class federatedx_io_mysql :public federatedx_io
6868
DYNAMIC_ARRAY savepoints;
6969
bool requested_autocommit;
7070
bool actual_autocommit;
71+
int stored_error_code;
72+
char stored_error_msg[MYSQL_ERRMSG_SIZE];
7173

7274
int actual_query(const char *buffer, size_t length);
7375
bool test_all_restrict() const;
@@ -134,12 +136,14 @@ federatedx_io *instantiate_io_mysql(MEM_ROOT *server_root,
134136

135137
federatedx_io_mysql::federatedx_io_mysql(FEDERATEDX_SERVER *aserver)
136138
: federatedx_io(aserver),
137-
requested_autocommit(TRUE), actual_autocommit(TRUE)
139+
requested_autocommit(TRUE), actual_autocommit(TRUE),
140+
stored_error_code(0)
138141
{
139142
DBUG_ENTER("federatedx_io_mysql::federatedx_io_mysql");
140143

141144
bzero(&mysql, sizeof(MYSQL));
142145
bzero(&savepoints, sizeof(DYNAMIC_ARRAY));
146+
stored_error_msg[0]= 0;
143147

144148
my_init_dynamic_array(PSI_INSTRUMENT_ME, &savepoints, sizeof(SAVEPT), 16, 16, MYF(0));
145149

@@ -483,12 +487,19 @@ my_ulonglong federatedx_io_mysql::last_insert_id() const
483487

484488
int federatedx_io_mysql::error_code()
485489
{
490+
if (stored_error_code)
491+
return stored_error_code;
486492
return mysql_errno(&mysql);
487493
}
488494

489495

490496
const char *federatedx_io_mysql::error_str()
491497
{
498+
if (stored_error_code)
499+
{
500+
DBUG_ASSERT(stored_error_msg[0] != 0);
501+
return stored_error_msg;
502+
}
492503
return mysql_error(&mysql);
493504
}
494505

@@ -583,7 +594,11 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats,
583594
goto error;
584595

585596
if (!(row= fetch_row(result)))
597+
{
598+
stored_error_code= 0;
599+
stored_error_msg[0]= 0;
586600
goto error;
601+
}
587602

588603
/*
589604
deleted is set in ha_federatedx::info
@@ -618,8 +633,13 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats,
618633
error:
619634
if (!mysql_errno(&mysql))
620635
{
621-
mysql.net.last_errno= ER_NO_SUCH_TABLE;
622-
strmake_buf(mysql.net.last_error, "Remote table does not exist");
636+
stored_error_code= ER_NO_SUCH_TABLE;
637+
strmake_buf(stored_error_msg, "Remote table does not exist");
638+
}
639+
else
640+
{
641+
stored_error_code= mysql_errno(&mysql);
642+
strmake_buf(stored_error_msg, mysql_error(&mysql));
623643
}
624644
free_result(result);
625645
return 1;

storage/federatedx/ha_federatedx.cc

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,12 +3130,48 @@ int ha_federatedx::info(uint flag)
31303130
error:
31313131
if (iop && *iop)
31323132
{
3133-
my_printf_error((*iop)->error_code(), "Received error: %d : %s", MYF(0),
3134-
(*iop)->error_code(), (*iop)->error_str());
3133+
if ((flag & HA_STATUS_VARIABLE) && !(flag & HA_STATUS_NO_LOCK))
3134+
{
3135+
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
3136+
ER_QUERY_ON_FOREIGN_DATA_SOURCE,
3137+
"FederatedX: Table '%s' is inaccessible: "
3138+
"%d : %s",
3139+
share->table_name,
3140+
(*iop)->error_code(),
3141+
(*iop)->error_str());
3142+
error_code= 0;
3143+
}
3144+
else
3145+
{
3146+
my_printf_error((*iop)->error_code(),
3147+
": %d : %s", MYF(0),
3148+
(*iop)->error_code(), (*iop)->error_str());
3149+
error_code= (*iop)->error_code();
3150+
}
31353151
}
31363152
else if (remote_error_number != -1 /* error already reported */)
31373153
{
31383154
error_code= remote_error_number;
3155+
/*
3156+
* Downgrade remote errors to warnings only when called from the
3157+
* INFORMATION_SCHEMA table scan path.
3158+
*
3159+
* INFORMATION_SCHEMA scans (sql_show.cc, fill_schema_table_by_open)
3160+
* call ha_federatedx::info() with HA_STATUS_VARIABLE set but WITHOUT
3161+
* HA_STATUS_NO_LOCK. All other callers that trigger the error path
3162+
* (e.g. direct SELECT, DELETE, SHOW TABLE STATUS) pass
3163+
* HA_STATUS_NO_LOCK alongside HA_STATUS_VARIABLE.
3164+
*
3165+
* This combination is therefore used as an indirect signal that we are
3166+
* inside an I_S scan, where aborting with a hard error would break the
3167+
* entire query rather than just skipping the inaccessible table.
3168+
*
3169+
* NOTE: This is an indirect inference based on current caller conventions.
3170+
* If a future caller passes HA_STATUS_VARIABLE without HA_STATUS_NO_LOCK
3171+
* for a non-I_S purpose, it would unintentionally receive
3172+
* warning-downgrade behavior. A cleaner long-term solution would be an
3173+
* explicit flag or context distinguishing I_S scans from direct access.
3174+
*/
31393175
my_error(error_code, MYF(0), ER_THD(thd, error_code));
31403176
}
31413177
fail:

0 commit comments

Comments
 (0)