Add ForeignScan node which executes simple scan on one foreign
authorShigeru Hanada <hanada@metrosystems.co.jp>
Wed, 15 Dec 2010 07:46:33 +0000 (16:46 +0900)
committerShigeru Hanada <hanada@metrosystems.co.jp>
Wed, 15 Dec 2010 07:46:33 +0000 (16:46 +0900)
table.

29 files changed:
doc/src/sgml/ref/explain.sgml
doc/src/sgml/ref/lock.sgml
doc/src/sgml/ref/select.sgml
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execMain.c
src/backend/executor/execProcnode.c
src/backend/executor/nodeForeignscan.c [new file with mode: 0644]
src/backend/foreign/foreign.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/plancat.c
src/include/executor/nodeForeignscan.h [new file with mode: 0644]
src/include/foreign/fdwapi.h [new file with mode: 0644]
src/include/foreign/foreign.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/plannodes.h
src/include/nodes/relation.h
src/include/optimizer/pathnode.h
src/test/regress/expected/foreign_data.out
src/test/regress/sql/foreign_data.sql

index 3d7d13c0c10d46dc33887b9625c4f239d5e29152..35f9331f954fc41c4e4df03f989fee766494cace 100644 (file)
@@ -129,8 +129,9 @@ ROLLBACK;
       Display additional information regarding the plan.  Specifically, include
       the output column list for each node in the plan tree, schema-qualify
       table and function names, always label variables in expressions with
-      their range table alias, and always print the name of each trigger for
-      which statistics are displayed.  This parameter defaults to
+      their range table alias, always print the name of each trigger for
+      which statistics are displayed, and print FDW-specific information for
+      each ForeignScan node in the plan tree.  This parameter defaults to
       <literal>FALSE</literal>.
      </para>
     </listitem>
index 86cd744ea4b23fb58ae492a189e51fb9ba8c6c09..1e571ce80c4a7ae2d1dc5bff7e5a12f475086f7f 100644 (file)
@@ -108,7 +108,8 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [, ...
     <term><replaceable class="PARAMETER">name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of an existing table to
+      The name (optionally schema-qualified) of an existing table
+      or a existing foreign table to
       lock.  If <literal>ONLY</> is specified, only that table is
       locked.  If <literal>ONLY</> is not specified, the table and all
       its descendant tables (if any) are locked.
index 24f8249713935b21e06cc87aa4525de46795a471..e5f7c863d0c80ee9edd7adde042b39e06e00348b 100644 (file)
@@ -195,6 +195,8 @@ TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] |
    or <literal>FOR SHARE</literal> requires
    <literal>UPDATE</literal> privilege as well (for at least one column
    of each table so selected).
+   So you cannot lock contents of a foreign table because only SELECT
+   privilege can be granted on foreign tables.
   </para>
  </refsect1>
 
index 99f5c295324f6a2bf3dbf9c064b2a3d7eeb30533..a7aace4f7d7edefeacc91b34041d36f0f5723538 100644 (file)
@@ -705,6 +705,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 +857,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:
@@ -1033,6 +1037,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
        case T_ValuesScan:
        case T_CteScan:
        case T_WorkTableScan:
+       case T_ForeignScan:
        case T_SubqueryScan:
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            break;
@@ -1100,6 +1105,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
            break;
    }
 
