mysql_fdw patch.

1: Re-establish mysql_fdw connections after server or user mapping changes
2: Qualify table name with the database name while identifying a unique column
3: Fix crash with ANALYZE on a non-existing remote table.
This commit is contained in:
TotaJ
2020-07-16 19:07:18 +08:00
parent 74b560d2c0
commit 769714f7bb
5 changed files with 620 additions and 39 deletions

View File

@ -48,6 +48,14 @@ define create_mysql_fdw_sources
rm -rf $(MYSQL_FDW_MEGRED_SOURCES_DIR); \
mkdir $(MYSQL_FDW_MEGRED_SOURCES_DIR); \
tar xfzv $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PACKAGE).tar.gz -C $(MYSQL_FDW_MEGRED_SOURCES_DIR) &> /dev/null; \
for ((i=1;i<=99;i++)); \
do \
file_name="$(MYSQL_FDW_DIR)/$$i-mysql_fdw-2.5.3_patch.patch"; \
if [ ! -f "$$file_name" ]; then \
exit 0; \
fi; \
patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $$file_name &> /dev/null; \
done
rename ".c" ".cpp" $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE)/*.c; \
patch -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PATCH).patch &> /dev/null;
patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PATCH).patch &> /dev/null;
endef

View File

@ -0,0 +1,250 @@
diff --git connection.c connection.c
index cb9b90d..0fdd890 100644
--- connection.c
+++ connection.c
@@ -23,8 +23,10 @@
#include "mpg_wchar.h"
#include "miscadmin.h"
#include "utils/hsearch.h"
+#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
+#include "utils/syscache.h"
/* Length of host */
#define HOST_LEN 256
@@ -46,6 +48,9 @@ typedef struct ConnCacheEntry
{
ConnCacheKey key; /* hash key (must be first) */
MYSQL *conn; /* connection to foreign server, or NULL */
+ bool invalidated; /* true if reconnect is pending */
+ uint32 server_hashvalue; /* hash value of foreign server OID */
+ uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
/*
@@ -53,6 +58,8 @@ typedef struct ConnCacheEntry
*/
static HTAB *ConnectionHash = NULL;
+static void mysql_inval_callback(Datum arg, int cacheid, uint32 hashvalue);
+
/*
* mysql_get_connection:
* Get a connection which can be used to execute queries on
@@ -80,6 +87,15 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
ConnectionHash = hash_create("mysql_fdw connections", 8,
&ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+ /*
+ * Register some callback functions that manage connection cleanup.
+ * This should be done just once in each backend.
+ */
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+ mysql_inval_callback, (Datum) 0);
+ CacheRegisterSyscacheCallback(USERMAPPINGOID,
+ mysql_inval_callback, (Datum) 0);
}
/* Create hash key for the entry. Assume no pad bytes in key struct */
@@ -95,8 +111,22 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
/* initialize new hashtable entry (key is already filled in) */
entry->conn = NULL;
}
+
+ /* If an existing entry has invalid connection then release it */
+ if (entry->conn != NULL && entry->invalidated)
+ {
+ elog(DEBUG3, "disconnecting mysql_fdw connection %p for option changes to take effect",
+ entry->conn);
+ _mysql_close(entry->conn);
+ entry->conn = NULL;
+ }
+
if (entry->conn == NULL)
{
+#if PG_VERSION_NUM < 90600
+ Oid umoid;
+#endif
+
entry->conn = mysql_connect(
opt->svr_address,
opt->svr_username,
@@ -113,6 +143,35 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
);
elog(DEBUG3, "new mysql_fdw connection %p for server \"%s\"",
entry->conn, server->servername);
+
+ /*
+ * Once the connection is established, then set the connection
+ * invalidation flag to false, also set the server and user mapping
+ * hash values.
+ */
+ entry->invalidated = false;
+ entry->server_hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+#if PG_VERSION_NUM >= 90600
+ entry->mapping_hashvalue =
+ GetSysCacheHashValue1(USERMAPPINGOID,
+ ObjectIdGetDatum(user->umid));
+#else
+ /* Pre-9.6, UserMapping doesn't store its OID, so look it up again */
+ umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER,
+ ObjectIdGetDatum(user->userid),
+ ObjectIdGetDatum(user->serverid));
+ if (!OidIsValid(umoid))
+ {
+ /* Not found for the specific user -- try PUBLIC */
+ umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER,
+ ObjectIdGetDatum(InvalidOid),
+ ObjectIdGetDatum(user->serverid));
+ }
+ entry->mapping_hashvalue =
+ GetSysCacheHashValue1(USERMAPPINGOID, ObjectIdGetDatum(umoid));
+#endif
}
return entry->conn;
}
@@ -233,3 +292,36 @@ mysql_connect(
return conn;
}
+
+/*
+ * Connection invalidation callback function for mysql.
+ *
+ * After a change to a pg_foreign_server or pg_user_mapping catalog entry,
+ * mark connections depending on that entry as needing to be remade. This
+ * implementation is similar as pgfdw_inval_callback.
+ */
+static void
+mysql_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+
+ /* ConnectionHash must exist already, if we're registered */
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /* Ignore invalid entries */
+ if (entry->conn == NULL)
+ continue;
+
+ /* hashvalue == 0 means a cache reset, must clear all state */
+ if (hashvalue == 0 ||
+ (cacheid == FOREIGNSERVEROID &&
+ entry->server_hashvalue == hashvalue) ||
+ (cacheid == USERMAPPINGOID &&
+ entry->mapping_hashvalue == hashvalue))
+ entry->invalidated = true;
+ }
+}
diff --git expected/mysql_fdw.out expected/mysql_fdw.out
index aca67e4..6048936 100644
--- expected/mysql_fdw.out
+++ expected/mysql_fdw.out
@@ -1,7 +1,11 @@
+\set MYSQL_HOST '\'localhost\''
+\set MYSQL_PORT '\'3306\''
+\set MYSQL_USER_NAME '\'foo\''
+\set MYSQL_PASS '\'bar\''
\c postgres postgres
CREATE EXTENSION mysql_fdw;
-CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw;
-CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username 'foo', password 'bar');
+CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT);;
+CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS);
CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'department');
CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee');
CREATE FOREIGN TABLE empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'empdata');
@@ -362,6 +366,36 @@ SELECT test_param_where2(1, 'One');
1
(1 row)
+-- FDW-121: After a change to a pg_foreign_server or pg_user_mapping catalog
+-- entry, existing connection should be invalidated and should make new
+-- connection using the updated connection details.
+-- Alter SERVER option.
+-- Set wrong host, subsequent operation on this server should use updated
+-- details and fail as the host address is not correct.
+ALTER SERVER mysql_svr OPTIONS (SET host 'localhos');
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+ERROR: failed to connect to MySQL: Unknown MySQL server host 'localhos' (2)
+-- Set the correct hostname, next operation should succeed.
+ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST);
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+ a | b
+---+-----
+ 1 | One
+(1 row)
+
+-- Alter USER MAPPING option.
+-- Set wrong username and password, next operation should fail.
+ALTER USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(SET username 'foo1', SET password 'bar1');
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+ERROR: failed to connect to MySQL: Access denied for user 'foo1'@'localhost' (using password: YES)
+-- Set correct username and password, next operation should succeed.
+ALTER USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(SET username :MYSQL_USER_NAME, SET password :MYSQL_PASS);
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+ a | b
+---+-----
+ 1 | One
+(1 row)
+
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;
diff --git sql/mysql_fdw.sql sql/mysql_fdw.sql
index 78efca8..4fd7ce3 100644
--- sql/mysql_fdw.sql
+++ sql/mysql_fdw.sql
@@ -1,7 +1,12 @@
+\set MYSQL_HOST '\'localhost\''
+\set MYSQL_PORT '\'3306\''
+\set MYSQL_USER_NAME '\'foo\''
+\set MYSQL_PASS '\'bar\''
+
\c postgres postgres
CREATE EXTENSION mysql_fdw;
-CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw;
-CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username 'foo', password 'bar');
+CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT);;
+CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS);
CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'department');
CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee');
@@ -82,6 +87,29 @@ create or replace function test_param_where2(integer, text) returns integer as '
SELECT test_param_where2(1, 'One');
+-- FDW-121: After a change to a pg_foreign_server or pg_user_mapping catalog
+-- entry, existing connection should be invalidated and should make new
+-- connection using the updated connection details.
+
+-- Alter SERVER option.
+-- Set wrong host, subsequent operation on this server should use updated
+-- details and fail as the host address is not correct.
+ALTER SERVER mysql_svr OPTIONS (SET host 'localhos');
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+
+-- Set the correct hostname, next operation should succeed.
+ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST);
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+
+-- Alter USER MAPPING option.
+-- Set wrong username and password, next operation should fail.
+ALTER USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(SET username 'foo1', SET password 'bar1');
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+
+-- Set correct username and password, next operation should succeed.
+ALTER USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(SET username :MYSQL_USER_NAME, SET password :MYSQL_PASS);
+SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;

