1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-05 07:21:24 +03:00

Implement an API to let foreign-data wrappers actually be functional.

This commit provides the core code and documentation needed.  A contrib
module test case will follow shortly.

Shigeru Hanada, Jan Urbanski, Heikki Linnakangas
This commit is contained in:
Tom Lane
2011-02-20 00:17:18 -05:00
parent d5813488a4
commit bb74240794
39 changed files with 1202 additions and 62 deletions

View File

@ -22,6 +22,7 @@
#include "commands/trigger.h"
#include "executor/hashjoin.h"
#include "executor/instrument.h"
#include "foreign/fdwapi.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "optimizer/var.h"
@ -80,25 +81,15 @@ static void show_sort_keys_common(PlanState *planstate,
List *ancestors, ExplainState *es);
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
const char *relationship, ExplainState *es);
static void ExplainPropertyList(const char *qlabel, List *data,
ExplainState *es);
static void ExplainProperty(const char *qlabel, const char *value,
bool numeric, ExplainState *es);
#define ExplainPropertyText(qlabel, value, es) \
ExplainProperty(qlabel, value, false, es)
static void ExplainPropertyInteger(const char *qlabel, int value,
ExplainState *es);
static void ExplainPropertyLong(const char *qlabel, long value,
ExplainState *es);
static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es);
static void ExplainOpenGroup(const char *objtype, const char *labelname,
bool labeled, ExplainState *es);
static void ExplainCloseGroup(const char *objtype, const char *labelname,
@ -705,6 +696,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_WorkTableScan:
pname = sname = "WorkTable Scan";
break;
case T_ForeignScan:
pname = sname = "Foreign Scan";
break;
case T_Material:
pname = sname = "Materialize";
break;
@ -854,6 +848,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
ExplainScanTarget((Scan *) plan, es);
break;
case T_BitmapIndexScan:
@ -1057,6 +1052,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
}
break;
case T_ForeignScan:
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
show_foreignscan_info((ForeignScanState *) planstate, es);
break;
case T_NestLoop:
show_upper_qual(((NestLoop *) plan)->join.joinqual,
"Join Filter", planstate, ancestors, es);
@ -1523,6 +1522,18 @@ show_hash_info(HashState *hashstate, ExplainState *es)
}
}
/*
* Show extra information for a ForeignScan node.
*/
static void
show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
{
FdwRoutine *fdwroutine = fsstate->fdwroutine;
/* Let the FDW emit whatever fields it wants */
fdwroutine->ExplainForeignScan(fsstate, es);
}
/*
* Fetch the name of an index in an EXPLAIN
*
@ -1570,6 +1581,7 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
case T_IndexScan:
case T_BitmapHeapScan:
case T_TidScan:
case T_ForeignScan:
/* Assert it's on a real relation */
Assert(rte->rtekind == RTE_RELATION);
objectname = get_rel_name(rte->relid);
@ -1695,7 +1707,7 @@ ExplainSubPlans(List *plans, List *ancestors,
* Explain a property, such as sort keys or targets, that takes the form of
* a list of unlabeled items. "data" is a list of C strings.
*/
static void
void
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
{
ListCell *lc;
@ -1817,10 +1829,19 @@ ExplainProperty(const char *qlabel, const char *value, bool numeric,
}
}
/*
* Explain a string-valued property.
*/
void
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
{
ExplainProperty(qlabel, value, false, es);
}
/*
* Explain an integer-valued property.
*/
static void
void
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
{
char buf[32];
@ -1832,7 +1853,7 @@ ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
/*
* Explain a long-integer-valued property.
*/
static void
void
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
{
char buf[32];
@ -1845,7 +1866,7 @@ ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
* Explain a float-valued property, using the specified number of
* fractional digits.
*/
static void
void
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es)
{

View File

@ -23,6 +23,6 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
nodeWindowAgg.o tstoreReceiver.o spi.o
nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o
include $(top_srcdir)/src/backend/common.mk

View File

@ -21,6 +21,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeGroup.h"
@ -186,6 +187,10 @@ ExecReScan(PlanState *node)
ExecReScanWorkTableScan((WorkTableScanState *) node);
break;
case T_ForeignScanState:
ExecReScanForeignScan((ForeignScanState *) node);
break;
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node);
break;

View File

@ -738,6 +738,13 @@ InitPlan(QueryDesc *queryDesc, int eflags)
break;
}
/* if foreign table, tuples can't be locked */
if (relation && relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign table \"%s\"",
RelationGetRelationName(relation))));
erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
erm->relation = relation;
erm->rti = rc->rti;