+   /* Show FDW specific information, if any */
+   if (es->verbose && IsA(plan, ForeignScan))
+   {
+       ForeignScan *scan = (ForeignScan *) plan;
+       if (scan->fplan->explainInfo)
+           ExplainPropertyText("FDW-Info", scan->fplan->explainInfo, es);
+   }
+
    /* Show buffer usage */
    if (es->buffers)
    {
@@ -1570,6 +1583,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);
index da4fbb440bc5cd9c41323d021945a9c4ac7069c5..a854c9a5dc68051cdab6a3832ad2b4b07b5a10d4 100644 (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
index d999a4481d2397af14b380ae78166edbaea78b5d..67b64d7d4c96beed9c79762ceece8f40562a51e6 100644 (file)
@@ -22,6 +22,7 @@
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
 #include "executor/nodeFunctionscan.h"
+#include "executor/nodeForeignscan.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeHash.h"
@@ -186,6 +187,10 @@ ExecReScan(PlanState *node)
            ExecReScanWorkTableScan((WorkTableScanState *) node);
            break;
 
+       case T_ForeignScanState:
+           ExecForeignReScan((ForeignScanState *) node);
+           break;
+
        case T_NestLoopState:
            ExecReScanNestLoop((NestLoopState *) node);
            break;
index c4719f33b96aa92df194127d026aef21b4ebb56d..73210cb3b1b7fd79269f0b5c6959ae98bf60480d 100644 (file)
@@ -934,6 +934,12 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
                    break;
            }
            break;
+       case RELKIND_FOREIGN_TABLE:
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("cannot change foreign table \"%s\"",
+                           RelationGetRelationName(resultRelationDesc))));
+           break;
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
index edd175e1299365fde50a2b84bf15a8d18838a722..5ba828d1ad83942b702e074a36f45a1aa0e1e524 100644 (file)
@@ -86,6 +86,7 @@
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
 #include "executor/nodeFunctionscan.h"
