Sorry, I sent older version, which is logically same but contains some whitespace problems. I resend only 0003 by this mail.
At Fri, 24 Aug 2018 16:51:31 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyot...@lab.ntt.co.jp> wrote in <20180824.165131.45788857.horiguchi.kyot...@lab.ntt.co.jp> > Hello. > > At Tue, 21 Aug 2018 11:01:32 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI > <horiguchi.kyot...@lab.ntt.co.jp> wrote in > <20180821.110132.261184472.horiguchi.kyot...@lab.ntt.co.jp> > > > You wrote: > > > > Several places seems to be assuming that fdw_scan_tlist may be > > > > used foreign scan on simple relation but I didn't find that > > > > actually happens. > > > > > > Yeah, currently, postgres_fdw and file_fdw don't use that list for > > > simple foreign table scans, but it could be used to improve the > > > efficiency for those scans, as explained in fdwhandler.sgml: > ... > > I'll put more consideration on using fdw_scan_tlist in the > > documented way. > > Done. postgres_fdw now generates full fdw_scan_tlist (as > documented) for foreign relations with junk columns, but a small > change in core was needed. However it is far less invasive than > the previous version and I believe that it dones't harm > maybe-existing use of fdw_scan_tlist on non-join rels. > > The previous patch didn't show "tableoid" in the Output list (as > "<added_junk>") of explain output but this does correctly by > referring to rte->eref->colnames. I believe no other FDW has > expanded foreign relation even if it uses fdw_scan_tlist for > ForeignScan on a base relation so it won't harm them. > > Since this uses fdw_scan_tlist so it is theoretically > back-patchable back to 9.6. This patch applies on top of the > current master. > > Please find the attached three files. > > 0001-Add-test-for-postgres_fdw-foreign-parition-update.patch > > This should fail for unpatched postgres_fdw. (Just for demonstration) > > 0002-Core-side-modification-for-PgFDW-foreign-update-fix.patch > > Core side change which allows fdw_scan_tlist to have extra > columns that is not defined in the base relation. > > 0003-Fix-of-foreign-update-bug-of-PgFDW.patch > > Fix of postgres_fdw for this problem. > > 0004-Regtest-change-for-PgFDW-foreign-update-fix.patch > > Regression test change separated for readability. regards. -- Kyotaro Horiguchi NTT Open Source Software Center
>From b95571ac7cf15101bfa045354a82befe074ecc55 Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp> Date: Fri, 24 Aug 2018 16:17:24 +0900 Subject: [PATCH 3/4] Fix of foreign update bug of PgFDW Postgres_fdw wrongly behavoes in updating foreign tables on a remote partitioned table when direct modify is not used. This is because postgres_fdw is forgetting that two different tuples with the same ctid may come in the case. With this patch it uses remote tableoid in addition to ctid to distinguish a remote tuple. --- contrib/postgres_fdw/deparse.c | 149 +++++++++++------- contrib/postgres_fdw/postgres_fdw.c | 291 +++++++++++++++++++++++++++++++----- 2 files changed, 344 insertions(+), 96 deletions(-) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 6001f4d25e..c4cd6a7249 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -1037,6 +1037,15 @@ deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, */ deparseExplicitTargetList(tlist, false, retrieved_attrs, context); } + else if (tlist != NIL) + { + /* + * The given tlist is that of base relation's expanded with junk + * columns. + */ + context->params_list = NULL; + deparseExplicitTargetList(tlist, false, retrieved_attrs, context); + } else { /* @@ -1088,6 +1097,42 @@ deparseFromExpr(List *quals, deparse_expr_cxt *context) } } +/* + * Adds one element in target/returning list if it is in attrs_used. + * + * If deparsestr is given, just use it. Otherwise resolves the name using rte. + */ +static inline void +deparseAddTargetListItem(StringInfo buf, + List **retrieved_attrs, Bitmapset *attrs_used, + Index rtindex, AttrNumber attnum, + char *deparsestr, RangeTblEntry *rte, + bool is_returning, bool qualify_col, + bool have_wholerow, bool *first) +{ + if (!have_wholerow && + !bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, attrs_used)) + return; + + if (!*first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + *first = false; + + if (deparsestr) + { + if (qualify_col) + ADD_REL_QUALIFIER(buf, rtindex); + + appendStringInfoString(buf, deparsestr); + } + else + deparseColumnRef(buf, rtindex, attnum, rte, qualify_col); + + *retrieved_attrs = lappend_int(*retrieved_attrs, attnum); +} + /* * Emit a target list that retrieves the columns specified in attrs_used. * This is used for both SELECT and RETURNING targetlists; the is_returning @@ -1128,58 +1173,28 @@ deparseTargetList(StringInfo buf, if (attr->attisdropped) continue; - if (have_wholerow || - bms_is_member(i - FirstLowInvalidHeapAttributeNumber, - attrs_used)) - { - if (!first) - appendStringInfoString(buf, ", "); - else if (is_returning) - appendStringInfoString(buf, " RETURNING "); - first = false; - - deparseColumnRef(buf, rtindex, i, rte, qualify_col); - - *retrieved_attrs = lappend_int(*retrieved_attrs, i); - } + deparseAddTargetListItem(buf, retrieved_attrs, attrs_used, + rtindex, i, NULL, rte, + is_returning, qualify_col, have_wholerow, + &first); } /* - * Add ctid and oid if needed. We currently don't support retrieving any - * other system columns. + * Add ctid, oid and tableoid if needed. The attribute name and number are + * assigned in postgresAddForeignUpdateTargets. We currently don't support + * retrieving any other system columns. */ - if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, - attrs_used)) - { - if (!first) - appendStringInfoString(buf, ", "); - else if (is_returning) - appendStringInfoString(buf, " RETURNING "); - first = false; + deparseAddTargetListItem(buf, retrieved_attrs, attrs_used, + rtindex, tupdesc->natts + 1, "tableoid", + NULL, is_returning, qualify_col, false, &first); - if (qualify_col) - ADD_REL_QUALIFIER(buf, rtindex); - appendStringInfoString(buf, "ctid"); + deparseAddTargetListItem(buf, retrieved_attrs, attrs_used, + rtindex, SelfItemPointerAttributeNumber, "ctid", + NULL, is_returning, qualify_col, false, &first); - *retrieved_attrs = lappend_int(*retrieved_attrs, - SelfItemPointerAttributeNumber); - } - if (bms_is_member(ObjectIdAttributeNumber - FirstLowInvalidHeapAttributeNumber, - attrs_used)) - { - if (!first) - appendStringInfoString(buf, ", "); - else if (is_returning) - appendStringInfoString(buf, " RETURNING "); - first = false; - - if (qualify_col) - ADD_REL_QUALIFIER(buf, rtindex); - appendStringInfoString(buf, "oid"); - - *retrieved_attrs = lappend_int(*retrieved_attrs, - ObjectIdAttributeNumber); - } + deparseAddTargetListItem(buf, retrieved_attrs, attrs_used, + rtindex, ObjectIdAttributeNumber, "oid", + NULL, is_returning, qualify_col, false, &first); /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) @@ -1728,7 +1743,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, deparseRelation(buf, rel); appendStringInfoString(buf, " SET "); - pindex = 2; /* ctid is always the first param */ + pindex = 3; /* tableoid and ctid always precede */ first = true; foreach(lc, targetAttrs) { @@ -1742,7 +1757,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, appendStringInfo(buf, " = $%d", pindex); pindex++; } - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfoString(buf, " WHERE tableoid = $1 AND ctid = $2"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_update_after_row, @@ -1858,7 +1873,7 @@ deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, { appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfoString(buf, " WHERE tableoid = $1 AND ctid = $2"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, @@ -2160,9 +2175,11 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, } else { - char *colname = NULL; + char *colname = NULL; List *options; ListCell *lc; + Relation rel; + int natts; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ Assert(!IS_SPECIAL_VARNO(varno)); @@ -2171,16 +2188,34 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, * 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); + rel = heap_open(rte->relid, NoLock); + natts = RelationGetNumberOfAttributes(rel); + heap_close(rel, NoLock); - if (strcmp(def->defname, "column_name") == 0) + if (rte->relkind == RELKIND_FOREIGN_TABLE) + { + if (varattno > 0 && varattno <= natts) { - colname = defGetString(def); - break; + options = GetForeignColumnOptions(rte->relid, varattno); + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "column_name") == 0) + { + colname = defGetString(def); + break; + } + } } + else if (varattno == natts + 1) + { + /* This should be an additional junk column */ + colname = "tableoid"; + } + else + elog(ERROR, "name resolution failed for attribute %d of relation %u", + varattno, rte->relid); } /* diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 0803c30a48..babf5a49d4 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -179,6 +179,7 @@ typedef struct PgFdwModifyState /* info about parameters for prepared statement */ AttrNumber ctidAttno; /* attnum of input resjunk ctid column */ + AttrNumber toidAttno; /* attnum of input resjunk tableoid column */ int p_nums; /* number of parameters to transmit */ FmgrInfo *p_flinfo; /* output conversion functions for them */ @@ -283,6 +284,12 @@ static void postgresGetForeignRelSize(PlannerInfo *root, static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); +static List *generate_scan_tlist_for_relation(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableoid, + PgFdwRelationInfo *fpinfo, + List *tlist, + List *recheck_quals); static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, @@ -392,6 +399,7 @@ static PgFdwModifyState *create_foreign_modify(EState *estate, List *retrieved_attrs); static void prepare_foreign_modify(PgFdwModifyState *fmstate); static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, + Oid tableoid, ItemPointer tupleid, TupleTableSlot *slot); static void store_returning_result(PgFdwModifyState *fmstate, @@ -1117,6 +1125,109 @@ postgresGetForeignPaths(PlannerInfo *root, } } +/* + * generate_scan_tlist_for_relation : + * Constructs fdw_scan_tlist from the followig sources. + * + * We may have appended tableoid and ctid junk columns to the parse + * targetlist. We need to give alternative scan tlist to planner in the + * case. This function returns the tlist consists of the following attributes + * in the order. + * + * 1. Relation attributes requested by user and needed for recheck + * fpinfo->attrs_used, fdw_recheck_quals and given tlist. + * 2. Junk columns and others in root->processed_tlist which are not added by 1 + * + * If no junk column exists, returns NIL. + */ +static List * +generate_scan_tlist_for_relation(PlannerInfo *root, + RelOptInfo *foreignrel, Oid foreigntableoid, + PgFdwRelationInfo *fpinfo, + List *tlist, List *recheck_quals) +{ + Index frelid = foreignrel->relid; + List *fdw_scan_tlist = NIL; + Relation frel; + int base_nattrs; + ListCell *lc; + Bitmapset *attrs = NULL; + int attnum; + + /* + * RelOptInfo has expanded number of attributes. Check it against the base + * relations's attribute number to determine the necessity for alternative + * scan target list. + */ + frel = heap_open(foreigntableoid, NoLock); + base_nattrs = RelationGetNumberOfAttributes(frel); + heap_close(frel, NoLock); + + if (base_nattrs == foreignrel->max_attr) + return NIL; + + /* We have junk columns. Construct alternative scan target list. */ + + /* collect needed relation attributes */ + attrs = bms_copy(fpinfo->attrs_used); + pull_varattnos((Node *)recheck_quals, frelid, &attrs); + pull_varattnos((Node *)tlist, frelid, &attrs); + + /* Add relation's attributes */ + while ((attnum = bms_first_member(attrs)) >= 0) + { + TargetEntry *tle; + Form_pg_attribute attr; + Var *var; + char *name = NULL; + + attnum += FirstLowInvalidHeapAttributeNumber; + if (attnum < 1) + continue; + if (attnum > base_nattrs) + break; + + attr = TupleDescAttr(frel->rd_att, attnum - 1); + if (attr->attisdropped) + var = (Var *) makeNullConst(INT4OID, -1, InvalidOid); + else + { + var = makeVar(frelid, attnum, + attr->atttypid, attr->atttypmod, + attr->attcollation, 0); + name = pstrdup(NameStr(attr->attname)); + } + + tle = makeTargetEntry((Expr *)var, + list_length(fdw_scan_tlist) + 1, + name, + false); + fdw_scan_tlist = lappend(fdw_scan_tlist, tle); + } + + /* Add junk attributes */ + foreach (lc, root->processed_tlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + Var *var = (Var *) tle->expr; + + /* + * We aren't interested in non Vars, vars of other rels and base + * attributes. + */ + if (IsA(var, Var) && var->varno == frelid && + (var->varattno > base_nattrs || var->varattno < 1)) + { + Assert(tle->resjunk); + tle = copyObject(tle); + tle->resno = list_length(fdw_scan_tlist) + 1; + fdw_scan_tlist = lappend(fdw_scan_tlist, tle); + } + } + + return fdw_scan_tlist; +} + /* * postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -1140,10 +1251,11 @@ postgresGetForeignPlan(PlannerInfo *root, List *fdw_recheck_quals = NIL; List *retrieved_attrs; StringInfoData sql; - ListCell *lc; if (IS_SIMPLE_REL(foreignrel)) { + ListCell *lc; + /* * For base relations, set scan_relid as the relid of the relation. */ @@ -1191,6 +1303,17 @@ postgresGetForeignPlan(PlannerInfo *root, * should recheck all the remote quals. */ fdw_recheck_quals = remote_exprs; + + /* + * We may have put tableoid and ctid as junk columns to the + * targetlist. Generate fdw_scan_tlist in the case. + */ + fdw_scan_tlist = generate_scan_tlist_for_relation(root, + foreignrel, + foreigntableid, + fpinfo, + tlist, + fdw_recheck_quals); } else { @@ -1383,16 +1506,12 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) * into local representation and error reporting during that process. */ if (fsplan->scan.scanrelid > 0) - { fsstate->rel = node->ss.ss_currentRelation; - fsstate->tupdesc = RelationGetDescr(fsstate->rel); - } else - { fsstate->rel = NULL; - fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; - } + /* Always use the tuple descriptor privided by core */ + fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor; fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc); /* @@ -1543,22 +1662,30 @@ postgresAddForeignUpdateTargets(Query *parsetree, Var *var; const char *attrname; TargetEntry *tle; + int varattno = RelationGetNumberOfAttributes(target_relation) + 1; /* - * In postgres_fdw, what we need is the ctid, same as for a regular table. + * In postgres_fdw, what we need is the tableoid and ctid, same as for a + * regular table. */ - /* Make a Var representing the desired value */ + /* + * Table OID is needed to retrieved as a non-system junk column in the + * returning tuple. We add it as a column after all regular columns. + */ + attrname = "tableoid"; var = makeVar(parsetree->resultRelation, - SelfItemPointerAttributeNumber, - TIDOID, + varattno++, + OIDOID, -1, InvalidOid, 0); - /* Wrap it in a resjunk TLE with the right name ... */ - attrname = "ctid"; - + /* + * Wrap it in a resjunk TLE with a name accessible later by FDW. Doesn't + * seem that we explicitly free this tle but give pstrdup'ed string here + * just in case. + */ tle = makeTargetEntry((Expr *) var, list_length(parsetree->targetList) + 1, pstrdup(attrname), @@ -1566,6 +1693,29 @@ postgresAddForeignUpdateTargets(Query *parsetree, /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); + + /* ... also needs to have colname entry */ + target_rte->eref->colnames = + lappend(target_rte->eref->colnames, makeString(pstrdup(attrname))); + + + /* Do the same for ctid */ + attrname = "ctid"; + var = makeVar(parsetree->resultRelation, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + + tle = makeTargetEntry((Expr *) var, + list_length(parsetree->targetList) + 1, + pstrdup(attrname), + true); + + parsetree->targetList = lappend(parsetree->targetList, tle); + target_rte->eref->colnames = + lappend(target_rte->eref->colnames, makeString(pstrdup(attrname))); } /* @@ -1769,7 +1919,7 @@ postgresExecForeignInsert(EState *estate, prepare_foreign_modify(fmstate); /* Convert parameters needed by prepared statement to text form */ - p_values = convert_prep_stmt_params(fmstate, NULL, slot); + p_values = convert_prep_stmt_params(fmstate, InvalidOid, NULL, slot); /* * Execute the prepared statement. @@ -1824,7 +1974,7 @@ postgresExecForeignUpdate(EState *estate, TupleTableSlot *planSlot) { PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; - Datum datum; + Datum toiddatum, ctiddatum; bool isNull; const char **p_values; PGresult *res; @@ -1835,17 +1985,26 @@ postgresExecForeignUpdate(EState *estate, prepare_foreign_modify(fmstate); /* Get the ctid that was passed up as a resjunk column */ - datum = ExecGetJunkAttribute(planSlot, - fmstate->ctidAttno, - &isNull); + toiddatum = ExecGetJunkAttribute(planSlot, + fmstate->toidAttno, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "tableoid is NULL"); + + /* Get the ctid that was passed up as a resjunk column */ + ctiddatum = ExecGetJunkAttribute(planSlot, + fmstate->ctidAttno, + &isNull); /* shouldn't ever get a null result... */ if (isNull) elog(ERROR, "ctid is NULL"); /* Convert parameters needed by prepared statement to text form */ p_values = convert_prep_stmt_params(fmstate, - (ItemPointer) DatumGetPointer(datum), - slot); + DatumGetObjectId(toiddatum), + (ItemPointer) DatumGetPointer(ctiddatum), + slot); /* * Execute the prepared statement. @@ -1900,7 +2059,7 @@ postgresExecForeignDelete(EState *estate, TupleTableSlot *planSlot) { PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; - Datum datum; + Datum ctiddatum, toiddatum; bool isNull; const char **p_values; PGresult *res; @@ -1911,17 +2070,26 @@ postgresExecForeignDelete(EState *estate, prepare_foreign_modify(fmstate); /* Get the ctid that was passed up as a resjunk column */ - datum = ExecGetJunkAttribute(planSlot, - fmstate->ctidAttno, - &isNull); + toiddatum = ExecGetJunkAttribute(planSlot, + fmstate->toidAttno, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "tableoid is NULL"); + + /* Get the ctid that was passed up as a resjunk column */ + ctiddatum = ExecGetJunkAttribute(planSlot, + fmstate->ctidAttno, + &isNull); /* shouldn't ever get a null result... */ if (isNull) elog(ERROR, "ctid is NULL"); /* Convert parameters needed by prepared statement to text form */ p_values = convert_prep_stmt_params(fmstate, - (ItemPointer) DatumGetPointer(datum), - NULL); + DatumGetObjectId(toiddatum), + (ItemPointer) DatumGetPointer(ctiddatum), + NULL); /* * Execute the prepared statement. @@ -2303,6 +2471,28 @@ postgresPlanDirectModify(PlannerInfo *root, returningList); } + /* + * The junk columns in the targetlist is no longer needed for FDW direct + * moidfy. Strip them so that the planner doesn't bother. + */ + if (fscan->scan.scanrelid > 0 && fscan->fdw_scan_tlist != NIL) + { + List *newtlist = NIL; + ListCell *lc; + + fscan->fdw_scan_tlist = NIL; + foreach (lc, subplan->targetlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + /* once found junk, all the rest are also junk */ + if (tle->resjunk) + continue; + newtlist = lappend(newtlist, tle); + } + subplan->targetlist = newtlist; + } + /* * Construct the SQL command string. */ @@ -2349,7 +2539,7 @@ postgresPlanDirectModify(PlannerInfo *root, /* * Update the foreign-join-related fields. */ - if (fscan->scan.scanrelid == 0) + if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0) { /* No need for the outer subplan. */ fscan->scan.plan.lefttree = NULL; @@ -3345,7 +3535,7 @@ create_foreign_modify(EState *estate, fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc); /* Prepare for output conversion of parameters used in prepared stmt. */ - n_params = list_length(fmstate->target_attrs) + 1; + n_params = list_length(fmstate->target_attrs) + 2; fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params); fmstate->p_nums = 0; @@ -3353,13 +3543,24 @@ create_foreign_modify(EState *estate, { Assert(subplan != NULL); + /* Find the remote tableoid resjunk column in the subplan's result */ + fmstate->toidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, + "tableoid"); + if (!AttributeNumberIsValid(fmstate->toidAttno)) + elog(ERROR, "could not find junk tableoid column"); + + /* First transmittable parameter will be table oid */ + getTypeOutputInfo(OIDOID, &typefnoid, &isvarlena); + fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; + /* Find the ctid resjunk column in the subplan's result */ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid"); if (!AttributeNumberIsValid(fmstate->ctidAttno)) elog(ERROR, "could not find junk ctid column"); - /* First transmittable parameter will be ctid */ + /* Second transmittable parameter will be ctid */ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena); fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); fmstate->p_nums++; @@ -3442,6 +3643,7 @@ prepare_foreign_modify(PgFdwModifyState *fmstate) */ static const char ** convert_prep_stmt_params(PgFdwModifyState *fmstate, + Oid tableoid, ItemPointer tupleid, TupleTableSlot *slot) { @@ -3453,10 +3655,15 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate, p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums); - /* 1st parameter should be ctid, if it's in use */ - if (tupleid != NULL) + /* First two parameters should be tableoid and ctid, if it's in use */ + if (tableoid != InvalidOid) { + Assert (tupleid != NULL); + /* don't need set_transmission_modes for TID output */ + p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], + ObjectIdGetDatum(tableoid)); + pindex++; p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], PointerGetDatum(tupleid)); pindex++; @@ -3685,8 +3892,8 @@ rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist) new_tlist = lappend(new_tlist, makeTargetEntry(tle->expr, list_length(new_tlist) + 1, - NULL, - false)); + tle->resname, + tle->resjunk)); } fscan->fdw_scan_tlist = new_tlist; } @@ -5576,12 +5783,18 @@ make_tuple_from_result_row(PGresult *res, */ oldcontext = MemoryContextSwitchTo(temp_context); - if (rel) - tupdesc = RelationGetDescr(rel); + /* + * If fdw_scan_tlist is provided for base relation, use the tuple + * descriptor given from planner. + */ + if (!rel || + (fsstate && + castNode(ForeignScan, fsstate->ss.ps.plan)->fdw_scan_tlist != NULL)) + tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; else { - Assert(fsstate); - tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + Assert(rel); + tupdesc = RelationGetDescr(rel); } values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); @@ -5623,7 +5836,7 @@ make_tuple_from_result_row(PGresult *res, errpos.cur_attno = i; if (i > 0) { - /* ordinary column */ + /* ordinary column and tableoid */ Assert(i <= tupdesc->natts); nulls[i - 1] = (valstr == NULL); /* Apply the input function even to nulls, to support domains */ -- 2.16.3