View File

@ -0,0 +1,190 @@
diff --git expected/mysql_fdw.out expected/mysql_fdw.out
index 6048936..2d77913 100644
--- expected/mysql_fdw.out
+++ expected/mysql_fdw.out
@@ -10,6 +10,9 @@ CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER
CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee');
CREATE FOREIGN TABLE empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'empdata');
CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb', table_name 'numbers');
+CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'student');
+CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student');
+CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'numbers');
SELECT * FROM department LIMIT 10;
department_id | department_name
---------------+-----------------
@@ -396,6 +399,46 @@ SELECT * FROM numbers ORDER BY 1 LIMIT 1;
1 | One
(1 row)
+-- FDW-126: Insert/update/delete statement failing in mysql_fdw by picking
+-- wrong database name.
+-- Verify the INSERT/UPDATE/DELETE operations on another foreign table which
+-- resides in the another database in MySQL. The previous commands performs
+-- the operation on foreign table created for tables in testdb MySQL database.
+-- Below operations will be performed for foreign table created for table in
+-- testdb1 MySQL database.
+INSERT INTO fdw126_ft1 VALUES(1, 'One');
+UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1;
+DELETE FROM fdw126_ft1 WHERE stu_id = 1;
+-- Select on employee foreign table which is created for employee table from
+-- testdb MySQL database. This call is just to cross verify if everything is
+-- working correctly.
+SELECT * FROM employee ORDER BY 1 LIMIT 1;
+ emp_id | emp_name | emp_dept_id
+--------+----------+-------------
+ 1 | emp - 1 | 1
+(1 row)
+
+-- Insert into fdw126_ft2 table which does not have dbname specified while
+-- creating the foreign table, so it will consider the schema name of foreign
+-- table as database name and try to connect/lookup into that database. Will
+-- throw an error.
+INSERT INTO fdw126_ft2 VALUES(2, 'Two');
+ERROR: failed to execute the MySQL query:
+Unknown database 'public'
+-- Check with the same table name from different database. fdw126_ft3 is
+-- pointing to the testdb1.numbers and not testdb.numbers table.
+-- INSERT/UPDATE/DELETE should be failing. SELECT will return no rows.
+INSERT INTO fdw126_ft3 VALUES(1, 'One');
+ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation
+SELECT * FROM fdw126_ft3 ORDER BY 1 LIMIT 1;
+ a | b
+---+---
+(0 rows)
+
+UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1;
+ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation
+DELETE FROM fdw126_ft3 WHERE a = 1;
+ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;
@@ -406,6 +449,9 @@ DROP FOREIGN TABLE numbers;
DROP FOREIGN TABLE department;
DROP FOREIGN TABLE employee;
DROP FOREIGN TABLE empdata;
+DROP FOREIGN TABLE fdw126_ft1;
+DROP FOREIGN TABLE fdw126_ft2;
+DROP FOREIGN TABLE fdw126_ft3;
DROP USER MAPPING FOR postgres SERVER mysql_svr;
DROP SERVER mysql_svr;
DROP EXTENSION mysql_fdw CASCADE;
diff --git mysql_fdw.c mysql_fdw.c
index 2e520c2..eaba2e3 100644
--- mysql_fdw.c
+++ mysql_fdw.c
@@ -964,7 +964,12 @@ mysql_is_column_unique(Oid foreigntableid)
/* Build the query */
initStringInfo(&sql);
- appendStringInfo(&sql, "EXPLAIN %s", options->svr_table);
+ /*
+ * Construct the query by prefixing the database name so that it can lookup
+ * in correct database.
+ */
+ appendStringInfo(&sql, "EXPLAIN %s.%s", options->svr_database,
+ options->svr_table);
if (_mysql_query(conn, sql.data) != 0)
{
switch(_mysql_errno(conn))
diff --git mysql_init.sh mysql_init.sh
index a970f19..bea095d 100644
--- mysql_init.sh
+++ mysql_init.sh
@@ -4,4 +4,6 @@ mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE department(department_id in
mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE employee(emp_id int, emp_name text, emp_dept_id int, PRIMARY KEY (emp_id))"
mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE empdata (emp_id int, emp_dat blob, PRIMARY KEY (emp_id))"
mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE numbers (a int PRIMARY KEY, b varchar(255))"
-
+mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE DATABASE testdb1"
+mysql -h 127.0.0.1 -u foo -D testdb1 -e "CREATE TABLE student (stu_id int PRIMARY KEY, stu_name text)"
+mysql -h 127.0.0.1 -u foo -D testdb1 -e "CREATE TABLE numbers (a int, b varchar(255))"
diff --git option.c option.c
index 880d984..574cb24 100644
--- option.c
+++ option.c
@@ -254,8 +254,19 @@ mysql_get_options(Oid foreignoid)
if (!opt->svr_port)
opt->svr_port = MYSQL_PORT;
- if (!opt->svr_table && f_table)
- opt->svr_table = get_rel_name(foreignoid);
+ /*
+ * When we don't have a table name or database name provided in the
+ * FOREIGN TABLE options, then use a foreign table name as the target table
+ * name and the namespace of the foreign table as a database name.
+ */
+ if (f_table)
+ {
+ if (!opt->svr_table)
+ opt->svr_table = get_rel_name(foreignoid);
+
+ if (!opt->svr_database)
+ opt->svr_database = get_namespace_name(get_rel_namespace(foreignoid));
+ }
return opt;
}
diff --git sql/mysql_fdw.sql sql/mysql_fdw.sql
index 4fd7ce3..b350c23 100644
--- sql/mysql_fdw.sql
+++ sql/mysql_fdw.sql
@@ -12,6 +12,9 @@ CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER
CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee');
CREATE FOREIGN TABLE empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'empdata');
CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb', table_name 'numbers');
+CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'student');
+CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student');
+CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'numbers');
SELECT * FROM department LIMIT 10;
SELECT * FROM employee LIMIT 10;
@@ -110,6 +113,38 @@ SELECT * FROM numbers ORDER BY 1 LIMIT 1;
ALTER USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(SET username :MYSQL_USER_NAME, SET password :MYSQL_PASS);
SELECT * FROM numbers ORDER BY 1 LIMIT 1;
+
+-- FDW-126: Insert/update/delete statement failing in mysql_fdw by picking
+-- wrong database name.
+
+-- Verify the INSERT/UPDATE/DELETE operations on another foreign table which
+-- resides in the another database in MySQL. The previous commands performs
+-- the operation on foreign table created for tables in testdb MySQL database.
+-- Below operations will be performed for foreign table created for table in
+-- testdb1 MySQL database.
+INSERT INTO fdw126_ft1 VALUES(1, 'One');
+UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1;
+DELETE FROM fdw126_ft1 WHERE stu_id = 1;
+
+-- Select on employee foreign table which is created for employee table from
+-- testdb MySQL database. This call is just to cross verify if everything is
+-- working correctly.
+SELECT * FROM employee ORDER BY 1 LIMIT 1;
+
+-- Insert into fdw126_ft2 table which does not have dbname specified while
+-- creating the foreign table, so it will consider the schema name of foreign
+-- table as database name and try to connect/lookup into that database. Will
+-- throw an error.
+INSERT INTO fdw126_ft2 VALUES(2, 'Two');
+
+-- Check with the same table name from different database. fdw126_ft3 is
+-- pointing to the testdb1.numbers and not testdb.numbers table.
+-- INSERT/UPDATE/DELETE should be failing. SELECT will return no rows.
+INSERT INTO fdw126_ft3 VALUES(1, 'One');
+SELECT * FROM fdw126_ft3 ORDER BY 1 LIMIT 1;
+UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1;
+DELETE FROM fdw126_ft3 WHERE a = 1;
+
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;
@@ -122,6 +157,9 @@ DROP FOREIGN TABLE numbers;
DROP FOREIGN TABLE department;
DROP FOREIGN TABLE employee;
DROP FOREIGN TABLE empdata;
+DROP FOREIGN TABLE fdw126_ft1;
+DROP FOREIGN TABLE fdw126_ft2;
+DROP FOREIGN TABLE fdw126_ft3;
DROP USER MAPPING FOR postgres SERVER mysql_svr;
DROP SERVER mysql_svr;
DROP EXTENSION mysql_fdw CASCADE;