View File

@ -85,6 +85,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
@ -232,6 +233,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
case T_ForeignScan:
result = (PlanState *) ExecInitForeignScan((ForeignScan *) node,
estate, eflags);
break;
/*
* join nodes
*/
@ -422,6 +428,10 @@ ExecProcNode(PlanState *node)
result = ExecWorkTableScan((WorkTableScanState *) node);
break;
case T_ForeignScanState:
result = ExecForeignScan((ForeignScanState *) node);
break;
/*
* join nodes
*/
@ -650,6 +660,10 @@ ExecEndNode(PlanState *node)
ExecEndWorkTableScan((WorkTableScanState *) node);
break;
case T_ForeignScanState:
ExecEndForeignScan((ForeignScanState *) node);
break;
/*
* join nodes
*/

View File

@ -0,0 +1,209 @@
/*-------------------------------------------------------------------------
*
* nodeForeignscan.c
* Routines to support scans of foreign tables
*
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/executor/nodeForeignscan.c
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
*
* ExecForeignScan scans a foreign table.
* ExecInitForeignScan creates and initializes state info.
* ExecReScanForeignScan rescans the foreign relation.
* ExecEndForeignScan releases any resources allocated.
*/
#include "postgres.h"
#include "executor/executor.h"
#include "executor/nodeForeignscan.h"
#include "foreign/fdwapi.h"
static TupleTableSlot *ForeignNext(ForeignScanState *node);
static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot);
/* ----------------------------------------------------------------
* ForeignNext
*
* This is a workhorse for ExecForeignScan
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ForeignNext(ForeignScanState *node)
{
TupleTableSlot *slot;
ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
ExprContext *econtext = node->ss.ps.ps_ExprContext;
MemoryContext oldcontext;
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
/*
* If any system columns are requested, we have to force the tuple into
* physical-tuple form to avoid "cannot extract system attribute from
* virtual tuple" errors later. We also insert a valid value for
* tableoid, which is the only actually-useful system column.
*/
if (plan->fsSystemCol && !TupIsNull(slot))
{
HeapTuple tup = ExecMaterializeSlot(slot);
tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
}
return slot;
}
/*
* ForeignRecheck -- access method routine to recheck a tuple in EvalPlanQual
*/
static bool
ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot)
{
/* There are no access-method-specific conditions to recheck. */
return true;
}
/* ----------------------------------------------------------------
* ExecForeignScan(node)
*
* Fetches the next tuple from the FDW, checks local quals, and
* returns it.
* We call the ExecScan() routine and pass it the appropriate
* access method functions.
* ----------------------------------------------------------------
*/
TupleTableSlot *
ExecForeignScan(ForeignScanState *node)
{
return ExecScan((ScanState *) node,
(ExecScanAccessMtd) ForeignNext,
(ExecScanRecheckMtd) ForeignRecheck);
}
/* ----------------------------------------------------------------
* ExecInitForeignScan
* ----------------------------------------------------------------
*/
ForeignScanState *
ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
{
ForeignScanState *scanstate;
Relation currentRelation;
FdwRoutine *fdwroutine;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
/*
* create state structure
*/
scanstate = makeNode(ForeignScanState);
scanstate->ss.ps.plan = (Plan *) node;
scanstate->ss.ps.state = estate;
/*
* Miscellaneous initialization
*
* create expression context for node
*/
ExecAssignExprContext(estate, &scanstate->ss.ps);
scanstate->ss.ps.ps_TupFromTlist = false;
/*
* initialize child expressions
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
(PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
(PlanState *) scanstate);
/*
* tuple table initialization
*/
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
ExecInitScanTupleSlot(estate, &scanstate->ss);
/*
* open the base relation and acquire appropriate lock on it.
*/
currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
scanstate->ss.ss_currentRelation = currentRelation;
/*
* get the scan type from the relation descriptor.
*/
ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
/*
* Initialize result tuple type and projection info.
*/
ExecAssignResultTypeFromTL(&scanstate->ss.ps);
ExecAssignScanProjectionInfo(&scanstate->ss);
/*
* Acquire function pointers from the FDW's handler, and init fdw_state.
*/
fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
/*
* Tell the FDW to initiate the scan.
*/
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate;
}
/* ----------------------------------------------------------------
* ExecEndForeignScan
*
* frees any storage allocated through C routines.
* ----------------------------------------------------------------
*/
void
ExecEndForeignScan(ForeignScanState *node)
{
/* Let the FDW shut down */
node->fdwroutine->EndForeignScan(node);
/* Free the exprcontext */
ExecFreeExprContext(&node->ss.ps);
/* clean out the tuple table */
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
ExecClearTuple(node->ss.ss_ScanTupleSlot);
/* close the relation. */
ExecCloseScanRelation(node->ss.ss_currentRelation);
}
/* ----------------------------------------------------------------
* ExecReScanForeignScan
*
* Rescans the relation.
* ----------------------------------------------------------------
*/
void
ExecReScanForeignScan(ForeignScanState *node)
{
node->fdwroutine->ReScanForeignScan(node);
ExecScanReScan(&node->ss);
}

