From 1152ff0f4bb0155fb4e595b44e85e320c0f39723 Mon Sep 17 00:00:00 2001 From: LeAnhMinh Date: Mon, 28 Mar 2022 18:02:50 +0700 Subject: [PATCH 1/3] Support Subscripting Array syntax **Describe the bug** mysql_fdw deparse the Subscripting Array to be the same as postgres_fdw. However, MySQL have no type as ARRAY in Postgresql, so we don't pushdown Array Subscripting. **To Reproduce** 1. Create table on MySQL server mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE s6(id int PRIMARY KEY, c1 int, c2 int, c3 text);" 2. Execute SQL CREATE FOREIGN TABLE ft5 (id int, c1 int, c2 int, c3 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 's6'); INSERT INTO ft5 SELECT id, id, id % 5, to_char(id, 'FM00000') FROM generate_series(1, 10) id; 3. Result before fixing EXPLAIN VERBOSE SELECT * FROM ft5 t1 WHERE c1 = (ARRAY[c1,c2,3])[2]; ERROR: failed to prepare the MySQL query: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '[`c1`, `c2`, 3])[2])))' at line 1 **Result after fixing** EXPLAIN VERBOSE SELECT * FROM ft5 t1 WHERE c1 = (ARRAY[c1,c2,3])[2]; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft5 t1 (cost=100.00..125.17 rows=6 width=44) Output: id, c1, c2, c3 Local server startup cost: 10 Remote query: SELECT `id`, `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`s6` WHERE ((`c1` = ELT(2, `c1`, `c2`, 3))) (4 rows) **Additional context** Use Mysql ELT(N,str1,str2,str3,...) function to support array subscripting function: + Support 1D array subscription. + Do not support multiple dimension array + Do not support slice function + Do not support Var subscription. --- deparse.c | 108 ++++++++++++++++++++++++++++++++-------------------- mysql_fdw.c | 19 +++++++-- 2 files changed, 82 insertions(+), 45 deletions(-) diff --git a/deparse.c b/deparse.c index fa0ad28..fcd74dc 100644 --- a/deparse.c +++ b/deparse.c @@ -112,7 +112,7 @@ static void mysql_deparse_param(Param *node, deparse_expr_cxt *context); #if PG_VERSION_NUM < 120000 static void mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context); #else -static void mysql_deparse_array_ref(SubscriptingRef *node, +static void mysql_deparse_subscripting_ref(SubscriptingRef *node, deparse_expr_cxt *context); #endif static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context); @@ -679,7 +679,7 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) mysql_deparse_array_ref((ArrayRef *) node, context); #else case T_SubscriptingRef: - mysql_deparse_array_ref((SubscriptingRef *) node, context); + mysql_deparse_subscripting_ref((SubscriptingRef *) node, context); #endif break; case T_FuncExpr: @@ -1003,53 +1003,41 @@ mysql_deparse_param(Param *node, deparse_expr_cxt *context) */ static void #if PG_VERSION_NUM < 120000 -mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context) +mysql_deparse_array_ref(ArrayRef * node, deparse_expr_cxt *context) #else -mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context) +mysql_deparse_subscripting_ref(SubscriptingRef *node, deparse_expr_cxt *context) #endif { - StringInfo buf = context->buf; - ListCell *lowlist_item; ListCell *uplist_item; + ListCell *lc; + StringInfo buf = context->buf; + bool first = true; + ArrayExpr *array_expr = (ArrayExpr *) node->refexpr; - /* Always parenthesize the expression. */ - appendStringInfoChar(buf, '('); + /* Not support slice function, which is excluded in pushdown checking */ + Assert(node->reflowerindexpr == NULL); + Assert(node->refupperindexpr != NULL); - /* - * Deparse referenced array expression first. If that expression includes - * a cast, we have to parenthesize to prevent the array subscript from - * being taken as typename decoration. We can avoid that in the typical - * case of subscripting a Var, but otherwise do it. - */ - if (IsA(node->refexpr, Var)) - deparseExpr(node->refexpr, context); - else - { - appendStringInfoChar(buf, '('); - deparseExpr(node->refexpr, context); - appendStringInfoChar(buf, ')'); - } + /* Transform array subscripting to ELT(index number, str1, str2, ...) */ + appendStringInfoString(buf, "ELT("); + + /* Append index number of ELT() expression */ + uplist_item = list_head(node->refupperindexpr); + deparseExpr(lfirst(uplist_item), context); + appendStringInfoString(buf, ", "); - /* Deparse subscript expressions. */ - lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ - foreach(uplist_item, node->refupperindexpr) + /* Deparse Array Expression in form of ELT syntax */ + foreach(lc, array_expr->elements) { - appendStringInfoChar(buf, '['); - if (lowlist_item) - { - deparseExpr(lfirst(lowlist_item), context); - appendStringInfoChar(buf, ':'); -#if PG_VERSION_NUM < 130000 - lowlist_item = lnext(lowlist_item); -#else - lowlist_item = lnext(node->reflowerindexpr, lowlist_item); -#endif - } - deparseExpr(lfirst(uplist_item), context); - appendStringInfoChar(buf, ']'); + if (!first) + appendStringInfoString(buf, ", "); + deparseExpr(lfirst(lc), context); + first = false; } + /* Enclose the ELT() expression */ appendStringInfoChar(buf, ')'); + } /* @@ -1493,6 +1481,11 @@ foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, /* Var belongs to foreign table */ collation = var->varcollid; state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; + + /* Mysql do not have Array data type */ + if (type_is_array(var->vartype)) + elog(ERROR, "mysql_fdw: Not support array data type\n"); + } else { @@ -1549,6 +1542,7 @@ foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, { SubscriptingRef *ar = (SubscriptingRef *) node; #endif + Assert(list_length(ar->refupperindexpr) > 0); /* Should not be in the join clauses of the Join-pushdown */ if (glob_cxt->is_remote_cond) @@ -1558,25 +1552,55 @@ foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, if (ar->refassgnexpr != NULL) return false; +#if PG_VERSION_NUM >= 140000 + + /* + * Recurse into the remaining subexpressions. The container + * subscripts will not affect collation of the SubscriptingRef + * result, so do those first and reset inner_cxt afterwards. + */ +#else + /* * Recurse to remaining subexpressions. Since the array * subscripts must yield (noncollatable) integers, they won't * affect the inner_cxt state. */ +#endif + /* Allow 1-D subcription, other case does not push down */ + if (list_length(ar->refupperindexpr) > 1) + return false; + if (!foreign_expr_walker((Node *) ar->refupperindexpr, glob_cxt, &inner_cxt)) return false; - if (!foreign_expr_walker((Node *) ar->reflowerindexpr, - glob_cxt, &inner_cxt)) + + /* Disable slice by checking reflowerindexpr [:] */ + if (ar->reflowerindexpr) return false; + +#if PG_VERSION_NUM >= 140000 + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; +#endif + /* Disble subcripting for Var, eg: c1[1] by checking T_Var */ if (!foreign_expr_walker((Node *) ar->refexpr, glob_cxt, &inner_cxt)) return false; +#if PG_VERSION_NUM >= 140000 /* - * Array subscripting should yield same collation as input, - * but for safety use same logic as for function nodes. + * Container subscripting typically yields same collation as + * refexpr's, but in case it doesn't, use same logic as for + * function nodes. */ +#else + + /* + * Container subscripting should yield same collation as + * input, but for safety use same logic as for function nodes. + */ +#endif collation = ar->refcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; diff --git a/mysql_fdw.c b/mysql_fdw.c index 992efee..ae75760 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -555,6 +555,12 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) List *fdw_private = fsplan->fdw_private; char sql_mode[255]; + /* + * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + /* * We'll save private state in node->fdw_state. */ @@ -861,7 +867,6 @@ mysqlIterateForeignScan(ForeignScanState *node) static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) { - MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; RangeTblEntry *rte; ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; int rtindex; @@ -901,10 +906,14 @@ mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) ExplainPropertyLong("Remote server startup cost", 25, es); #endif } - /* Show the remote query in verbose mode */ if (es->verbose) - ExplainPropertyText("Remote query", festate->query, es); + { + char *remote_sql = strVal(list_nth(fdw_private, + mysqlFdwScanPrivateSelectSql)); + + ExplainPropertyText("Remote query", remote_sql, es); + } } /* @@ -916,6 +925,10 @@ mysqlEndForeignScan(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; + /* if festate is NULL, we are in EXPLAIN; do nothing */ + if (festate == NULL) + return; + if (festate->table && festate->table->mysql_res) { mysql_free_result(festate->table->mysql_res); From e4cc9fff3e4558c65a974a370e0c4d1e5f51f3d0 Mon Sep 17 00:00:00 2001 From: LeAnhMinh Date: Tue, 29 Mar 2022 10:35:52 +0700 Subject: [PATCH 2/3] Not pushdown system column instead of raise error **Describe the bug** Because MySQL does not support tableoid, ctid,... So not pushdown system column instead of raise error. **To Reproduce** 1. Execute SQL CREATE FOREIGN TABLE ft1 ( c0 int, c1 int NOT NULL, c2 int NOT NULL, c3 text, c4 timestamptz, c5 timestamp, c6 varchar(10), c7 char(10) default 'ft1', c8 user_enum ) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_post'); EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; 2.Result before fixing EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; ERROR: system attribute tableoid can't be fetched from remote relation **Result after fixing** QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid -> Sort Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid Sort Key: t1.c3, t1.c1, t1.tableoid -> Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid Remote query: SELECT `C 1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_post`.`T 1` (8 rows) --- deparse.c | 143 ++++++++++++++++++++++++++++++++++++-------- expected/select.out | 63 +++++++++++++++++-- mysql_fdw.c | 22 ------- 3 files changed, 177 insertions(+), 51 deletions(-) diff --git a/deparse.c b/deparse.c index fcd74dc..61271dc 100644 --- a/deparse.c +++ b/deparse.c @@ -549,9 +549,6 @@ mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root, bool qualify_col) { RangeTblEntry *rte; - char *colname = NULL; - List *options; - ListCell *lc; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ Assert(!IS_SPECIAL_VARNO(varno)); @@ -559,37 +556,123 @@ mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, /* Get RangeTblEntry from array in PlannerInfo. */ rte = planner_rt_fetch(varno, root); - /* - * If it's a column of a foreign table, and it has the column_name FDW - * option, use that value. - */ - options = GetForeignColumnOptions(rte->relid, varattno); - foreach(lc, options) + /* We not support fetching any system attributes from remote side */ + if (varattno < 0) { - DefElem *def = (DefElem *) lfirst(lc); + /* + * All other system attributes are fetched as 0, except for table OID + * and ctid, table OID is fetched as the local table OID, ctid is + * fectch as invalid value. However, we must be careful; the table + * could be beneath an outer join, in which case it must go to NULL + * whenever the rest of the row does. + */ + char fetchval[32]; - if (strcmp(def->defname, "column_name") == 0) + if (varattno == TableOidAttributeNumber) { - colname = defGetString(def); - break; + /* + * table OID is fetched as the local table OID + */ + pg_snprintf(fetchval, sizeof(fetchval), "%u", rte->relid); } + else if (varattno == SelfItemPointerAttributeNumber) + { + /* + * ctid is fetched as '(4294967295,0)' ~ (0xFFFFFFFF, 0) (invalid + * value), which is default value of tupleSlot->tts_tid after run + * ExecClearTuple. + */ + pg_snprintf(fetchval, sizeof(fetchval), "'(%u,%u)'", + InvalidBlockNumber, + InvalidOffsetNumber); + } + else + { + /* other system attributes are fetched as 0 */ + pg_snprintf(fetchval, sizeof(fetchval), "%u", 0); + } + + appendStringInfo(buf, "%s", fetchval); } + else if (varattno == 0) + { + /* Whole row reference */ + Relation rel; + Bitmapset *attrs_used; - /* - * If it's a column of a regular table or it doesn't have column_name - * FDW option, use attribute name. - */ - if (colname == NULL) + /* Required only to be passed down to deparseTargetList(). */ + List *retrieved_attrs; + + /* + * The lock on the relation will be held by upper callers, so it's + * fine to open it with no lock here. + */ + rel = table_open(rte->relid, NoLock); + + /* + * The local name of the foreign table can not be recognized by the + * foreign server and the table it references on foreign server might + * have different column ordering or different columns than those + * declared locally. Hence we have to deparse whole-row reference as + * ROW(columns referenced locally). Construct this by deparsing a + * "whole row" attribute. + */ + attrs_used = bms_add_member(NULL, + 0 - FirstLowInvalidHeapAttributeNumber); + + /* + * In case the whole-row reference is under an outer join then it has + * to go NULL whenever the rest of the row goes NULL. Deparsing a join + * query would always involve multiple relations, thus qualify_col + * would be true. + */ + + mysql_deparse_target_list(buf, root, varno, rel, attrs_used, + &retrieved_attrs); + + table_close(rel, NoLock); + bms_free(attrs_used); + } + else + { + char *colname = NULL; + List *options; + ListCell *lc; + + /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ + Assert(!IS_SPECIAL_VARNO(varno)); + + /* + * If it's a column of a foreign table, and it has the column_name FDW + * option, use that value. + */ + options = GetForeignColumnOptions(rte->relid, varattno); + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "column_name") == 0) + { + colname = defGetString(def); + break; + } + } + + /* + * If it's a column of a regular table or it doesn't have column_name + * FDW option, use attribute name. + */ + if (colname == NULL) #if PG_VERSION_NUM >= 110000 - colname = get_attname(rte->relid, varattno, false); + colname = get_attname(rte->relid, varattno, false); #else - colname = get_relid_attribute_name(rte->relid, varattno); + colname = get_relid_attribute_name(rte->relid, varattno); #endif + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); - if (qualify_col) - ADD_REL_QUALIFIER(buf, varno); - - appendStringInfoString(buf, mysql_quote_identifier(colname, '`')); + appendStringInfoString(buf, mysql_quote_identifier(colname, '`')); + } } static void @@ -1476,9 +1559,10 @@ foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, * non-default collation. */ if (bms_is_member(var->varno, glob_cxt->relids) && - var->varlevelsup == 0) + var->varlevelsup == 0 && var->varattno > 0) { /* Var belongs to foreign table */ + /* Else check the collation */ collation = var->varcollid; state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; @@ -1494,6 +1578,15 @@ foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, var->varcollid != DEFAULT_COLLATION_OID) return false; + /* + * System columns should not be sent to + * the remote, since we don't make any effort to ensure + * that local and remote values match (tableoid, in + * particular, almost certainly doesn't match). + */ + if (var->varattno < 0) + return false; + /* We can consider that it doesn't set collation */ collation = InvalidOid; state = FDW_COLLATE_NONE; diff --git a/expected/select.out b/expected/select.out index 832c960..fe47014 100644 --- a/expected/select.out +++ b/expected/select.out @@ -1307,13 +1307,68 @@ SELECT t1.c1, (SELECT c2 FROM f_test_tbl1 WHERE c1 =(SELECT 500)) -- FDW-255: Should throw an error when we select system attribute. SELECT xmin FROM f_test_tbl1; -ERROR: system attribute "xmin" can't be fetched from remote relation + xmin +------ + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 + 96 +(14 rows) + SELECT ctid, xmax, tableoid FROM f_test_tbl1; -ERROR: system attribute "ctid" can't be fetched from remote relation + ctid | xmax | tableoid +----------------+------------+---------- + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 + (4294967295,0) | 4294967295 | 16480 +(14 rows) + SELECT xmax, c1 FROM f_test_tbl1; -ERROR: system attribute "xmax" can't be fetched from remote relation + xmax | c1 +------------+------ + 4294967295 | 100 + 4294967295 | 200 + 4294967295 | 300 + 4294967295 | 400 + 4294967295 | 500 + 4294967295 | 600 + 4294967295 | 700 + 4294967295 | 800 + 4294967295 | 900 + 4294967295 | 1000 + 4294967295 | 1100 + 4294967295 | 1200 + 4294967295 | 1300 + 4294967295 | 1400 +(14 rows) + SELECT count(tableoid) FROM f_test_tbl1; -ERROR: system attribute "tableoid" can't be fetched from remote relation + count +------- + 14 +(1 row) + -- FDW-333: MySQL BINARY and VARBINARY data type should map to BYTEA in -- Postgres while importing the schema. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO ("test5") diff --git a/mysql_fdw.c b/mysql_fdw.c index ae75760..5fc5439 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -1283,28 +1283,6 @@ mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, scan_var_list = pull_var_clause((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS); - /* System attributes are not allowed. */ - foreach(lc, scan_var_list) - { - Var *var = lfirst(lc); - const FormData_pg_attribute *attr; - - Assert(IsA(var, Var)); - - if (var->varattno >= 0) - continue; - -#if PG_VERSION_NUM >= 120000 - attr = SystemAttributeDefinition(var->varattno); -#else - attr = SystemAttributeDefinition(var->varattno, false); -#endif - ereport(ERROR, - (errcode(ERRCODE_FDW_COLUMN_NAME_NOT_FOUND), - errmsg("system attribute \"%s\" can't be fetched from remote relation", - attr->attname.data))); - } - if (IS_JOIN_REL(foreignrel)) { scan_var_list = list_concat_unique(NIL, scan_var_list); From 4d42bba05948816ac3d568273e10a5f61673ea50 Mon Sep 17 00:00:00 2001 From: LeAnhMinh Date: Tue, 29 Mar 2022 13:56:02 +0700 Subject: [PATCH 3/3] Do not allocate when natts is 0 **Describe the bug** Failed to bind the MySQL query when foreign scan will select NULL from an inner join. **To Reproduce** 1. On MySQL server DROP DATABASE IF EXISTS join_limit; CREATE DATABASE join_limit; USE join_limit; CREATE TABLE J1_TBL ( i integer, j integer, t text); CREATE TABLE J2_TBL ( i integer, k integer); CREATE TABLE J3_TBL ( i integer, t text); INSERT INTO J1_TBL VALUES (1, 4, 'one'); INSERT INTO J1_TBL VALUES (2, 3, 'two'); INSERT INTO J1_TBL VALUES (3, 2, 'three'); INSERT INTO J1_TBL VALUES (4, 1, 'four'); INSERT INTO J1_TBL VALUES (5, 0, 'five'); INSERT INTO J1_TBL VALUES (6, 6, 'six'); INSERT INTO J1_TBL VALUES (7, 7, 'seven'); INSERT INTO J1_TBL VALUES (8, 8, 'eight'); INSERT INTO J1_TBL VALUES (0, NULL, 'zero'); INSERT INTO J1_TBL VALUES (NULL, NULL, 'null'); INSERT INTO J1_TBL VALUES (NULL, 0, 'zero'); INSERT INTO J2_TBL VALUES (1, -1); INSERT INTO J2_TBL VALUES (2, 2); INSERT INTO J2_TBL VALUES (3, -3); INSERT INTO J2_TBL VALUES (2, 4); INSERT INTO J2_TBL VALUES (5, -5); INSERT INTO J2_TBL VALUES (5, -5); INSERT INTO J2_TBL VALUES (0, NULL); INSERT INTO J2_TBL VALUES (NULL, NULL); INSERT INTO J2_TBL VALUES (NULL, 0); INSERT INTO J3_TBL VALUES (1, 'first'); INSERT INTO J3_TBL VALUES (2, NULL); INSERT INTO J3_TBL VALUES (2, 'second'); INSERT INTO J3_TBL VALUES (3, 'third'); INSERT INTO J3_TBL VALUES (4, 'forth'); INSERT INTO J3_TBL VALUES (5, 'five'); 2. Execute SQL CREATE FOREIGN TABLE J1_TBL__mysql_svr__0 ( i integer, j integer, t text) SERVER mysql_svr OPTIONS (dbname 'join_limit', table_name 'J1_TBL'); CREATE FOREIGN TABLE J2_TBL__mysql_svr__0 ( i integer, k integer) SERVER mysql_svr OPTIONS (dbname 'join_limit', table_name 'J2_TBL'); CREATE FOREIGN TABLE J3_TBL__mysql_svr__0 ( i integer, t text) SERVER mysql_svr OPTIONS (dbname 'join_limit', table_name 'J3_TBL'); SELECT * FROM j2_tbl__mysql_svr__0 where exists (select t3.i, t from j3_tbl__mysql_svr__0 t3 join j2_tbl__mysql_svr__0 t2 on t2.i = t3.i); 3. Result before fixing SELECT * FROM j2_tbl__mysql_svr__0 where exists (select t3.i, t from j3_tbl__mysql_svr__0 t3 join j2_tbl__mysql_svr__0 t2 on t2.i = t3.i); ERROR: failed to bind the MySQL query **Result after fixing** i | k ---+---- 1 | -1 2 | 2 3 | -3 2 | 4 5 | -5 5 | -5 0 | | | 0 (9 rows) **Additional context** Because with tupleDescriptor->natts = 0 is used to palloc for table->mysql_bind. But it will return invalid memory like address: 0x7f7f7f7f7f7f7f7f. With this allocated memory, the binding will fail and it causes error "failed to bind the MySQL query". When natts is 0, it means the PostgreSQL/PGSpider core does not need any result in tuple slot. Therefore, no need to allocate memory for festate->table in this case. --- mysql_fdw.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mysql_fdw.c b/mysql_fdw.c index 5fc5439..c35acf5 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -564,7 +564,7 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) /* * We'll save private state in node->fdw_state. */ - festate = (MySQLFdwExecState *) palloc(sizeof(MySQLFdwExecState)); + festate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); node->fdw_state = (void *) festate; /* @@ -694,6 +694,9 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) mysql_stmt_attr_set(festate->stmt, STMT_ATTR_PREFETCH_ROWS, (void *) &options->fetch_size); + if (tupleDescriptor->natts == 0) + return; + festate->table = (mysql_table *) palloc0(sizeof(mysql_table)); festate->table->column = (mysql_column *) palloc0(sizeof(mysql_column) * tupleDescriptor->natts); festate->table->mysql_bind = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * tupleDescriptor->natts);