View File

@ -0,0 +1,127 @@
diff --git expected/mysql_fdw.out expected/mysql_fdw.out
index 2d77913..882674a 100644
--- expected/mysql_fdw.out
+++ expected/mysql_fdw.out
@@ -13,6 +13,8 @@ CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (db
CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'student');
CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student');
CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'numbers');
+CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'nosuchtable');
+CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb2', table_name 'numbers');
SELECT * FROM department LIMIT 10;
department_id | department_name
---------------+-----------------
@@ -439,6 +441,14 @@ UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1;
ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation
DELETE FROM fdw126_ft3 WHERE a = 1;
ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation
+-- Perform the ANALYZE on the foreign table which is not present on the remote
+-- side. Should not crash.
+-- The database is present but not the target table.
+ANALYZE fdw126_ft4;
+ERROR: relation testdb1.nosuchtable does not exist
+-- The database itself is not present.
+ANALYZE fdw126_ft5;
+ERROR: relation testdb2.numbers does not exist
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;
@@ -452,6 +462,8 @@ DROP FOREIGN TABLE empdata;
DROP FOREIGN TABLE fdw126_ft1;
DROP FOREIGN TABLE fdw126_ft2;
DROP FOREIGN TABLE fdw126_ft3;
+DROP FOREIGN TABLE fdw126_ft4;
+DROP FOREIGN TABLE fdw126_ft5;
DROP USER MAPPING FOR postgres SERVER mysql_svr;
DROP SERVER mysql_svr;
DROP EXTENSION mysql_fdw CASCADE;
diff --git mysql_fdw.c mysql_fdw.c
index eaba2e3..1c8c71f 100644
--- mysql_fdw.c
+++ mysql_fdw.c
@@ -1209,7 +1209,6 @@ mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNu
MYSQL_ROW row;
Oid foreignTableId = RelationGetRelid(relation);
mysql_opt *options;
- char *relname;
ForeignServer *server;
UserMapping *user;
ForeignTable *table;
@@ -1220,19 +1219,14 @@ mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNu
/* Fetch options */
options = mysql_get_options(foreignTableId);
+ Assert(options->svr_database != NULL && options->svr_table != NULL);
/* Connect to the server */
conn = mysql_get_connection(server, user, options);
/* Build the query */
initStringInfo(&sql);
-
- /* If no table name specified, use the foreign table name */
- relname = options->svr_table;
- if ( relname == NULL)
- relname = RelationGetRelationName(relation);
-
- mysql_deparse_analyze(&sql, options->svr_database, relname);
+ mysql_deparse_analyze(&sql, options->svr_database, options->svr_table);
if (_mysql_query(conn, sql.data) != 0)
{
@@ -1264,6 +1258,18 @@ mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNu
}
}
result = _mysql_store_result(conn);
+
+ /*
+ * To get the table size in ANALYZE operation, we run a SELECT query by
+ * passing the database name and table name. So if the remote table is not
+ * present, then we end up getting zero rows. Throw an error in that case.
+ */
+ if (_mysql_num_rows(result) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FDW_TABLE_NOT_FOUND),
+ errmsg("relation %s.%s does not exist", options->svr_database,
+ options->svr_table)));
+
if (result)
{
row = _mysql_fetch_row(result);
diff --git sql/mysql_fdw.sql sql/mysql_fdw.sql
index b350c23..776220a 100644
--- sql/mysql_fdw.sql
+++ sql/mysql_fdw.sql
@@ -15,6 +15,8 @@ CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (db
CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'student');
CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student');
CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'numbers');
+CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb1', table_name 'nosuchtable');
+CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb2', table_name 'numbers');
SELECT * FROM department LIMIT 10;
SELECT * FROM employee LIMIT 10;
@@ -145,6 +147,14 @@ SELECT * FROM fdw126_ft3 ORDER BY 1 LIMIT 1;
UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1;
DELETE FROM fdw126_ft3 WHERE a = 1;
+-- Perform the ANALYZE on the foreign table which is not present on the remote
+-- side. Should not crash.
+-- The database is present but not the target table.
+ANALYZE fdw126_ft4;
+-- The database itself is not present.
+ANALYZE fdw126_ft5;
+
+
DELETE FROM employee;
DELETE FROM department;
DELETE FROM empdata;
@@ -160,6 +170,8 @@ DROP FOREIGN TABLE empdata;
DROP FOREIGN TABLE fdw126_ft1;
DROP FOREIGN TABLE fdw126_ft2;
DROP FOREIGN TABLE fdw126_ft3;
+DROP FOREIGN TABLE fdw126_ft4;
+DROP FOREIGN TABLE fdw126_ft5;
DROP USER MAPPING FOR postgres SERVER mysql_svr;
DROP SERVER mysql_svr;
DROP EXTENSION mysql_fdw CASCADE;

