diff --git a/deparse.c b/deparse.c index fa0ad28..61271dc 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); @@ -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 @@ -679,7 +762,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 +1086,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("); - /* Deparse subscript expressions. */ - lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ - foreach(uplist_item, node->refupperindexpr) + /* Append index number of ELT() expression */ + uplist_item = list_head(node->refupperindexpr); + deparseExpr(lfirst(uplist_item), context); + appendStringInfoString(buf, ", "); + + /* 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, ')'); + } /* @@ -1488,11 +1559,17 @@ 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; + + /* Mysql do not have Array data type */ + if (type_is_array(var->vartype)) + elog(ERROR, "mysql_fdw: Not support array data type\n"); + } else { @@ -1501,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; @@ -1549,6 +1635,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 +1645,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 + + /* + * Container subscripting typically yields same collation as + * refexpr's, but in case it doesn't, use same logic as for + * function nodes. + */ +#else /* - * Array subscripting should yield same collation as input, - * but for safety use same logic as for function nodes. + * 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/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 992efee..c35acf5 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -555,10 +555,16 @@ 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. */ - festate = (MySQLFdwExecState *) palloc(sizeof(MySQLFdwExecState)); + festate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); node->fdw_state = (void *) festate; /* @@ -688,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); @@ -861,7 +870,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 +909,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 +928,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); @@ -1270,28 +1286,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);