+#include "executor/nodeForeignscan.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeHash.h"
 #include "executor/nodeHashjoin.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
             */
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644 (file)
index 0000000..1831b00
--- /dev/null
@@ -0,0 +1,259 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.c
+ *   Support routines for sequential scans of foreign tables.
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *   src/backend/executor/nodeForeignscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *     ExecForeignScan             sequentially scans a foreign table.
+ *     ExecForeignNext             retrieve next tuple in sequential order.
+ *     ExecInitForeignScan         creates and initializes a seqscan node.
+ *     ExecEndForeignScan          releases any storage allocated.
+ *     ExecForeignReScan           rescans the foreign table
+ *     ExecForeignMarkPos          marks scan position
+ *     ExecForeignRestrPos         restores scan position
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeForeignscan.h"
+#include "foreign/foreign.h"
+#include "miscadmin.h"
+
+static TupleTableSlot *ForeignNext(ForeignScanState *node);
+static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot);
+
+/* ----------------------------------------------------------------
+ *                     Scan Support
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------------------------------------------------------
+ *     ForeignNext
+ *
+ *     This is a workhorse for ExecForeignScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ForeignNext(ForeignScanState *node)
+{
+   TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+
+   Assert(node->ss.ps.state->es_direction == ForwardScanDirection);
+
+   /* tupleslot will be filled by Iterate. */
+   if (node->routine->Iterate == NULL)
+       ereport(ERROR,
+               (errmsg("foreign-data wrapper must support Iterate to scan foreign table")));
+   node->routine->Iterate(node->fstate, slot);
+
+   /* Set tableoid if the tuple was valid. */
+   if (HeapTupleIsValid(slot->tts_tuple))
+   {
+       /*
+        * If the foreign-data wrapper returned a MinimalTuple, materialize the
+        * tuple to store system attributes.
+        */
+       if (!TTS_HAS_PHYSICAL_TUPLE(slot))
+           ExecMaterializeSlot(slot);
+
+       /* overwrite only tableoid of the tuple */
+       slot->tts_tuple->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)
+{
+   /* ForeignScan never use keys in ForeignNext. */
+   return true;
+}
+
+/* ----------------------------------------------------------------
+ *     ExecForeignScan(node)
+ *
+ *     Scans the relation sequentially and returns the next qualifying
+ *     tuple.
+ *     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         *routine;
+
+   /*
+    * foreign scan has no child node.
+    * but not any more.
+    */
+   Assert(outerPlan(node) == NULL);
+   Assert(innerPlan(node) == NULL);
+
+   /*
+    * 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);
+
+   /*
+    * 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);
+
+   /*
+    * initialize scan relation. get the relation object id from the
+    * relid'th entry in the range table, open that relation and acquire
+    * appropriate lock on it.
+    */
+   currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
+   scanstate->ss.ss_currentRelation = currentRelation;
+   ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+   scanstate->ss.ps.ps_TupFromTlist = false;
+
+   /*
+    * Initialize result tuple type and projection info.
+    */
+   ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+   ExecAssignScanProjectionInfo(&scanstate->ss);
+
+   /* cache the routine for the table in ForeignScanState */
+   routine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
+   scanstate->routine = routine;
+
+   /*
+    * If this execution was not for EXPLAIN w/o ANALYZE flag, initiate the
+    * foreign scan.
+    */
+   if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+   {
+       ForeignScan    *scan = (ForeignScan *) scanstate->ss.ps.plan;
+       if (routine->BeginScan != NULL)
+           scanstate->fstate = routine->BeginScan(scan->fplan,
+                                                  estate->es_param_list_info);
+   }
+
+   return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *     ExecEndForeignScan
+ *
+ *     frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndForeignScan(ForeignScanState *node)
+{
+   Relation        relation;
+
+   /* close the scan */
+   if (node->routine->EndScan != NULL)
+       node->routine->EndScan(node->fstate);
+
+   /* get information from node */
+   relation = node->ss.ss_currentRelation;
+
+   /* 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(relation);
+}
+
+/* ----------------------------------------------------------------
+ *                     Join Support
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------------------------------------------------------
+ *     ExecForeignReScan
+ *
+ *     Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignReScan(ForeignScanState *node)
+{
+   if (node->routine->ReScan != NULL)
+       node->routine->ReScan(node->fstate);
+
+   ExecScanReScan((ScanState *) node);
+}
+
+/* ----------------------------------------------------------------
+ *     ExecForeignMarkPos(node)
+ *
+ *     Marks scan position.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignMarkPos(ForeignScanState *node)
+{
+   elog(ERROR, "ForeignScan does not support mark/restore");
+}
+
+/* ----------------------------------------------------------------
+ *     ExecForeignRestrPos
+ *
+ *     Restores scan position.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignRestrPos(ForeignScanState *node)
+{
+   elog(ERROR, "ForeignScan does not support mark/restore");
+}
index 7e7ca86709f34f4de4b78f717ac10c51ee62ff5a..8699f9f794ab8f20b48952e081e62118cf1f3a13 100644 (file)
@@ -455,6 +455,75 @@ GetForeignTable(Oid relid)
    return ft;
 }
 
+/*
+ * GetFdwRoutine - look up the handler of the foreign-data wrapper by OID and
+ * retrieve FdwRoutine.
+ */
+FdwRoutine *
+GetFdwRoutine(Oid fdwhandler)
+{
+   FmgrInfo                flinfo;
+   FunctionCallInfoData    fcinfo;
+   Datum                   result;
+   FdwRoutine             *routine;
+
+   if (fdwhandler == InvalidOid)
+       elog(ERROR, "foreign-data wrapper has no handler");
+
+   fmgr_info(fdwhandler, &flinfo);
+   InitFunctionCallInfoData(fcinfo, &flinfo, 0, NULL, NULL);
+   result = FunctionCallInvoke(&fcinfo);
+
+   if (fcinfo.isnull ||
+       (routine = (FdwRoutine *) DatumGetPointer(result)) == NULL)
+   {
+       elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
+       routine = NULL; /* keep compiler quiet */
+   }
+
+   return routine;
+}
+
+/*
+ * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper by
+ * OID of the foreign table and retrieve FdwRoutine.
+ */
+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 function 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);
+
+   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);
+
+   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;
+   ReleaseSysCache(tp);
+
+   return GetFdwRoutine(fdwhandler);
+}
+
 /*
  * Determine the relation is a foreign table.
  */
