Skip to content

Commit 9dd3108

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 b4bc43e commit 9dd3108

5 files changed

Lines changed: 186 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_FOREIGN_DATA_SOURCE_DOESNT_EXIST
2121
select * from fed;
2222
drop table fed;
2323

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
# Test that INFORMATION_SCHEMA queries succeed with a warning
9+
# when a FederatedX remote table is unreachable, instead of
10+
# returning a hard error.
11+
#
12+
connection slave;
13+
USE federated;
14+
CREATE TABLE t1 (
15+
id INT NOT NULL,
16+
name VARCHAR(64)
17+
) ENGINE=MyISAM;
18+
INSERT INTO t1 VALUES (1, 'foo');
19+
connection master;
20+
USE federated;
21+
CREATE TABLE t1 (
22+
id INT NOT NULL,
23+
name VARCHAR(64)
24+
) ENGINE=FEDERATED
25+
CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1';
26+
# Verify the federated table works before dropping remote table.
27+
SELECT * FROM t1;
28+
id name
29+
1 foo
30+
# Drop the remote table to simulate an unreachable/missing table.
31+
connection slave;
32+
USE federated;
33+
DROP TABLE t1;
34+
connection master;
35+
# Now SELECT from INFORMATION_SCHEMA should succeed and push a
36+
# warning instead of returning a hard error.
37+
USE federated;
38+
# The query must succeed (not error out).
39+
SELECT TABLE_NAME, TABLE_ROWS
40+
FROM information_schema.TABLES
41+
WHERE TABLE_SCHEMA = 'federated';
42+
TABLE_NAME TABLE_ROWS
43+
t1 NULL
44+
Warnings:
45+
Warning 1296 FederatedX: Table 't1' is inaccessible: 1146 : Table 'federated.t1' doesn't exist
46+
# Confirm a warning was issued for the inaccessible table.
47+
SHOW WARNINGS;
48+
Level Code Message
49+
Warning 1296 FederatedX: Table 't1' is inaccessible: 1146 : Table 'federated.t1' doesn't exist
50+
# Cleanup
51+
DROP TABLE IF EXISTS t1;
52+
DROP DATABASE federated;
53+
connection slave;
54+
DROP DATABASE IF EXISTS federated;
55+
connection master;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# MDEV-39196: SELECT from information_schema fails when FederatedX
2+
# loses underlying table.
3+
--source include/not_embedded.inc
4+
--source include/federated.inc
5+
6+
--echo #
7+
--echo # Test that INFORMATION_SCHEMA queries succeed with a warning
8+
--echo # when a FederatedX remote table is unreachable, instead of
9+
--echo # returning a hard error.
10+
--echo #
11+
12+
--echo connection slave;
13+
connection slave;
14+
--echo USE federated;
15+
USE federated;
16+
--echo CREATE TABLE t1 (
17+
--echo id INT NOT NULL,
18+
--echo name VARCHAR(64)
19+
--echo ) ENGINE=MyISAM;
20+
CREATE TABLE t1 (
21+
id INT NOT NULL,
22+
name VARCHAR(64)
23+
) ENGINE=MyISAM;
24+
--echo INSERT INTO t1 VALUES (1, 'foo');
25+
INSERT INTO t1 VALUES (1, 'foo');
26+
27+
--echo connection master;
28+
connection master;
29+
--echo USE federated;
30+
USE federated;
31+
--replace_result $SLAVE_MYPORT SLAVE_PORT
32+
--echo CREATE TABLE t1 (
33+
--echo id INT NOT NULL,
34+
--echo name VARCHAR(64)
35+
--echo ) ENGINE=FEDERATED
36+
--echo CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1';
37+
eval CREATE TABLE t1 (
38+
id INT NOT NULL,
39+
name VARCHAR(64)
40+
) ENGINE=FEDERATED
41+
CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1';
42+
43+
--echo # Verify the federated table works before dropping remote table.
44+
SELECT * FROM t1;
45+
46+
--echo # Drop the remote table to simulate an unreachable/missing table.
47+
--echo connection slave;
48+
connection slave;
49+
--echo USE federated;
50+
USE federated;
51+
--echo DROP TABLE t1;
52+
DROP TABLE t1;
53+
54+
--echo connection master;
55+
connection master;
56+
--echo # Now SELECT from INFORMATION_SCHEMA should succeed and push a
57+
--echo # warning instead of returning a hard error.
58+
--echo USE federated;
59+
USE federated;
60+
--echo # The query must succeed (not error out).
61+
SELECT TABLE_NAME, TABLE_ROWS
62+
FROM information_schema.TABLES
63+
WHERE TABLE_SCHEMA = 'federated';
64+
65+
--echo # Confirm a warning was issued for the inaccessible table.
66+
SHOW WARNINGS;
67+
68+
--echo # Cleanup
69+
--echo DROP TABLE IF EXISTS t1;
70+
DROP TABLE IF EXISTS t1;
71+
--echo DROP DATABASE federated;
72+
DROP DATABASE federated;
73+
--echo connection slave;
74+
connection slave;
75+
--echo DROP DATABASE IF EXISTS federated;
76+
DROP DATABASE IF EXISTS federated;
77+
--echo connection master;
78+
connection master;