View File

@ -16,8 +16,10 @@
#include "catalog/namespace.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "funcapi.h"
#include "miscadmin.h"
@ -54,19 +56,22 @@ GetForeignDataWrapper(Oid fdwid)
fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
fdw = palloc(sizeof(ForeignDataWrapper));
fdw = (ForeignDataWrapper *) palloc(sizeof(ForeignDataWrapper));
fdw->fdwid = fdwid;
fdw->owner = fdwform->fdwowner;
fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
fdw->fdwhandler = fdwform->fdwhandler;
fdw->fdwvalidator = fdwform->fdwvalidator;
/* Extract the options */
/* Extract the fdwoptions */
datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
tp,
Anum_pg_foreign_data_wrapper_fdwoptions,
&isnull);
fdw->options = untransformRelOptions(datum);
if (isnull)
fdw->options = NIL;
else
fdw->options = untransformRelOptions(datum);
ReleaseSysCache(tp);
@ -88,7 +93,8 @@ GetForeignDataWrapperOidByName(const char *fdwname, bool missing_ok)
if (!OidIsValid(fdwId) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("foreign-data wrapper \"%s\" does not exist", fdwname)));
errmsg("foreign-data wrapper \"%s\" does not exist",
fdwname)));
return fdwId;
}
@ -103,7 +109,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
{
Oid fdwId = GetForeignDataWrapperOidByName(fdwname, missing_ok);
if (!OidIsValid(fdwId) && missing_ok)
if (!OidIsValid(fdwId))
return NULL;
return GetForeignDataWrapper(fdwId);
@ -129,7 +135,7 @@ GetForeignServer(Oid serverid)
serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
server = palloc(sizeof(ForeignServer));
server = (ForeignServer *) palloc(sizeof(ForeignServer));
server->serverid = serverid;
server->servername = pstrdup(NameStr(serverform->srvname));
server->owner = serverform->srvowner;
@ -154,9 +160,10 @@ GetForeignServer(Oid serverid)
tp,
Anum_pg_foreign_server_srvoptions,
&isnull);
/* untransformRelOptions does exactly what we want - avoid duplication */
server->options = untransformRelOptions(datum);
if (isnull)
server->options = NIL;
else
server->options = untransformRelOptions(datum);
ReleaseSysCache(tp);
@ -191,7 +198,7 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
{
Oid serverid = GetForeignServerOidByName(srvname, missing_ok);
if (!OidIsValid(serverid) && missing_ok)
if (!OidIsValid(serverid))
return NULL;
return GetForeignServer(serverid);
@ -233,16 +240,19 @@ GetUserMapping(Oid userid, Oid serverid)
umform = (Form_pg_user_mapping) GETSTRUCT(tp);
um = (UserMapping *) palloc(sizeof(UserMapping));
um->userid = userid;
um->serverid = serverid;
/* Extract the umoptions */
datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
tp,
Anum_pg_user_mapping_umoptions,
&isnull);
um = palloc(sizeof(UserMapping));
um->userid = userid;
um->serverid = serverid;
um->options = untransformRelOptions(datum);
if (isnull)
um->options = NIL;
else
um->options = untransformRelOptions(datum);
ReleaseSysCache(tp);
@ -250,6 +260,116 @@ GetUserMapping(Oid userid, Oid serverid)
}
/*
* GetForeignTable - look up the foreign table definition by relation oid.
*/
ForeignTable *
GetForeignTable(Oid relid)
{
Form_pg_foreign_table tableform;
ForeignTable *ft;
HeapTuple tp;
Datum datum;
bool isnull;
tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign table %u", relid);
tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
ft = (ForeignTable *) palloc(sizeof(ForeignTable));
ft->relid = relid;
ft->serverid = tableform->ftserver;
/* Extract the ftoptions */
datum = SysCacheGetAttr(FOREIGNTABLEREL,
tp,
Anum_pg_foreign_table_ftoptions,
&isnull);
if (isnull)
ft->options = NIL;
else
ft->options = untransformRelOptions(datum);
ReleaseSysCache(tp);
return ft;
}
/*
* GetFdwRoutine - call the specified foreign-data wrapper handler routine
* to get its FdwRoutine struct.
*/
FdwRoutine *
GetFdwRoutine(Oid fdwhandler)
{
Datum datum;
FdwRoutine *routine;
datum = OidFunctionCall0(fdwhandler);
routine = (FdwRoutine *) DatumGetPointer(datum);
if (routine == NULL || !IsA(routine, FdwRoutine))
elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
fdwhandler);
return routine;
}
/*
* GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper
* for the given foreign table, and retrieve its FdwRoutine struct.
*/
FdwRoutine *
GetFdwRoutineByRelId(Oid relid)
{
HeapTuple tp;
Form_pg_foreign_data_wrapper fdwform;
Form_pg_foreign_server serverform;
Form_pg_foreign_table tableform;
Oid serverid;
Oid fdwid;
Oid fdwhandler;
/* Get server OID for the foreign table. */
tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign table %u", relid);
tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
serverid = tableform->ftserver;
ReleaseSysCache(tp);
/* Get foreign-data wrapper OID for the server. */
tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign server %u", serverid);
serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
fdwid = serverform->srvfdw;
ReleaseSysCache(tp);
/* Get handler function OID for the FDW. */
tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
fdwhandler = fdwform->fdwhandler;
/* Complain if FDW has been set to NO HANDLER. */
if (!OidIsValid(fdwhandler))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("foreign-data wrapper \"%s\" has no handler",
NameStr(fdwform->fdwname))));
ReleaseSysCache(tp);
/* And finally, call the handler function. */
return GetFdwRoutine(fdwhandler);
}
/*
* deflist_to_tuplestore - Helper function to convert DefElem list to
* tuplestore usable in SRF.
@ -261,7 +381,7 @@ deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options)
TupleDesc tupdesc;
Tuplestorestate *tupstore;
Datum values[2];
bool nulls[2] = {0};
bool nulls[2];
MemoryContext per_query_ctx;
MemoryContext oldcontext;
@ -294,6 +414,7 @@ deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options)
values[0] = CStringGetTextDatum(def->defname);
values[1] = CStringGetTextDatum(((Value *) def->arg)->val.str);
nulls[0] = nulls[1] = false;
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
@ -313,7 +434,8 @@ pg_options_to_table(PG_FUNCTION_ARGS)
{
Datum array = PG_GETARG_DATUM(0);
deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, untransformRelOptions(array));
deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo,
untransformRelOptions(array));
return (Datum) 0;
}
@ -407,7 +529,8 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid option \"%s\"", def->defname),
errhint("Valid options in this context are: %s", buf.data)));
errhint("Valid options in this context are: %s",
buf.data)));
PG_RETURN_BOOL(false);
}

View File

@ -23,6 +23,7 @@
#include "postgres.h"
#include "miscadmin.h"
#include "foreign/fdwapi.h"
#include "nodes/plannodes.h"
#include "nodes/relation.h"
#include "utils/datum.h"
@ -549,6 +550,43 @@ _copyWorkTableScan(WorkTableScan *from)
return newnode;
}
/*
* _copyForeignScan
*/
static ForeignScan *
_copyForeignScan(ForeignScan *from)
{
ForeignScan *newnode = makeNode(ForeignScan);
/*
* copy node superclass fields
*/
CopyScanFields((Scan *) from, (Scan *) newnode);
/*
* copy remainder of node
*/
COPY_SCALAR_FIELD(fsSystemCol);
COPY_NODE_FIELD(fdwplan);
return newnode;
}
/*
* _copyFdwPlan
*/
static FdwPlan *
_copyFdwPlan(FdwPlan *from)
{
FdwPlan *newnode = makeNode(FdwPlan);
COPY_SCALAR_FIELD(startup_cost);
COPY_SCALAR_FIELD(total_cost);
COPY_NODE_FIELD(fdw_private);
return newnode;
}
/*
* CopyJoinFields
*
@ -3839,6 +3877,12 @@ copyObject(void *from)
case T_WorkTableScan:
retval = _copyWorkTableScan(from);
break;
case T_ForeignScan:
retval = _copyForeignScan(from);
break;
case T_FdwPlan:
retval = _copyFdwPlan(from);
break;
case T_Join:
retval = _copyJoin(from);
break;

View File

@ -24,6 +24,7 @@
#include <ctype.h>
#include "lib/stringinfo.h"
#include "foreign/fdwapi.h"
#include "nodes/plannodes.h"
#include "nodes/relation.h"
#include "utils/datum.h"
@ -537,6 +538,27 @@ _outWorkTableScan(StringInfo str, WorkTableScan *node)
WRITE_INT_FIELD(wtParam);
}
static void
_outForeignScan(StringInfo str, ForeignScan *node)
{
WRITE_NODE_TYPE("FOREIGNSCAN");
_outScanInfo(str, (Scan *) node);
WRITE_BOOL_FIELD(fsSystemCol);
WRITE_NODE_FIELD(fdwplan);
}
static void
_outFdwPlan(StringInfo str, FdwPlan *node)
{
WRITE_NODE_TYPE("FDWPLAN");
WRITE_FLOAT_FIELD(startup_cost, "%.2f");
WRITE_FLOAT_FIELD(total_cost, "%.2f");
WRITE_NODE_FIELD(fdw_private);
}
static void
_outJoin(StringInfo str, Join *node)
{
@ -1507,6 +1529,16 @@ _outTidPath(StringInfo str, TidPath *node)
WRITE_NODE_FIELD(tidquals);
}
static void
_outForeignPath(StringInfo str, ForeignPath *node)
{
WRITE_NODE_TYPE("FOREIGNPATH");
_outPathInfo(str, (Path *) node);
WRITE_NODE_FIELD(fdwplan);
}
static void
_outAppendPath(StringInfo str, AppendPath *node)
{
@ -2672,6 +2704,12 @@ _outNode(StringInfo str, void *obj)
case T_WorkTableScan:
_outWorkTableScan(str, obj);
break;
case T_ForeignScan:
_outForeignScan(str, obj);
break;
case T_FdwPlan:
_outFdwPlan(str, obj);
break;
case T_Join:
_outJoin(str, obj);
break;
@ -2877,6 +2915,9 @@ _outNode(StringInfo str, void *obj)
case T_TidPath:
_outTidPath(str, obj);
break;
case T_ForeignPath:
_outForeignPath(str, obj);
break;
case T_AppendPath:
_outAppendPath(str, obj);
break;

View File

@ -347,6 +347,7 @@ RelOptInfo - a relation or joined relations
IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID
ForeignPath - scan a foreign table
AppendPath - append multiple subpaths together
MergeAppendPath - merge multiple subpaths, preserving their common sort order
ResultPath - a Result plan node (used for FROM-less SELECT)

View File

@ -17,6 +17,7 @@
#include <math.h>
#include "catalog/pg_class.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
#include "nodes/print.h"
@ -34,6 +35,7 @@
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
/* These parameters are set by GUC */
@ -63,6 +65,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
bool *differentTypes);
@ -197,9 +201,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
}
else
{
/* Plain relation */
Assert(rel->rtekind == RTE_RELATION);
set_plain_rel_pathlist(root, rel, rte);
if (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
{
/* Foreign table */
set_foreign_pathlist(root, rel, rte);
}
else
{
/* Plain relation */
set_plain_rel_pathlist(root, rel, rte);
}
}
#ifdef OPTIMIZER_DEBUG
@ -904,6 +916,23 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
set_cheapest(rel);
}
/*
* set_foreign_pathlist
* Build the (single) access path for a foreign table RTE
*/
static void
set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
/* Mark rel with estimated output rows, width, etc */
set_foreign_size_estimates(root, rel);
/* Generate appropriate path */
add_path(rel, (Path *) create_foreignscan_path(root, rel));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
/*
* make_rel_from_joinlist
* Build access paths using a "joinlist" to guide the join path search.
@ -1503,6 +1532,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_TidPath:
ptype = "TidScan";
break;
case T_ForeignPath:
ptype = "ForeignScan";
break;
case T_AppendPath:
ptype = "Append";
break;

View File

@ -3324,6 +3324,34 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
set_baserel_size_estimates(root, rel);
}
/*
* set_foreign_size_estimates
* Set the size estimates for a base relation that is a foreign table.
*
* There is not a whole lot that we can do here; the foreign-data wrapper
* is responsible for producing useful estimates. We can do a decent job
* of estimating baserestrictcost, so we set that, and we also set up width
* using what will be purely datatype-driven estimates from the targetlist.
* There is no way to do anything sane with the rows value, so we just put
* a default estimate and hope that the wrapper can improve on it. The
* wrapper's PlanForeignScan function will be called momentarily.
*
* The rel's targetlist and restrictinfo list must have been constructed
* already.
*/
void
set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
/* Should only be applied to base relations */
Assert(rel->relid > 0);
rel->rows = 1000; /* entirely bogus default estimate */
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
set_rel_width(root, rel);
}
/*
* set_rel_width

View File

@ -20,6 +20,7 @@
#include <math.h>
#include "access/skey.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@ -71,6 +72,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
@ -112,6 +115,8 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
Index scanrelid, int ctePlanId, int cteParam);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
Index scanrelid, bool fsSystemCol, FdwPlan *fdwplan);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
@ -206,6 +211,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
plan = create_scan_plan(root, best_path);
break;
case T_HashJoin:
@ -346,6 +352,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
scan_clauses);
break;
case T_ForeignScan:
plan = (Plan *) create_foreignscan_plan(root,
(ForeignPath *) best_path,
tlist,
scan_clauses);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
@ -468,6 +481,7 @@ disuse_physical_tlist(Plan *plan, Path *path)
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
plan->targetlist = build_relation_tlist(path->parent);
break;
default:
@ -1755,6 +1769,56 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
return scan_plan;
}
/*
* create_foreignscan_plan
* Returns a foreignscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static ForeignScan *
create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses)
{
ForeignScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
RangeTblEntry *rte;
bool fsSystemCol;
int i;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(rel->rtekind == RTE_RELATION);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_RELATION);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Detect whether any system columns are requested from rel */
fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
{
fsSystemCol = true;
break;
}
}
scan_plan = make_foreignscan(tlist,
scan_clauses,
scan_relid,
fsSystemCol,
best_path->fdwplan);
copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*****************************************************************************
*
@ -2978,6 +3042,28 @@ make_worktablescan(List *qptlist,
return node;
}
static ForeignScan *
make_foreignscan(List *qptlist,
List *qpqual,
Index scanrelid,
bool fsSystemCol,
FdwPlan *fdwplan)
{
ForeignScan *node = makeNode(ForeignScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->fsSystemCol = fsSystemCol;
node->fdwplan = fdwplan;
return node;
}
Append *
make_append(List *appendplans, List *tlist)
{

View File

@ -1914,7 +1914,8 @@ preprocess_rowmarks(PlannerInfo *root)
newrc->rti = newrc->prti = i;
newrc->rowmarkId = ++(root->glob->lastRowMarkId);
/* real tables support REFERENCE, anything else needs COPY */
if (rte->rtekind == RTE_RELATION)
if (rte->rtekind == RTE_RELATION &&
get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
newrc->markType = ROW_MARK_REFERENCE;
else
newrc->markType = ROW_MARK_COPY;

View File

@ -402,6 +402,18 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
}
break;
case T_ForeignScan:
{
ForeignScan *splan = (ForeignScan *) plan;
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
}
break;
case T_NestLoop:
case T_MergeJoin:
case T_HashJoin:

View File

@ -2054,6 +2054,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
context.paramids = bms_add_members(context.paramids, scan_params);
break;
case T_ForeignScan:
context.paramids = bms_add_members(context.paramids, scan_params);
break;
case T_ModifyTable:
{
ModifyTable *mtplan = (ModifyTable *) plan;

View File

@ -17,6 +17,7 @@
#include <math.h>
#include "catalog/pg_operator.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@ -1419,6 +1420,41 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
return pathnode;
}
/*
* create_foreignscan_path
* Creates a path corresponding to a scan of a foreign table,
* returning the pathnode.
*/
ForeignPath *
create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
{
ForeignPath *pathnode = makeNode(ForeignPath);
RangeTblEntry *rte;
FdwRoutine *fdwroutine;
FdwPlan *fdwplan;
pathnode->path.pathtype = T_ForeignScan;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* result is always unordered */
/* Get FDW's callback info */
rte = planner_rt_fetch(rel->relid, root);
fdwroutine = GetFdwRoutineByRelId(rte->relid);
/* Let the FDW do its planning */
fdwplan = fdwroutine->PlanForeignScan(rte->relid, root, rel);
if (fdwplan == NULL || !IsA(fdwplan, FdwPlan))
elog(ERROR, "foreign-data wrapper PlanForeignScan function for relation %u did not return an FdwPlan struct",
rte->relid);
pathnode->fdwplan = fdwplan;
/* use costs estimated by FDW */
pathnode->path.startup_cost = fdwplan->startup_cost;
pathnode->path.total_cost = fdwplan->total_cost;
return pathnode;
}
/*
* create_nestloop_path
* Creates a pathnode corresponding to a nestloop join between two

View File

@ -90,12 +90,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
*/
relation = heap_open(relationObjectId, NoLock);
/* Foreign table scans are not implemented yet. */
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign table scans are not yet supported")));
rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
rel->max_attr = RelationGetNumberOfAttributes(relation);
rel->reltablespace = RelationGetForm(relation)->reltablespace;
@ -463,6 +457,11 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
*pages = 1;
*tuples = 1;
break;
case RELKIND_FOREIGN_TABLE:
/* Just use whatever's in pg_class */
*pages = rel->rd_rel->relpages;
*tuples = rel->rd_rel->reltuples;
break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;

View File

@ -40,6 +40,7 @@
#include "parser/parse_target.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
@ -2176,9 +2177,14 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
/* ignore foreign tables */
if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
{
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
}
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
@ -2225,6 +2231,12 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
if (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign table \"%s\"",
get_rel_name(rte->relid)),
parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
pushedDown);

View File

@ -1392,8 +1392,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
if (rte->rtekind == RTE_RELATION)
{
applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
/* ignore foreign tables */
if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
{
applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
}
}
else if (rte->rtekind == RTE_SUBQUERY)
{

View File

@ -1621,6 +1621,26 @@ FunctionCall9(FmgrInfo *flinfo, Datum arg1, Datum arg2,
* by FunctionCallN(). If the same function is to be invoked repeatedly,
* do the fmgr_info() once and then use FunctionCallN().
*/
Datum
OidFunctionCall0(Oid functionId)
{
FmgrInfo flinfo;
FunctionCallInfoData fcinfo;
Datum result;
fmgr_info(functionId, &flinfo);
InitFunctionCallInfoData(fcinfo, &flinfo, 0, NULL, NULL);
result = FunctionCallInvoke(&fcinfo);
/* Check for null result, since caller is clearly not expecting one */
if (fcinfo.isnull)
elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
return result;
}
Datum
OidFunctionCall1(Oid functionId, Datum arg1)
{