index 60970443d7e087d95a3bd597bc89080f5279beb1..94f2923dbaa430917b2aa393e9312b1f09a868b3 100644 (file)
@@ -547,6 +547,42 @@ _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_NODE_FIELD(fplan);
+
+   return newnode;
+}
+
+/*
+ * _copyFdwPlan
+ */
+static FdwPlan *
+_copyFdwPlan(FdwPlan *from)
+{
+   FdwPlan *newnode = makeNode(FdwPlan);
+
+   /*
+    * copy node superclass fields
+    */
+   COPY_STRING_FIELD(explainInfo);
+   COPY_SCALAR_FIELD(startup_cost);
+   COPY_SCALAR_FIELD(total_cost);
+   COPY_NODE_FIELD(private);
+
+   return newnode;
+}
+
 /*
  * CopyJoinFields
  *
@@ -3754,6 +3790,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;
index 7d77d84a37008fab1be76b4ee86bc6df28cca4d8..c6c5fd586a3ddf1846df71f5c6c34639cd640352 100644 (file)
@@ -532,6 +532,25 @@ _outWorkTableScan(StringInfo str, WorkTableScan *node)
    WRITE_INT_FIELD(wtParam);
 }
 
+static void
+_outForeignScan(StringInfo str, ForeignScan *node)
+{
+   WRITE_NODE_TYPE("FOREIGNSCAN");
+
+   _outScanInfo(str, (Scan *) node);
+}
+
+static void
+_outFdwPlan(StringInfo str, FdwPlan *node)
+{
+   WRITE_NODE_TYPE("FDWPLAN");
+
+   WRITE_STRING_FIELD(explainInfo);
+    WRITE_FLOAT_FIELD(startup_cost, "%.2f");
+    WRITE_FLOAT_FIELD(total_cost, "%.2f");
+   WRITE_NODE_FIELD(private);
+}
+
 static void
 _outJoin(StringInfo str, Join *node)
 {
@@ -1478,6 +1497,14 @@ _outTidPath(StringInfo str, TidPath *node)
    WRITE_NODE_FIELD(tidquals);
 }
 
+static void
+_outForeignPath(StringInfo str, ForeignPath *node)
+{
+   WRITE_NODE_TYPE("FOREIGNPATH");
+
+   _outPathInfo(str, (Path *) node);
+}
+
 static void
 _outAppendPath(StringInfo str, AppendPath *node)
 {
@@ -2606,6 +2633,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;
@@ -2808,6 +2841,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;
index ce893a77be79134b2de6039a610194496be22cfd..758c1e269e2e922b1cb339139f51051beee2fc74 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "foreign/foreign.h"
 #include "nodes/nodeFuncs.h"
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
@@ -255,14 +256,22 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
     * least one dimension of cost or sortedness.
     */
 
-   /* Consider sequential scan */
-   add_path(rel, create_seqscan_path(root, rel));
+   if (IsForeignTable(rte->relid))
+   {
+       /* only foreign scan path is applyable to foreign table */
+       add_path(rel, create_foreignscan_path(root, rel));
+   }
+   else
+   {
+       /* Consider sequential scan */
+       add_path(rel, create_seqscan_path(root, rel));
 
-   /* Consider index scans */
-   create_index_paths(root, rel);
+       /* Consider index scans */
+       create_index_paths(root, rel);
 
-   /* Consider TID scans */
-   create_tidscan_paths(root, rel);
+       /* Consider TID scans */
+       create_tidscan_paths(root, rel);
+   }
 
    /* Now find the cheapest of the paths for this rel */
    set_cheapest(rel);
@@ -1503,6 +1512,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;
index 1bbf35ed74d193f1e0dd3022480572c9345a0b44..983a2323196bdc9e7b2f84dee0d98f32224683f4 100644 (file)
@@ -20,6 +20,7 @@
 #include <math.h>
 
 #include "access/skey.h"
+#include "catalog/pg_class.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, Path *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, RangeTblEntry *rte,
+                  List *qpqual, Index scanrelid, FdwPlan *fplan);
 static BitmapAnd *make_bitmap_and(List *bitmapplans);
 static BitmapOr *make_bitmap_or(List *bitmapplans);
 static NestLoop *make_nestloop(List *tlist,
@@ -204,6 +209,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:
@@ -344,6 +350,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
                                                      scan_clauses);
            break;
 