storage/federatedx/federatedx_io_mysql.cc

Lines changed: 20 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+
bzero(stored_error_msg, sizeof(stored_error_msg));
143147

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

@@ -483,12 +487,16 @@ 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+
return stored_error_msg;
492500
return mysql_error(&mysql);
493501
}
494502

@@ -583,7 +591,11 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats,
583591
goto error;
584592

585593
if (!(row= fetch_row(result)))
594+
{
595+
stored_error_code= 0;
596+
bzero(stored_error_msg, sizeof(stored_error_msg));
586597
goto error;
598+
}
587599

588600
/*
589601
deleted is set in ha_federatedx::info
@@ -618,8 +630,13 @@ bool federatedx_io_mysql::table_metadata(ha_statistics *stats,
618630
error:
619631
if (!mysql_errno(&mysql))
620632
{
621-
mysql.net.last_errno= ER_NO_SUCH_TABLE;
622-
strmake_buf(mysql.net.last_error, "Remote table does not exist");
633+
stored_error_code= ER_NO_SUCH_TABLE;
634+
strmake_buf(stored_error_msg, "Remote table does not exist");
635+
}
636+
else
637+
{
638+
stored_error_code= mysql_errno(&mysql);
639+
strmake_buf(stored_error_msg, mysql_error(&mysql));
623640
}
624641
free_result(result);
625642
return 1;

storage/federatedx/ha_federatedx.cc

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,8 +3130,38 @@ 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+
uint remote_err= (uint)(*iop)->error_code();
3134+
/*
3135+
Only downgrade to a warning for errors that mean the remote server
3136+
or table is temporarily unreachable (connection failure, table
3137+
dropped on remote side). All other errors including access-denied
3138+
must remain hard errors so callers receive the correct errno.
3139+
*/
3140+
switch (remote_err)
3141+
{
3142+
case 2002: /* CR_CONNECTION_ERROR - can't connect via socket */
3143+
case 2003: /* CR_CONN_HOST_ERROR - can't connect to host */
3144+
case 2005: /* CR_UNKNOWN_HOST - unknown host */
3145+
case 2006: /* CR_SERVER_GONE_ERROR - server has gone away */
3146+
case 2013: /* CR_SERVER_LOST - lost connection during query */
3147+
case 1146: /* ER_NO_SUCH_TABLE - remote table dropped */
3148+
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
3149+
ER_QUERY_ON_FOREIGN_DATA_SOURCE,
3150+
"FederatedX: Table '%s' is inaccessible: "
3151+
"%d : %s",
3152+
share->table_name,
3153+
(*iop)->error_code(),
3154+
(*iop)->error_str());
3155+
error_code= 0;
3156+
break;
3157+
default:
3158+
my_printf_error((*iop)->error_code(),
3159+
"Received error: %d : %s", MYF(0),
3160+
(*iop)->error_code(),
3161+
(*iop)->error_str());
3162+
error_code= (*iop)->error_code();
3163+
break;
3164+
}
31353165
}
31363166
else if (remote_error_number != -1 /* error already reported */)
31373167
{

0 commit comments

Comments
 (0)