View File

@ -1,7 +1,7 @@
diff --git a/code/mysql_fdw-REL-2_5_3/Makefile b/code/mysql_fdw-REL-2_5_3/Makefile
index d5e7b36..157a6cf 100644
--- a/code/mysql_fdw-REL-2_5_3/Makefile
+++ b/code/mysql_fdw-REL-2_5_3/Makefile
diff --git Makefile Makefile
index d5e7b36..4cd59e9 100644
--- Makefile
+++ Makefile
@@ -31,6 +31,8 @@ else
MYSQL_LIB = mysqlclient
endif
@ -37,25 +37,27 @@ index d5e7b36..157a6cf 100644
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
diff --git a/code/mysql_fdw-REL-2_5_3/connection.cpp b/code/mysql_fdw-REL-2_5_3/connection.cpp
index 6b18027..0a9e40f 100644
--- a/code/mysql_fdw-REL-2_5_3/connection.cpp
+++ b/code/mysql_fdw-REL-2_5_3/connection.cpp
@@ -22,6 +22,7 @@
#include "utils/hsearch.h"
diff --git connection.cpp connection.cpp
index a517a73..3fc2f20 100644
--- connection.cpp
+++ connection.cpp
@@ -24,6 +24,7 @@
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/syscache.h"
+#include "storage/ipc.h"
/* Length of host */
#define HOST_LEN 256
@@ -48,8 +49,13 @@ typedef struct ConnCacheEntry
@@ -53,10 +54,15 @@ typedef struct ConnCacheEntry
/*
* Connection cache (initialized on first use)
*/
-static HTAB *ConnectionHash = NULL;
+static THR_LOCAL HTAB *ConnectionHash = NULL;
static void mysql_inval_callback(Datum arg, int cacheid, uint32 hashvalue);
+static void
+mysql_fdw_exit(int code, Datum arg)
+{
@ -64,7 +66,7 @@ index 6b18027..0a9e40f 100644
/*
* mysql_get_connection:
* Get a connection which can be used to execute queries on
@@ -73,10 +79,11 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
@@ -80,7 +86,7 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
ctl.hash = tag_hash;
/* allocate ConnectionHash in the cache context */
@ -73,11 +75,15 @@ index 6b18027..0a9e40f 100644
ConnectionHash = hash_create("mysql_fdw connections", 8,
&ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
@@ -93,6 +99,7 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
mysql_inval_callback, (Datum) 0);
CacheRegisterSyscacheCallback(USERMAPPINGOID,
mysql_inval_callback, (Datum) 0);
+ on_proc_exit(&mysql_fdw_exit, PointerGetDatum(NULL));
}
/* Create hash key for the entry. Assume no pad bytes in key struct */
@@ -86,7 +93,7 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
@@ -102,7 +109,7 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt)
/*
* Find or create cached entry for requested connection.
*/
@ -86,7 +92,7 @@ index 6b18027..0a9e40f 100644
if (!found)
{
/* initialize new hashtable entry (key is already filled in) */
@@ -137,6 +144,9 @@ mysql_cleanup_connection(void)
@@ -196,6 +203,9 @@ mysql_cleanup_connection(void)
_mysql_close(entry->conn);
entry->conn = NULL;
}
@ -96,10 +102,10 @@ index 6b18027..0a9e40f 100644
}
/*
diff --git a/code/mysql_fdw-REL-2_5_3/deparse.cpp b/code/mysql_fdw-REL-2_5_3/deparse.cpp
diff --git deparse.cpp deparse.cpp
index a75c270..94b1799 100644
--- a/code/mysql_fdw-REL-2_5_3/deparse.cpp
+++ b/code/mysql_fdw-REL-2_5_3/deparse.cpp
--- deparse.cpp
+++ deparse.cpp
@@ -20,7 +20,7 @@
#include "pgtime.h"
@ -216,10 +222,10 @@ index a75c270..94b1799 100644
first = false;
}
appendStringInfoChar(buf, ']');
diff --git a/code/mysql_fdw-REL-2_5_3/mysql_fdw.cpp b/code/mysql_fdw-REL-2_5_3/mysql_fdw.cpp
index f1e26c3..49243d1 100644
--- a/code/mysql_fdw-REL-2_5_3/mysql_fdw.cpp
+++ b/code/mysql_fdw-REL-2_5_3/mysql_fdw.cpp
diff --git mysql_fdw.cpp mysql_fdw.cpp
index d518e2e..a56fae6 100644
--- mysql_fdw.cpp
+++ mysql_fdw.cpp
@@ -53,7 +53,7 @@
#include "utils/timestamp.h"
#include "utils/formatting.h"
@ -448,7 +454,7 @@ index f1e26c3..49243d1 100644
festate->conn = conn;
festate->cursor_exists = false;
@@ -1047,12 +1083,12 @@ mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid)
@@ -1052,12 +1088,12 @@ mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid)
mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, foreigntableid);
/* Create a ForeignPath node and add it as only possible path */
@ -463,7 +469,7 @@ index f1e26c3..49243d1 100644
startup_cost,
total_cost,
NIL, /* no pathkeys */
@@ -1155,7 +1191,7 @@ mysqlGetForeignPlan(
@@ -1160,7 +1196,7 @@ mysqlGetForeignPlan(
mysql_append_where_clause(&sql, root, baserel, remote_conds,
true, &params_list);
@ -472,7 +478,7 @@ index f1e26c3..49243d1 100644
(root->parse->commandType == CMD_UPDATE ||
root->parse->commandType == CMD_DELETE))
{
@@ -1319,7 +1355,7 @@ mysqlPlanForeignModify(PlannerInfo *root,
@@ -1330,7 +1366,7 @@ mysqlPlanForeignModify(PlannerInfo *root,
#if PG_VERSION_NUM >= 90500
Bitmapset *tmpset = bms_copy(rte->updatedCols);
#else
@ -481,7 +487,7 @@ index f1e26c3..49243d1 100644
#endif
AttrNumber col;
@@ -1633,7 +1669,7 @@ mysqlExecForeignUpdate(EState *estate,
@@ -1644,7 +1680,7 @@ mysqlExecForeignUpdate(EState *estate,
n_params = list_length(fmstate->retrieved_attrs);
mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * n_params);
@ -490,7 +496,7 @@ index f1e26c3..49243d1 100644
/* Bind the values */
foreach(lc, fmstate->retrieved_attrs)
@@ -1822,7 +1858,7 @@ mysqlExecForeignDelete(EState *estate,
@@ -1833,7 +1869,7 @@ mysqlExecForeignDelete(EState *estate,
static void
mysqlEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo)
{
@ -499,10 +505,10 @@ index f1e26c3..49243d1 100644
if (festate && festate->stmt)
{
diff --git a/code/mysql_fdw-REL-2_5_3/mysql_fdw.h b/code/mysql_fdw-REL-2_5_3/mysql_fdw.h
index 5b543cd..b2a7011 100644
--- a/code/mysql_fdw-REL-2_5_3/mysql_fdw.h
+++ b/code/mysql_fdw-REL-2_5_3/mysql_fdw.h
diff --git mysql_fdw.h mysql_fdw.h
index 5b543cd..ea58af7 100644
--- mysql_fdw.h
+++ mysql_fdw.h
@@ -135,31 +135,31 @@ extern bool is_foreign_expr(PlannerInfo *root,
Expr *expr);
@ -533,9 +539,9 @@ index 5b543cd..b2a7011 100644
-bool ((*_mysql_ssl_set)(MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher));
-MYSQL *((*_mysql_real_connect)(MYSQL *mysql,
+extern int ((*_mysql_options)(MYSQL *mysql,enum mysql_option option, const void *arg));
+extern int ((*_mysql_stmt_prepare)(MYSQL_STMT *stmt, const char *query, unsigned long length));
+extern int ((*_mysql_stmt_execute)(MYSQL_STMT *stmt));
+extern int ((*_mysql_stmt_fetch)(MYSQL_STMT *stmt));
+extern int ((*_mysql_stmt_prepare)(MYSQL_STMT *stmt, const char *query, unsigned long length));
+extern int ((*_mysql_query)(MYSQL *mysql, const char *q));
+extern bool ((*_mysql_stmt_attr_set)(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr));
+extern bool ((*_mysql_stmt_close)(MYSQL_STMT * stmt));
@ -582,10 +588,10 @@ index 5b543cd..b2a7011 100644
/* option.c headers */
diff --git a/code/mysql_fdw-REL-2_5_3/mysql_query.cpp b/code/mysql_fdw-REL-2_5_3/mysql_query.cpp
diff --git mysql_query.cpp mysql_query.cpp
index 8c25f5c..6093a5a 100644
--- a/code/mysql_fdw-REL-2_5_3/mysql_query.cpp
+++ b/code/mysql_fdw-REL-2_5_3/mysql_query.cpp
--- mysql_query.cpp
+++ mysql_query.cpp
@@ -21,7 +21,7 @@
#include <unistd.h>
@ -734,10 +740,10 @@ index 8c25f5c..6093a5a 100644
switch (pgtyp)
{
diff --git a/code/mysql_fdw-REL-2_5_3/option.cpp b/code/mysql_fdw-REL-2_5_3/option.cpp
index 880d984..f3b77f7 100644
--- a/code/mysql_fdw-REL-2_5_3/option.cpp
+++ b/code/mysql_fdw-REL-2_5_3/option.cpp
diff --git option.cpp option.cpp
index 574cb24..5c92c50 100644
--- option.cpp
+++ option.cpp
@@ -81,7 +81,7 @@ static struct MySQLFdwOption valid_options[] =
{ NULL, InvalidOid }
};