+       case T_ForeignScan:
+           plan = (Plan *) create_foreignscan_plan(root,
+                                                     best_path,
+                                                     tlist,
+                                                     scan_clauses);
+           break;
+
        default:
            elog(ERROR, "unrecognized node type: %d",
                 (int) best_path->pathtype);
@@ -466,6 +479,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:
@@ -1747,6 +1761,43 @@ 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, Path *best_path,
+                   List *tlist, List *scan_clauses)
+{
+   ForeignPath    *fpath = (ForeignPath *) best_path;
+   ForeignScan    *scan_plan;
+   Index           scan_relid = best_path->parent->relid;
+   RangeTblEntry  *rte;
+
+   /* it should be a base rel... */
+   Assert(scan_relid > 0);
+   Assert(best_path->parent->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);
+
+   scan_plan = make_foreignscan(tlist,
+                                rte,
+                                scan_clauses,
+                                scan_relid,
+                                fpath->fplan);
+
+   copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+   return scan_plan;
+}
+
 
 /*****************************************************************************
  *
@@ -2962,6 +3013,27 @@ make_worktablescan(List *qptlist,
    return node;
 }
 
+static ForeignScan *
+make_foreignscan(List *qptlist,
+                RangeTblEntry *rte,
+                List *qpqual,
+                Index scanrelid,
+                FdwPlan *fplan)
+{
+   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->fplan = fplan;
+
+   return node;
+}
+
 Append *
 make_append(List *appendplans, List *tlist)
 {
index 0074679207ac89ea4fd5e042f9be5ad74e559816..e760ad9379e46e6e4e4f78db031efdfb3a06207f 100644 (file)
@@ -400,6 +400,17 @@ 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:
index 39ef420284d3cdb7b14a3db807113767eb66b2a7..9fe5cccef026b409aabe79183140fdf3f863fffb 100644 (file)
@@ -2044,6 +2044,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;
index 4686578e3bd3cccda4fe48fcced4c674871f0886..d154c30c4e356e95ec9d38c2e7b5056a0df5b8e9 100644 (file)
@@ -1237,6 +1237,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
            continue;
        }
 
+       /*
+        * SELECT FOR UPDATE/SHARE is not allowd to foreign tables because
+        * they are read-only.
+        */
+       if (newrelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE &&
+           lockmode != AccessShareLock)
+           ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                           errmsg("SELECT FOR UPDATE/SHARE is not allowed with foreign tables")));
+
        /*
         * Build an RTE for the child, and attach to query's rangetable list.
         * We copy most fields of the parent's RTE, but replace relation OID,
index 2439d814ce879b935be1c4a04676ea97908e469c..ced28f0d1a51d0b305b94a1bee8968f2279dde0f 100644 (file)
@@ -17,6 +17,7 @@
 #include <math.h>
 
 #include "catalog/pg_operator.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -1419,6 +1420,33 @@ 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.
+ */
+Path *
+create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
+{
+   RangeTblEntry  *rte;
+   FdwRoutine     *routine;
+   ForeignPath    *pathnode = makeNode(ForeignPath);
+
+   pathnode->path.pathtype = T_ForeignScan;
+   pathnode->path.parent = rel;
+   pathnode->path.pathkeys = NIL;  /* result is always unordered */
+   
+   rte = planner_rt_fetch(rel->relid, root);
+   routine = GetFdwRoutineByRelId(rte->relid);
+   pathnode->fplan = routine->PlanRelScan(rte->relid, root, rel);
+
+   /* use costs estimated by FDW */
+   pathnode->path.startup_cost = pathnode->fplan->startup_cost;
+   pathnode->path.total_cost = pathnode->fplan->total_cost;
+
+   return (Path *) pathnode;
+}
+
 /*
  * create_nestloop_path
  *   Creates a pathnode corresponding to a nestloop join between two
index 2ab272552be03ec16bfdde5a484a2e700e46d129..5e73d107deb30f42fe4da2c0dc03857a3e45ce05 100644 (file)
@@ -454,6 +454,11 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
            *pages = 1;
            *tuples = 1;
            break;
+       case RELKIND_FOREIGN_TABLE:
+           /* foreign tables has no storage, trust statistics  */
+           *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;
@@ -753,7 +758,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
  *
  * We also support building a "physical" tlist for subqueries, functions,
  * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * SubqueryScan, FunctionScan, ValuesScan, CteScan, WorkTableScan and
+ * ForeignScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
new file mode 100644 (file)
index 0000000..7967c5e
--- /dev/null
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeForeignscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEFOREIGNSCAN_H
+#define NODEFOREIGNSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern ForeignScanState *ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecForeignScan(ForeignScanState *node);
+extern void ExecEndForeignScan(ForeignScanState *node);
+extern void ExecForeignMarkPos(ForeignScanState *node);
+extern void ExecForeignRestrPos(ForeignScanState *node);
+extern void ExecForeignReScan(ForeignScanState *node);
+
+#endif   /* NODEFOREIGNSCAN_H */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644 (file)
index 0000000..2de0541
--- /dev/null
@@ -0,0 +1,147 @@
+/*-------------------------------------------------------------------------
+ *
+ * fdwapi.h
+ *   API for foreign-data wrappers
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ *
+ * src/include/foreign/fdwapi.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FDWAPI_H
+#define FDWAPI_H
+
+#include "executor/tuptable.h"
+#include "nodes/pg_list.h"
+#include "nodes/relation.h"
+
+/*
+ * When a plan is going to be cached, the plan node is copied into another
+ * context with copyObject. It means that FdwPlan, a part of ForeignScan plan
+ * node, and its contents must have copyObject support too.
+ */
+struct FdwPlan
+{
+   NodeTag type;
+
+   /*
+    * Free-form text shown in EXPLAIN. The SQL to be sent to the remote
+    * server is typically shown here.
+    */
+   char *explainInfo;
+
+   /*
+    * Cost estimation info. The startup_cost should include the cost of
+    * connecting to the remote host and sending over the query, as well as
+    * the cost of starting up the query so that it returns the first result
+    * row.
+    */
+   double startup_cost;
+   double total_cost;
+#ifdef HOOK_ESTIMATE_REL_SIZE
+   double rows;
+   int width;
+#endif
+
+   /*
+    * FDW-private data. FDW must guarantee that every elements in this list
+    * have copyObject support.  If FDW needs to store arbitrary data such as
+    * non-Node structure, Const of bytea would be able to use as a container.
+    */
+   List *private;
+};
+typedef struct FdwPlan FdwPlan;
+
+struct FdwExecutionState
+{
+   /* FDW-private data */
+   void *private;
+};
+typedef struct FdwExecutionState FdwExecutionState;
+
+/*
+ * Common interface routines of FDW, inspired by the FDW API in the SQL/MED
+ * standard, but adapted to the PostgreSQL world.
+ *
+ * A foreign-data wrapper implements these routines. At a minimum, it must
+ * implement BeginScan, Iterate and EndScan, and either PlanNative or
+ * PlanRelScan.
+ *
+ * The PlanXXX functions return an FdwPlan struct that can later be executed
+ * with BeginScan. The implementation should fill in the cost estimates in
+ * FdwPlan, as well as a tuple descriptor that describes the result set.
+ */
+struct FdwRoutine
+{
+#ifdef IN_THE_FUTURE
+   /*
+    * Plan a query of arbitrary native SQL (or other query language supported
+    * by the foreign server). This is used for SQL/MED passthrough mode, or
+    * e.g contrib/dblink.
+    */
+   FdwPlan *(*PlanNative)(Oid serverid, char *query);
+
+   /*
+    * Plan a whole subquery. This is used for example to execute an aggregate
+    * query remotely without pulling all the rows to the local server.
+    *
+    * The implementation can return NULL if it cannot satisfy the whole
+    * subquery, in which case the planner will break down the query into
+    * smaller parts and call PlanRelScan for the foreign tables involved.
+    *
+    * The implementation must be careful to only accept queries it fully
+    * understands! For example, if it ignores windowClauses, and returns
+    * a non-NULL results for a query that contains one, the windowClause
+    * would be lost and the query would return incorrect results.
+    */
+   FdwPlan *(*PlanQuery)(PlannerInfo *root, Query query);
+#endif
+
+   /*
+    * Plan a scan on a foreign table. 'foreigntableid' identifies the foreign
+    * table, and 'attnos' is an integer list of attribute numbers for the
+    * columns to be returned. Note that 'attnos' can also be an empty list,
+    * typically for "SELECT COUNT(*) FROM foreigntable" style queries where
+    * we just need to know how many rows there are. The number and type of
+    * attributes in the tuple descriptor in the returned FdwPlan must match
+    * the attributes specified in attnos, or an error will be thrown.
+    *
+    * 'root' and 'baserel' contain context information that the
+    * implementation can use to restrict the rows that are fetched.
+    * baserel->baserestrictinfo is particularly interseting, as it contains
+    * quals (WHERE clauses) that can be used to filter the rows in the remote
+    * server. 'root' and 'baserel' can be safely ignored, the planner will
+    * re-check the quals on every fetched row anyway.
+    */
+   FdwPlan *(*PlanRelScan)(Oid foreigntableid, PlannerInfo *root,
+                           RelOptInfo *baserel);
+
+   /*
+    * Begin execution of a foreign scan.  This function is called when an
+    * actual scan is needed, so EXPLAIN without ANALYZE option doesn't call
+    * BeginScan(). 
+    */
+   FdwExecutionState *(*BeginScan)(FdwPlan *plan, ParamListInfo params);
+
+   /*
+    * Fetch the next record and store it into slot.
+    */
+   void (*Iterate)(FdwExecutionState *state, TupleTableSlot *slot);
+
+   /*
+    * Reset the read pointer to the head of the scan.
+    * This function will be called when the new outer tuple was acquired in a
+    * nested loop.
+    */
+   void (*ReScan)(FdwExecutionState *state);
+
+   /*
+    * End the foreign scan and do clean up.
+    */
+   void (*EndScan)(FdwExecutionState *state);
+};
+typedef struct FdwRoutine FdwRoutine;
+
+#endif   /* FDWAPI_H */
+
index 2326fad110dc238a91b20f817e198e232da11079..f3c8dbbcf11aea53a89df13f0485adc6ac458ed2 100644 (file)
@@ -14,6 +14,7 @@
 #define FOREIGN_H
 
 #include "executor/tuptable.h"
+#include "foreign/fdwapi.h"
 #include "utils/relcache.h"
 
 
@@ -79,6 +80,8 @@ extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
                            bool missing_ok);
 extern Oid GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
+extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
+extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
 extern bool IsForeignTable(Oid relid);
 extern Oid GetFdwValidator(Oid relid);
 extern List *GetGenericOptionsPerColumn(Oid relid, int2 attnum);
index d669c24b981f11b1ad480c7c56cb22f33e613fac..971f6e8ccd9bea8920f5757b78ba411bcab26d52 100644 (file)
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/skey.h"
+#include "foreign/fdwapi.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "nodes/relation.h"
 #include "nodes/tidbitmap.h"
 #include "utils/hsearch.h"
 #include "utils/rel.h"
@@ -1386,6 +1388,20 @@ typedef struct WorkTableScanState
    RecursiveUnionState *rustate;
 } WorkTableScanState;
 
+/* ----------------
+ *  ForeignScanState information
+ *
+ *     ForeignScan nodes are used to scan the foreign table managed by
+ *     a foreign server.
+ * ----------------
+ */
+typedef struct ForeignScanState
+{
+   ScanState       ss;         /* its first field is NodeTag */
+   FdwRoutine     *routine;
+   FdwExecutionState *fstate;  /* private data for each data wrapper */
+} ForeignScanState;
+
 /* ----------------------------------------------------------------
  *              Join State Information
  * ----------------------------------------------------------------
index 1bb5a11994dcf9fe0f063382daa475ece7a12e5e..9bf9b21e214eed39be87c8e93ae38d5dfc4bf683 100644 (file)
@@ -60,6 +60,8 @@ typedef enum NodeTag
    T_ValuesScan,
    T_CteScan,
    T_WorkTableScan,
+   T_ForeignScan,
+   T_FdwPlan,
    T_Join,
    T_NestLoop,
    T_MergeJoin,
@@ -103,6 +105,7 @@ typedef enum NodeTag
    T_ValuesScanState,
    T_CteScanState,
    T_WorkTableScanState,
+   T_ForeignScanState,
    T_JoinState,
    T_NestLoopState,
    T_MergeJoinState,
@@ -216,6 +219,7 @@ typedef enum NodeTag
    T_MergePath,
    T_HashPath,
    T_TidPath,
+   T_ForeignPath,
    T_AppendPath,
    T_MergeAppendPath,
    T_ResultPath,
index b89eb55ad767ae138d3cb2ba8984221ae6e00586..89e8dfcd8322da9da380ebc64a485740f6b85559 100644 (file)
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "foreign/fdwapi.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "storage/itemptr.h"
@@ -431,6 +432,16 @@ typedef struct WorkTableScan
    int         wtParam;        /* ID of Param representing work table */
 } WorkTableScan;
 
+/* ----------------
+ *     ForeignScan node
+ * ----------------
+ */
+typedef struct ForeignScan
+{
+   Scan            scan;
+   FdwPlan        *fplan;
+} ForeignScan;
+
 
 /*
  * ==========
index e7ebcfcc81ab64053f630c08604127d136007ffb..e6fbd333c515915d452b3b28def38da3d0754382 100644 (file)
@@ -744,6 +744,15 @@ typedef struct TidPath
    List       *tidquals;       /* qual(s) involving CTID = something */
 } TidPath;
 
+/*
+ * ForeignPath represents a scan on a foreign table
+ */
+typedef struct ForeignPath
+{
+   Path        path;
+   struct FdwPlan *fplan;
+} ForeignPath;
+
 /*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
index 2dde5e07ef548d425266546db20a01ab8309243a..11b57e9fab4ac8090fa728bfe5c7b32fbe3b3bd0 100644 (file)
@@ -61,6 +61,7 @@ extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
+extern Path *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel);
 
 extern NestPath *create_nestloop_path(PlannerInfo *root,
                     RelOptInfo *joinrel,
index 8bc55ea23c547cebe51321dda45e5c64830b630e..e15f58d5451e489e6f6962bc513d94693c53b391 100644 (file)
@@ -711,6 +711,10 @@ Has OIDs: no
 
 CREATE INDEX id_ft1_c2 ON ft1 (c2);                             -- ERROR
 ERROR:  "ft1" is not a table
+SELECT * FROM ft1;                                              -- ERROR
+ERROR:  foreign-data wrapper has no handler
+EXPLAIN (VERBOSE) SELECT * FROM ft1;                            -- ERROR
+ERROR:  foreign-data wrapper has no handler
 -- ALTER FOREIGN TABLE
 COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
 COMMENT ON FOREIGN TABLE ft1 IS NULL;
index 5cbafa5e2ddbff248a7a4e063750af2bd0bc3cc0..a7c503e0c8dc2b856deb3633fe8fbbf60309867f 100644 (file)
@@ -285,6 +285,8 @@ CREATE FOREIGN TABLE ft2 () INHERITS (t1) SERVER sc OPTIONS (delimiter ' ', quot
 \d+ ft2
 \det+
 CREATE INDEX id_ft1_c2 ON ft1 (c2);                             -- ERROR
+SELECT * FROM ft1;                                              -- ERROR
+EXPLAIN (VERBOSE) SELECT * FROM ft1;                            -- ERROR
 
 -- ALTER FOREIGN TABLE
 COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';