fdw-api-2.patch
text/x-diff
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| doc/src/sgml/catalogs.sgml | 11 | 0 |
| doc/src/sgml/ddl.sgml | 35 | 0 |
| doc/src/sgml/fdwhandler.sgml | 125 | 0 |
| doc/src/sgml/filelist.sgml | 1 | 0 |
| doc/src/sgml/postgres.sgml | 1 | 0 |
| doc/src/sgml/ref/alter_foreign_data_wrapper.sgml | 26 | 2 |
| doc/src/sgml/ref/create_foreign_data_wrapper.sgml | 16 | 2 |
| src/backend/commands/explain.c | 14 | 0 |
| src/backend/commands/foreigncmds.c | 118 | 9 |
| src/backend/executor/execAmi.c | 5 | 0 |
| src/backend/executor/execProcnode.c | 14 | 0 |
| src/backend/executor/Makefile | 1 | 1 |
| src/backend/executor/nodeForeignscan.c | 286 | 0 |
| src/backend/executor/nodeLockRows.c | 6 | 0 |
| src/backend/foreign/foreign.c | 112 | 0 |
| src/backend/nodes/copyfuncs.c | 46 | 3 |
| src/backend/nodes/equalfuncs.c | 2 | 3 |
| src/backend/nodes/outfuncs.c | 38 | 0 |
| src/backend/optimizer/path/allpaths.c | 19 | 6 |
| src/backend/optimizer/plan/createplan.c | 71 | 0 |
| src/backend/optimizer/plan/planner.c | 1 | 1 |
| src/backend/optimizer/plan/setrefs.c | 11 | 0 |
| src/backend/optimizer/plan/subselect.c | 4 | 0 |
| src/backend/optimizer/util/pathnode.c | 45 | 0 |
| src/backend/optimizer/util/plancat.c | 5 | 6 |
| src/backend/parser/analyze.c | 13 | 3 |
| src/backend/parser/gram.y | 27 | 15 |
| src/backend/rewrite/rewriteHandler.c | 6 | 2 |
| src/backend/utils/adt/pseudotypes.c | 26 | 0 |
| src/bin/pg_dump/pg_dump.c | 33 | 7 |
| src/bin/pg_dump/pg_dump.h | 1 | 0 |
| src/bin/psql/describe.c | 2 | 0 |
| src/include/catalog/pg_foreign_data_wrapper.h | 5 | 3 |
| src/include/catalog/pg_proc.h | 4 | 0 |
| src/include/catalog/pg_type.h | 2 | 0 |
| src/include/executor/nodeForeignscan.h | 26 | 0 |
| src/include/foreign/fdwapi.h | 159 | 0 |
| src/include/foreign/foreign.h | 11 | 0 |
| src/include/nodes/execnodes.h | 16 | 0 |
| src/include/nodes/nodes.h | 4 | 0 |
| src/include/nodes/parsenodes.h | 2 | 3 |
| src/include/nodes/plannodes.h | 11 | 0 |
| src/include/nodes/relation.h | 9 | 0 |
| src/include/optimizer/pathnode.h | 1 | 0 |
| src/include/utils/builtins.h | 2 | 0 |
| src/test/regress/expected/foreign_data.out | 94 | 90 |
| src/test/regress/sql/foreign_data.sql | 4 | 2 |
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 30f3980..1ea2ba4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3045,6 +3045,17 @@
</row>
<row>
+ <entry><structfield>fdwhandler</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+ <entry>
+ References a handler function that is responsible for
+ supplying foreign-data wrapper routines.
+ Zero if no handler is provided.
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>fdwacl</structfield></entry>
<entry><type>aclitem[]</type></entry>
<entry></entry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a65b4bc..06a82a4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2986,6 +2986,41 @@ ANALYZE measurement;
</sect2>
</sect1>
+ <sect1 id="ddl-foreign-data">
+ <title>Foreign Data</title>
+ <indexterm>
+ <primary>foreign data</primary>
+ </indexterm>
+ <para>
+ <productname>PostgreSQL</productname> implements parts of the SQL/MED
+ specification, which allows you to access external data that resides
+ outside PostgreSQL, using normal SQL queries.
+ </para>
+
+ <para>
+ Foreign data is accessed with help from a
+ <firstterm>foreign data wrapper</firstterm>. A foreign data wrapper is a
+ library that can communicate with an external data source, hiding the
+ details of connecting to the data source and fetching data from it. There
+ are several foreign data wrappers available for e.g reading files residing
+ on the server, or to connect to another PostgreSQL instance. If none of
+ the existing foreign data wrappers suite your needs, you can write your
+ own, see <xref linkend="fdwhandler">.
+ </para>
+
+ <para>
+ To access foreign data, you need to create a foreign server to define
+ the connection details to the external data source, using options required
+ by the foreign data wrapper. Then you need to create one or more
+ <firstterm>foreign tables</firstterm> that define the structure of the
+ remote data. A foreign table can be used in queries just like a normal
+ table, but a foreign table has no storage in the PostgreSQL server.
+ Whenever it is used, PostgreSQL asks the foreign data wrapper to fetch
+ the data from the external source.
+ </para>
+
+ </sect1>
+
<sect1 id="ddl-others">
<title>Other Database Objects</title>
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
new file mode 100644
index 0000000..1aca29e
--- /dev/null
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -0,0 +1,125 @@
+<!-- doc/src/sgml/fdwhandler.sgml -->
+
+ <chapter id="fdwhandler">
+ <title>Writing A Foreign Data Wrapper</title>
+
+ <indexterm zone="fdwhandler">
+ <primary>foreign data wrapper</primary>
+ <secondary>handler for</secondary>
+ </indexterm>
+
+ <para>
+ All operations on foreign tables are handled through a foreign data
+ wrapper, which consists of a set of functions that the planner and
+ executor call. The foreign data wrapper is responsible for fetching
+ data from the remote data source and returning it to the PostgreSQL
+ executor. This chapter outlines how to write a new foreign data wrapper.
+ </para>
+
+ <para>
+ The FDW provider needs to implement a handler function, and (optionally)
+ a validator function. Both functions must be written in a compiled
+ language such as C, using the version-1 interface.
+ The validator function must be registered with
+ <productname>PostgreSQL</productname> as taking two arguments, a text
+ array containing the options, and an oid representing the type of object
+ the options are to be associated with. The handler function must be
+ registered as taking no arguments, and returning the type
+ <type>fdw_handler</type>. This special pseudotype identifies the
+ function as a fdw handler and prevents it from being called directly in
+ SQL commands. For more details on C language calling conventions and
+ dynamic loading, see <xref linkend="xfunc-c">.
+ </para>
+
+ <para>
+ The validator function is responsible for validating
+ options given in the CREATE FOREIGN DATA WRAPPER, CREATE SERVER and
+ CREATE FOREIGN TABLE commands.
+ </para>
+
+ <para>
+ The handler function returns a struct of function pointers to callback
+ functions that are called by the planner and executor.
+ </para>
+
+ <para>
+ The SQL standard specifies an interface for writing foreign data wrappers.
+ However, PostgreSQL does not implement that API, because the effort to
+ accommodate it into PostgreSQL would be large. The standard API hasn't
+ gained wide adoption anyway, there aren't many standard-compliant foreign
+ data wrappers out there.
+ </para>
+
+ <para>
+ The foreign data wrappers included in the standard distribution
+ are good references when trying to write your own.
+ Look into the <filename>contrib/file_fdw</> subdirectory of the source tree.
+ The <xref linkend="sql-createforeigndatawrapper">
+ reference page also has some useful details.
+ </para>
+
+ <sect1 id="fdw-routines">
+ <title>Foreign Data Wrapper routines</title>
+
+ <para>
+ The FDW handler function returns a FdwRoutine struct with the following
+ callback functions:
+ </para>
+
+ <para>
+<programlisting>
+FdwPlan *
+PlanRelScan (Oid foreigntableid,
+ PlannerInfo *root,
+ RelOptInfo *baserel);
+</programlisting>
+ Plan a scan on a foreign table. This is called when a query is planned.
+ The function must return a palloc'd struct containing cost estimates,
+ a string to show for this scan in EXPLAIN, and any FDW-private information
+ that is needed to execute the foreign scan at a later stage.
+ A prepared plan may be executed many times.
+ </para>
+
+ <para>
+<programlisting>
+FdwExecutionState *
+BeginScan (FdwPlan *plan
+ ParamListInfo *params);
+</programlisting>
+ Begin exeucuting a foreign scan. This is called when a query is executed.
+ The function must return a palloc'd struct containing any private
+ information needed by the Iterate and EndScan calls that follow.
+ </para>
+
+ <para>
+<programlisting>
+void
+Iterate (FdwExecutionState *state,
+ TupleTableSlot *slot);
+</programlisting>
+ Fetch one row from the foreign source. Note that this is called in a
+ short-lived memory context that may be reset between every invocation of
+ Iterate. Create a memory context in BeginScan if you need longer-lived
+ storage.
+ </para>
+
+ <para>
+<programlisting>
+void
+ReScan (FdwExecutionState *state);
+</programlisting>
+ Restarts the scan. Optional, the backend will call BeginScan+EndScan
+ if ReScan function is not implemented.
+ </para>
+
+ <para>
+<programlisting>
+void
+EndScan (FdwExecutionState *state);
+</programlisting>
+ Ends the scan.
+ </para>
+
+ </sect1>
+
+ </chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index b9d4ea5..659bcba 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -86,6 +86,7 @@
<!entity indexam SYSTEM "indexam.sgml">
<!entity nls SYSTEM "nls.sgml">
<!entity plhandler SYSTEM "plhandler.sgml">
+<!entity fdwhandler SYSTEM "fdwhandler.sgml">
<!entity protocol SYSTEM "protocol.sgml">
<!entity sources SYSTEM "sources.sgml">
<!entity storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 4d32f7d..98d19a5 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -238,6 +238,7 @@
&sources;
&nls;
&plhandler;
+ &fdwhandler;
&geqo;
&indexam;
&gist;
diff --git a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
index 4e9e8a2..7e17ef3 100644
--- a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
+++ b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
[ VALIDATOR <replaceable class="parameter">valfunction</replaceable> | NO VALIDATOR ]
+ [ HANDLER <replaceable class="parameter">handler</replaceable> | NO HANDLER ]
[ OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ]) ]
ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
</synopsis>
@@ -86,6 +87,29 @@ ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable> OWN
</varlistentry>
<varlistentry>
+ <term><literal>HANDLER <replaceable class="parameter">handler</replaceable></literal></term>
+ <listitem>
+ <para>
+ Specifies a new foreign-data wrapper handler function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO HANDLER</literal></term>
+ <listitem>
+ <para>
+ This is used to specify that the foreign-data wrapper should no
+ longer have a handler function.
+ </para>
+ <para>
+ Note that foreign tables which uses a foreign-data wrapper with no
+ handler can't be used in a SELECT statement.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
@@ -127,8 +151,8 @@ ALTER FOREIGN DATA WRAPPER dbi VALIDATOR bob.myvalidator;
<para>
<command>ALTER FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
9075-9 (SQL/MED). The standard does not specify the <literal>
- VALIDATOR</literal> and <literal>OWNER TO</> variants of the
- command.
+ VALIDATOR</literal>, <literal>HANDLER</> and <literal>OWNER TO</>
+ variants of the command.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
index f626d56..d70321b 100644
--- a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
+++ b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
[ VALIDATOR <replaceable class="parameter">valfunction</replaceable> | NO VALIDATOR ]
+ [ HANDLER <replaceable class="parameter">handler</replaceable> | NO HANDLER ]
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -82,6 +83,19 @@ CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>HANDLER <replaceable class="parameter">handler</replaceable></literal></term>
+ <listitem>
+ <para>
+ <replaceable class="parameter">handler</replaceable> is the
+ name of a previously registered function that will be called to
+ retrieve a set of functions for foreign tables.
+ The handler function must take no arguments.
+ The return type must be <type>fdw_handler</type>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] )</literal></term>
<listitem>
<para>
@@ -151,8 +165,8 @@ CREATE FOREIGN DATA WRAPPER mywrapper
<para>
<command>CREATE FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
- 9075-9 (SQL/MED), with the exception that
- the <literal>VALIDATOR</literal> clause is an extension and the
+ 9075-9 (SQL/MED), with the exception that the <literal>VALIDATOR</literal>
+ and <literal>HANDLER</literal> clauses are extensions and the
clauses <literal>LIBRARY</literal> and <literal>LANGUAGE</literal>
are not yet implemented in PostgreSQL.
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 6534590..553f544 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -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 (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);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index a2b5358..18fe32e 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -317,16 +317,74 @@ AlterForeignServerOwner(const char *name, Oid newOwnerId)
* Convert a validator function name passed from the parser to an Oid.
*/
static Oid
-lookup_fdw_validator_func(List *validator)
+lookup_fdw_validator_func(DefElem *validator)
{
Oid funcargtypes[2];
+ if (validator == NULL || validator->arg == NULL)
+ return InvalidOid;
+
funcargtypes[0] = TEXTARRAYOID;
funcargtypes[1] = OIDOID;
- return LookupFuncName(validator, 2, funcargtypes, false);
+ return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
/* return value is ignored, so we don't check the type */
}
+static Oid
+lookup_fdw_handler_func(DefElem *handler)
+{
+ Oid handlerOid;
+
+ if (handler == NULL || handler->arg == NULL)
+ return InvalidOid;
+
+ /* check that handler have correct return type */
+ handlerOid = LookupFuncName((List *) handler->arg, 0, NULL, false);
+ if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s must return type \"fdw_handler\"",
+ NameListToString((List *) handler->arg))));
+ }
+
+ return handlerOid;
+}
+
+static void
+parse_func_options(List *func_options, DefElem **validator, DefElem **handler)
+{
+ ListCell *cell;
+
+ *validator = NULL;
+ *handler = NULL;
+ foreach(cell, func_options)
+ {
+ DefElem *def = lfirst(cell);
+
+ if (strcmp(def->defname, "validator") == 0)
+ {
+ if (*validator)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ *validator = def;
+ }
+ else if (strcmp(def->defname, "handler") == 0)
+ {
+ if (*handler)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ *handler = def;
+ }
+ else
+ {
+ elog(ERROR, "option \"%s\" not recognized",
+ def->defname);
+ }
+ }
+}
/*
* Create a foreign-data wrapper
@@ -339,7 +397,10 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
bool nulls[Natts_pg_foreign_data_wrapper];
HeapTuple tuple;
Oid fdwId;
+ DefElem *defvalidator;
+ DefElem *defhandler;
Oid fdwvalidator;
+ Oid fdwhandler;
Datum fdwoptions;
Oid ownerId;
ObjectAddress myself;
@@ -377,12 +438,13 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
- if (stmt->validator)
- fdwvalidator = lookup_fdw_validator_func(stmt->validator);
- else
- fdwvalidator = InvalidOid;
+ /* Lookup validator and handler functions, if given */
+ parse_func_options(stmt->func_options, &defvalidator, &defhandler);
+ fdwvalidator = lookup_fdw_validator_func(defvalidator);
+ fdwhandler = lookup_fdw_handler_func(defhandler);
values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator;
+ values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = fdwhandler;
nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
@@ -416,6 +478,21 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ if (fdwhandler != InvalidOid)
+ {
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ myself.classId = ForeignDataWrapperRelationId;
+ myself.objectId = fdwId;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fdwhandler;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
/* dependency on extension */
@@ -443,7 +520,10 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
Oid fdwId;
bool isnull;
Datum datum;
+ DefElem *defvalidator;
+ DefElem *defhandler;
Oid fdwvalidator;
+ Oid fdwhandler;
/* Must be super user */
if (!superuser())
@@ -467,9 +547,11 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
- if (stmt->change_validator)
+ parse_func_options(stmt->func_options, &defvalidator, &defhandler);
+
+ if (defvalidator)
{
- fdwvalidator = stmt->validator ? lookup_fdw_validator_func(stmt->validator) : InvalidOid;
+ fdwvalidator = lookup_fdw_validator_func(defvalidator);
repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
@@ -477,7 +559,7 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
* It could be that the options for the FDW, SERVER and USER MAPPING
* are no longer valid with the new validator. Warn about this.
*/
- if (stmt->validator)
+ if (defvalidator->arg)
ereport(WARNING,
(errmsg("changing the foreign-data wrapper validator can cause "
"the options for dependent objects to become invalid")));
@@ -495,6 +577,33 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
fdwvalidator = DatumGetObjectId(datum);
}
+ if (defhandler)
+ {
+ fdwhandler = lookup_fdw_handler_func(defhandler);
+ repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
+ repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;
+
+ /*
+ * It could be that the behavior of accessing foreign table changes
+ * with the new handler. Warn about this.
+ */
+ if (defhandler->arg)
+ ereport(WARNING,
+ (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
+ }
+ else
+ {
+ /*
+ * Validator is not changed, but we need it for validating options.
+ */
+ datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+ tp,
+ Anum_pg_foreign_data_wrapper_fdwhandler,
+ &isnull);
+ Assert(!isnull);
+ fdwhandler = DatumGetObjectId(datum);
+ }
+
/*
* Options specified, validate and update.
*/
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index da4fbb4..a854c9a 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -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
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 6bdc60c..9f7b138 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -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:
+ ExecForeignReScan((ForeignScanState *) node);
+ break;
+
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node);
break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index e8ebec1..f275b41 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -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
*/
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644
index 0000000..d512b49
--- /dev/null
+++ b/src/backend/executor/nodeForeignscan.c
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.c
+ * Support routines for 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 a foreignscan node.
+ * ExecEndForeignScan releases any resources allocated.
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeForeignscan.h"
+#include "foreign/foreign.h"
+#include "miscadmin.h"
+#include "utils/memutils.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;
+ ExprContext *econtext;
+ MemoryContext oldcontext;
+
+ 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")));
+
+ /* Slot has to be cleared explicitly before resetting per-tuple context. */
+ ExecClearTuple(slot);
+
+ /* We call Iterate in per-tuple context, similar to FunctionScan */
+ econtext = node->ss.ps.ps_ExprContext;
+ ResetExprContext(econtext);
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ node->routine->Iterate(node->fstate, slot);
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Set tableoid if the tuple was valid. */
+ if (!TupIsNull(slot))
+ {
+ /*
+ * 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)
+ *
+ * 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 *routine;
+
+ /*
+ * foreign scan has no child node.
+ */
+ 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)
+ ereport(ERROR,
+ (errmsg("foreign-data wrapper must support BeginScan to scan foreign table")));
+ 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 it has been opened */
+ if (node->fstate != NULL)
+ {
+ if (node->routine->EndScan == NULL)
+ ereport(ERROR,
+ (errmsg("foreign-data wrapper must support BeginScan to scan foreign table")));
+ 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)
+{
+ /* Use EndScan and BeginScan if the FDW doesn't supply ReScan */
+ if (node->routine->ReScan == NULL)
+ {
+ ForeignScan *scan = (ForeignScan *) node->ss.ps.plan;
+ EState *estate = node->ss.ps.state;
+
+ Assert(node->routine->BeginScan);
+
+ node->routine->EndScan(node->fstate);
+ node->fstate = node->routine->BeginScan(scan->fplan,
+ estate->es_param_list_info);
+ }
+ else
+ 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");
+}
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 2e08008..e7ce86c 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,6 +79,12 @@ lnext:
if (node->lr_epqstate.estate != NULL)
EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL);
+ /* if foreign table, the tuple can't be locked */
+ if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ (errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign tables"))));
+
/* if child rel, must check whether it produced this row */
if (erm->rti != erm->prti)
{
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 9a0f847..8089fc6 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -16,6 +16,7 @@
#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/foreign.h"
@@ -59,6 +60,7 @@ GetForeignDataWrapper(Oid fdwid)
fdw->owner = fdwform->fdwowner;
fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
fdw->fdwvalidator = fdwform->fdwvalidator;
+ fdw->fdwhandler = fdwform->fdwhandler;
/* Extract the options */
datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
@@ -250,6 +252,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 = SearchSysCache(FOREIGNTABLEREL,
+ ObjectIdGetDatum(relid),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+
+ ft = palloc(sizeof(ForeignTable));
+ ft->relid = relid;
+ ft->serverid = tableform->ftserver;
+
+ /* Extract the ftoptions */
+ datum = SysCacheGetAttr(FOREIGNTABLEREL,
+ tp,
+ Anum_pg_foreign_table_ftoptions,
+ &isnull);
+
+ /* untransformRelOptions does exactly what we want - avoid duplication */
+ ft->options = untransformRelOptions(datum);
+ ReleaseSysCache(tp);
+
+ 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);
+}
+
+
+/*
* deflist_to_tuplestore - Helper function to convert DefElem list to
* tuplestore usable in SRF.
*/
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 57d5802..ab5effe 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -550,6 +550,44 @@ _copyWorkTableScan(WorkTableScan *from)
}
/*
+ * _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_SCALAR_FIELD(serverid);
+ COPY_SCALAR_FIELD(userid);
+ COPY_STRING_FIELD(explainInfo);
+ COPY_SCALAR_FIELD(startup_cost);
+ COPY_SCALAR_FIELD(total_cost);
+ COPY_NODE_FIELD(fdw_private);
+
+ return newnode;
+}
+
+/*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
@@ -3282,7 +3320,7 @@ _copyCreateFdwStmt(CreateFdwStmt *from)
CreateFdwStmt *newnode = makeNode(CreateFdwStmt);
COPY_STRING_FIELD(fdwname);
- COPY_NODE_FIELD(validator);
+ COPY_NODE_FIELD(func_options);
COPY_NODE_FIELD(options);
return newnode;
@@ -3294,8 +3332,7 @@ _copyAlterFdwStmt(AlterFdwStmt *from)
AlterFdwStmt *newnode = makeNode(AlterFdwStmt);
COPY_STRING_FIELD(fdwname);
- COPY_NODE_FIELD(validator);
- COPY_SCALAR_FIELD(change_validator);
+ COPY_NODE_FIELD(func_options);
COPY_NODE_FIELD(options);
return newnode;
@@ -3840,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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f57cf99..dd332f1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1679,7 +1679,7 @@ static bool
_equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
- COMPARE_NODE_FIELD(validator);
+ COMPARE_NODE_FIELD(func_options);
COMPARE_NODE_FIELD(options);
return true;
@@ -1689,8 +1689,7 @@ static bool
_equalAlterFdwStmt(AlterFdwStmt *a, AlterFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
- COMPARE_NODE_FIELD(validator);
- COMPARE_SCALAR_FIELD(change_validator);
+ COMPARE_NODE_FIELD(func_options);
COMPARE_NODE_FIELD(options);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5c943bc..2e798ed 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -538,6 +538,27 @@ _outWorkTableScan(StringInfo str, WorkTableScan *node)
}
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_OID_FIELD(serverid);
+ WRITE_OID_FIELD(userid);
+ WRITE_STRING_FIELD(explainInfo);
+ WRITE_FLOAT_FIELD(startup_cost, "%.2f");
+ WRITE_FLOAT_FIELD(total_cost, "%.2f");
+ WRITE_NODE_FIELD(fdw_private);
+}
+
+static void
_outJoin(StringInfo str, Join *node)
{
WRITE_NODE_TYPE("JOIN");
@@ -1508,6 +1529,14 @@ _outTidPath(StringInfo str, TidPath *node)
}
static void
+_outForeignPath(StringInfo str, ForeignPath *node)
+{
+ WRITE_NODE_TYPE("FOREIGNPATH");
+
+ _outPathInfo(str, (Path *) node);
+}
+
+static void
_outAppendPath(StringInfo str, AppendPath *node)
{
WRITE_NODE_TYPE("APPENDPATH");
@@ -2670,6 +2699,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;
@@ -2875,6 +2910,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;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37d8332..934c430 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -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 */
@@ -255,14 +257,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 (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
+ {
+ /* Foreign scan is the only possible path for a 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 +1513,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;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f01114c..9906bad 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -71,6 +71,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 +114,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,
@@ -206,6 +210,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 +351,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);
@@ -468,6 +480,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 +1768,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;
+}
+
/*****************************************************************************
*
@@ -2978,6 +3028,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)
{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 01b9038..efc670f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1914,7 +1914,7 @@ 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;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d004f6c..5830b0b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -402,6 +402,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:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 29eb9dc..96a257f 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -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;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d1010af..45752b4 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -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"
@@ -1420,6 +1421,50 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
}
/*
+ * 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);
+ ForeignTable *ftable;
+
+ 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);
+ if (routine->PlanRelScan == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign-data wrapper must implement PlanRelScan to scan foreign table")));
+
+ /* Let the FDW do its planning */
+ pathnode->fplan = routine->PlanRelScan(rte->relid, root, rel);
+ if (pathnode->fplan == NULL)
+ elog(ERROR, "PlanRelScan returned NULL");
+
+ /*
+ * Fill in some extra fields in the plan for the convenience of the
+ * BeginScan function when the plan is later executed.
+ */
+ ftable = GetForeignTable(rte->relid);
+ pathnode->fplan->serverid = ftable->serverid;
+ pathnode->fplan->userid = GetUserId();
+
+ /* 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
* relations.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 1f79ba2..1e8518f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -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;
@@ -462,6 +456,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;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 22447f9..1fa49e5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -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,13 @@ 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 +2230,11 @@ 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 applied to a foreign table"),
+ parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
pushedDown);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3857205..63fa0b5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -309,6 +309,9 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
+%type <list> opt_fdw_options fdw_options
+%type <defelt> fdw_option
+
%type <range> OptTempTableName
%type <into> into_clause create_as_target
@@ -3509,16 +3512,33 @@ AlterExtensionContentsStmt:
*
*****************************************************************************/
-CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_validator create_generic_options
+CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_fdw_options create_generic_options
{
CreateFdwStmt *n = makeNode(CreateFdwStmt);
n->fdwname = $5;
- n->validator = $6;
+ n->func_options = $6;
n->options = $7;
$$ = (Node *) n;
}
;
+fdw_option:
+ VALIDATOR handler_name { $$ = makeDefElem("validator", (Node *)$2); }
+ | NO VALIDATOR { $$ = makeDefElem("validator", NULL); }
+ | HANDLER handler_name { $$ = makeDefElem("handler", (Node *)$2); }
+ | NO HANDLER { $$ = makeDefElem("handler", NULL); }
+ ;
+
+fdw_options:
+ fdw_option { $$ = list_make1($1); }
+ | fdw_options fdw_option { $$ = lappend($1, $2); }
+ ;
+
+opt_fdw_options:
+ fdw_options { $$ = $1; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
/*****************************************************************************
*
* QUERY :
@@ -3551,28 +3571,20 @@ DropFdwStmt: DROP FOREIGN DATA_P WRAPPER name opt_drop_behavior
*
****************************************************************************/
-AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name validator_clause alter_generic_options
+AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name opt_fdw_options alter_generic_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
- n->validator = $6;
- n->change_validator = true;
+ n->func_options = $6;
n->options = $7;
$$ = (Node *) n;
}
- | ALTER FOREIGN DATA_P WRAPPER name validator_clause
+ | ALTER FOREIGN DATA_P WRAPPER name fdw_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
- n->validator = $6;
- n->change_validator = true;
- $$ = (Node *) n;
- }
- | ALTER FOREIGN DATA_P WRAPPER name alter_generic_options
- {
- AlterFdwStmt *n = makeNode(AlterFdwStmt);
- n->fdwname = $5;
- n->options = $6;
+ n->func_options = $6;
+ n->options = NIL;
$$ = (Node *) n;
}
;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c6491e1..3a50642 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -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)
{
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 2be0696..779844d 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -453,3 +453,29 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
{
return textsend(fcinfo);
}
+
+/*
+ * fdw_handler_in - input routine for pseudo-type FDW_HANDLER.
+ */
+Datum
+fdw_handler_in(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot accept a value of type fdw_handler")));
+
+ PG_RETURN_VOID(); /* keep compiler quiet */
+}
+
+/*
+ * fdw_handler_out - output routine for pseudo-type FDW_HANDLER.
+ */
+Datum
+fdw_handler_out(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot display a value of type fdw_handler")));
+
+ PG_RETURN_VOID(); /* keep compiler quiet */
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 0fd706c..4f4b09b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6234,6 +6234,7 @@ getForeignDataWrappers(int *numForeignDataWrappers)
int i_fdwname;
int i_rolname;
int i_fdwvalidator;
+ int i_fdwhandler;
int i_fdwacl;
int i_fdwoptions;
@@ -6247,13 +6248,27 @@ getForeignDataWrappers(int *numForeignDataWrappers)
/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");
- appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
- "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl,"
- "array_to_string(ARRAY("
- " SELECT option_name || ' ' || quote_literal(option_value) "
- " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
- "FROM pg_foreign_data_wrapper",
- username_subquery);
+ if (g_fout->remoteVersion >= 90100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+ "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, "
+ "fdwhandler::pg_catalog.regproc, fdwacl,"
+ "array_to_string(ARRAY("
+ " SELECT option_name || ' ' || quote_literal(option_value) "
+ " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
+ "FROM pg_foreign_data_wrapper",
+ username_subquery);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+ "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl,"
+ "array_to_string(ARRAY("
+ " SELECT option_name || ' ' || quote_literal(option_value) "
+ " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
+ "FROM pg_foreign_data_wrapper",
+ username_subquery);
+ }
res = PQexec(g_conn, query->data);
check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
@@ -6268,6 +6283,8 @@ getForeignDataWrappers(int *numForeignDataWrappers)
i_fdwname = PQfnumber(res, "fdwname");
i_rolname = PQfnumber(res, "rolname");
i_fdwvalidator = PQfnumber(res, "fdwvalidator");
+ if (g_fout->remoteVersion >= 90100)
+ i_fdwhandler = PQfnumber(res, "fdwhandler");
i_fdwacl = PQfnumber(res, "fdwacl");
i_fdwoptions = PQfnumber(res, "fdwoptions");
@@ -6281,6 +6298,10 @@ getForeignDataWrappers(int *numForeignDataWrappers)
fdwinfo[i].dobj.namespace = NULL;
fdwinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname));
fdwinfo[i].fdwvalidator = strdup(PQgetvalue(res, i, i_fdwvalidator));
+ if (g_fout->remoteVersion >= 90100)
+ fdwinfo[i].fdwhandler = strdup(PQgetvalue(res, i, i_fdwhandler));
+ else
+ fdwinfo[i].fdwhandler = NULL;
fdwinfo[i].fdwoptions = strdup(PQgetvalue(res, i, i_fdwoptions));
fdwinfo[i].fdwacl = strdup(PQgetvalue(res, i, i_fdwacl));
@@ -10906,6 +10927,11 @@ dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
appendPQExpBuffer(q, " VALIDATOR %s",
fdwinfo->fdwvalidator);
+ if (g_fout->remoteVersion >= 90100 && fdwinfo->fdwhandler &&
+ strcmp(fdwinfo->fdwhandler, "-") != 0)
+ appendPQExpBuffer(q, " HANDLER %s",
+ fdwinfo->fdwhandler);
+
if (fdwinfo->fdwoptions && strlen(fdwinfo->fdwoptions) > 0)
appendPQExpBuffer(q, " OPTIONS (%s)", fdwinfo->fdwoptions);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3c02af4..cc07686 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -441,6 +441,7 @@ typedef struct _fdwInfo
DumpableObject dobj;
char *rolname;
char *fdwvalidator;
+ char *fdwhandler;
char *fdwoptions;
char *fdwacl;
} FdwInfo;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 735eef7..719782f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3563,9 +3563,11 @@ listForeignDataWrappers(const char *pattern, bool verbose)
printfPQExpBuffer(&buf,
"SELECT fdwname AS \"%s\",\n"
" pg_catalog.pg_get_userbyid(fdwowner) AS \"%s\",\n"
+ " fdwhandler::pg_catalog.regproc AS \"%s\",\n"
" fdwvalidator::pg_catalog.regproc AS \"%s\"",
gettext_noop("Name"),
gettext_noop("Owner"),
+ gettext_noop("Handler"),
gettext_noop("Validator"));
if (verbose)
diff --git a/src/include/catalog/pg_foreign_data_wrapper.h b/src/include/catalog/pg_foreign_data_wrapper.h
index a83556d..971326c 100644
--- a/src/include/catalog/pg_foreign_data_wrapper.h
+++ b/src/include/catalog/pg_foreign_data_wrapper.h
@@ -33,6 +33,7 @@ CATALOG(pg_foreign_data_wrapper,2328)
NameData fdwname; /* foreign-data wrapper name */
Oid fdwowner; /* FDW owner */
Oid fdwvalidator; /* optional validation function */
+ Oid fdwhandler; /* foreign-data routines function */
/* VARIABLE LENGTH FIELDS start here. */
@@ -52,11 +53,12 @@ typedef FormData_pg_foreign_data_wrapper *Form_pg_foreign_data_wrapper;
* ----------------
*/
-#define Natts_pg_foreign_data_wrapper 5
+#define Natts_pg_foreign_data_wrapper 6
#define Anum_pg_foreign_data_wrapper_fdwname 1
#define Anum_pg_foreign_data_wrapper_fdwowner 2
#define Anum_pg_foreign_data_wrapper_fdwvalidator 3
-#define Anum_pg_foreign_data_wrapper_fdwacl 4
-#define Anum_pg_foreign_data_wrapper_fdwoptions 5
+#define Anum_pg_foreign_data_wrapper_fdwhandler 4
+#define Anum_pg_foreign_data_wrapper_fdwacl 5
+#define Anum_pg_foreign_data_wrapper_fdwoptions 6
#endif /* PG_FOREIGN_DATA_WRAPPER_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index a859885..834adb8 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3913,6 +3913,10 @@ DATA(insert OID = 2777 ( anynonarray_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 27
DESCR("I/O");
DATA(insert OID = 2778 ( anynonarray_out PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "2776" _null_ _null_ _null_ _null_ anynonarray_out _null_ _null_ _null_ ));
DESCR("I/O");
+DATA(insert OID = 3116 ( fdw_handler_in PGNSP PGUID 12 1 0 0 f f f f f i 1 0 3115 "2275" _null_ _null_ _null_ _null_ fdw_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3117 ( fdw_handler_out PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "3115" _null_ _null_ _null_ _null_ fdw_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
/* cryptographic */
DATA(insert OID = 2311 ( md5 PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ md5_text _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index d82078e..0f7312e 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -631,6 +631,8 @@ DATA(insert OID = 2776 ( anynonarray PGNSP PGUID 4 t p P f t \054 0 0 0 anynona
#define ANYNONARRAYOID 2776
DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 0 _null_ _null_ ));
#define ANYENUMOID 3500
+DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_handler_in fdw_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ ));
+#define FDW_HANDLEROID 3115
/*
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
new file mode 100644
index 0000000..2aaaf22
--- /dev/null
+++ b/src/include/executor/nodeForeignscan.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2011, 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
index 0000000..22e7a8e
--- /dev/null
+++ b/src/include/foreign/fdwapi.h
@@ -0,0 +1,159 @@
+/*-------------------------------------------------------------------------
+ *
+ * fdwapi.h
+ * API for foreign-data wrappers
+ *
+ * Portions Copyright (c) 1996-2011, 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;
+
+ Oid serverid;
+ Oid userid;
+
+ /*
+ * 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 NOT_USED
+ /* XXX: Should FDWs estimate rows and width? */
+ 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 can be used as a container.
+ */
+ List *fdw_private;
+};
+typedef struct FdwPlan FdwPlan;
+
+struct FdwExecutionState
+{
+ /* FDW-private data */
+ void *fdw_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 PlanRelScan, BeginScan, Iterate, ReScan and EndScan.
+ *
+ * The PlanXXX functions return an FdwPlan struct that can later be executed
+ * with BeginScan. The implementation should fill in the cost estimates in
+ * FdwPlan, and may store private information.
+ */
+struct FdwRoutine
+{
+ /*
+ * Plan a scan on a foreign table. 'foreigntableid' identifies the foreign
+ * table.
+ *
+ * 'root' and 'baserel' contain context information that the
+ * implementation can use to restrict the data that is fetched.
+ * baserel->baserestrictinfo is particularly interesting, 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. baserel->reltargetlist
+ * can be used to determine which columns need to be fetched.
+ */
+ FdwPlan *(*PlanRelScan) (Oid foreigntableid, PlannerInfo *root,
+ RelOptInfo *baserel);
+
+ /*
+ * Begin execution of a foreign scan.
+ *
+ * This function is only called when an actual scan is needed, EXPLAIN
+ * without ANALYZE option doesn't call BeginScan().
+ */
+ FdwExecutionState *(*BeginScan) (FdwPlan * plan, ParamListInfo params);
+
+ /*
+ * Fetch the next record and store it into slot.
+ *
+ * FDW can return result as either a physical tuple or a virtual tuple. If
+ * FDW returns virtual tuple, executor will materialize the virtual tuple
+ * and store tableoid in it.
+ *
+ * When the end of external data is reached, FDW should clear the slot
+ * with ExecClearTuple(slot) to tell executor to finish the scan.
+ *
+ * Note that Iterate is called in per-tuple memory context which is reset
+ * before each call.
+ */
+ void (*Iterate) (FdwExecutionState * state, TupleTableSlot *slot);
+
+ /*
+ * Reset the read pointer to the head of the scan.
+ *
+ * This function will be called when a new outer tuple is acquired in a
+ * nested loop. Optional, if not defined the executor will call EndScan
+ * and BeginScan.
+ */
+ void (*ReScan) (FdwExecutionState * state);
+
+ /*
+ * End the foreign scan and do clean up.
+ */
+ void (*EndScan) (FdwExecutionState * state);
+
+#ifdef NOT_USED
+
+ /*
+ * 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
+};
+typedef struct FdwRoutine FdwRoutine;
+
+#endif /* FDWAPI_H */
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2d1495c..b0cb37c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -13,6 +13,7 @@
#ifndef FOREIGN_H
#define FOREIGN_H
+#include "foreign/fdwapi.h"
#include "nodes/parsenodes.h"
@@ -38,6 +39,7 @@ typedef struct ForeignDataWrapper
Oid owner; /* FDW owner user Oid */
char *fdwname; /* Name of the FDW */
Oid fdwvalidator;
+ Oid fdwhandler;
List *options; /* fdwoptions as DefElem list */
} ForeignDataWrapper;
@@ -59,6 +61,12 @@ typedef struct UserMapping
List *options; /* useoptions as DefElem list */
} UserMapping;
+typedef struct ForeignTable
+{
+ Oid relid; /* relation Oid */
+ Oid serverid; /* server Oid */
+ List *options; /* ftoptions as DefElem list */
+} ForeignTable;
extern ForeignServer *GetForeignServer(Oid serverid);
extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
@@ -68,5 +76,8 @@ extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
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 *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
#endif /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 44da70f..a872eae 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,8 +17,10 @@
#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"
@@ -1403,6 +1405,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
* ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0d0574..b7f130f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -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,
@@ -217,6 +220,7 @@ typedef enum NodeTag
T_MergePath,
T_HashPath,
T_TidPath,
+ T_ForeignPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8aaa8c1..6bc427d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1574,7 +1574,7 @@ typedef struct CreateFdwStmt
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
- List *validator; /* optional validator function (qual. name) */
+ List *func_options; /* VALIDATOR/HANDLER conbination */
List *options; /* generic options to FDW */
} CreateFdwStmt;
@@ -1582,8 +1582,7 @@ typedef struct AlterFdwStmt
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
- List *validator; /* optional validator function (qual. name) */
- bool change_validator;
+ List *func_options; /* VALIDATOR/HANDLER conbination */
List *options; /* generic options to FDW */
} AlterFdwStmt;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index b4fb4f2..4367e40 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -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"
@@ -436,6 +437,16 @@ typedef struct WorkTableScan
int wtParam; /* ID of Param representing work table */
} WorkTableScan;
+/* ----------------
+ * ForeignScan node
+ * ----------------
+ */
+typedef struct ForeignScan
+{
+ Scan scan;
+ FdwPlan *fplan;
+} ForeignScan;
+
/*
* ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 49ce441..d3ac26c 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -749,6 +749,15 @@ typedef struct TidPath
} 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.
*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index ff220e3..fe74600 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -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,
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c3eb67b..692f405 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -520,6 +520,8 @@ extern Datum pg_node_tree_in(PG_FUNCTION_ARGS);
extern Datum pg_node_tree_out(PG_FUNCTION_ARGS);
extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS);
extern Datum pg_node_tree_send(PG_FUNCTION_ARGS);
+extern Datum fdw_handler_in(PG_FUNCTION_ARGS);
+extern Datum fdw_handler_out(PG_FUNCTION_ARGS);
/* regexp.c */
extern Datum nameregexeq(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index d6c650b..8f7eac5 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -16,11 +16,11 @@ CREATE ROLE unprivileged_role;
CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
-SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
- fdwname | fdwvalidator | fdwoptions
-------------+--------------------------+------------
- dummy | - |
- postgresql | postgresql_fdw_validator |
+SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+ fdwname | fdwvalidator | fdwhandler | fdwoptions
+------------+--------------------------+------------+------------
+ dummy | - | - |
+ postgresql | postgresql_fdw_validator | - |
(2 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
@@ -38,12 +38,12 @@ CREATE FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR
ERROR: function bar(text[], oid) does not exist
CREATE FOREIGN DATA WRAPPER foo;
\dew
- List of foreign-data wrappers
- Name | Owner | Validator
-------------+-------------------+--------------------------
- dummy | foreign_data_user | -
- foo | foreign_data_user | -
- postgresql | foreign_data_user | postgresql_fdw_validator
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator
+------------+-------------------+---------+--------------------------
+ dummy | foreign_data_user | - | -
+ foo | foreign_data_user | - | -
+ postgresql | foreign_data_user | - | postgresql_fdw_validator
(3 rows)
CREATE FOREIGN DATA WRAPPER foo; -- duplicate
@@ -51,12 +51,12 @@ ERROR: foreign-data wrapper "foo" already exists
DROP FOREIGN DATA WRAPPER foo;
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1');
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+-------------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {testing=1}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+-------------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {testing=1}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
@@ -64,12 +64,12 @@ CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', testing '2'); -- ERROR
ERROR: option "testing" provided more than once
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2');
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+-----------------------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {testing=1,another=2}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+-----------------------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {testing=1,another=2}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
@@ -80,12 +80,12 @@ HINT: Must be superuser to create a foreign-data wrapper.
RESET ROLE;
CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator;
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | postgresql_fdw_validator | |
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | postgresql_fdw_validator | |
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
-- ALTER FOREIGN DATA WRAPPER
@@ -97,12 +97,12 @@ ALTER FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR
ERROR: function bar(text[], oid) does not exist
ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR;
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | |
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | |
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
@@ -112,34 +112,34 @@ ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP c); -- ERROR
ERROR: option "c" not found
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+-----------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {a=1,b=2}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+-----------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {a=1,b=2}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+-----------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {b=3,c=4}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+-----------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {b=3,c=4}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4'); -- ERROR
ERROR: option "b" provided more than once
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {b=3,c=4,a=2}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {b=3,c=4,a=2}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
SET ROLE regress_test_role;
@@ -149,12 +149,12 @@ HINT: Must be superuser to alter a foreign-data wrapper.
SET ROLE regress_test_role_super;
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+-------------------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+-------------------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role; -- ERROR
@@ -168,12 +168,12 @@ ERROR: permission denied to alter foreign-data wrapper "foo"
HINT: Must be superuser to alter a foreign-data wrapper.
RESET ROLE;
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------------+--------------------------+-------------------+-------------------
- dummy | foreign_data_user | - | |
- foo | regress_test_role_super | - | | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------------+---------+--------------------------+-------------------+-------------------
+ dummy | foreign_data_user | - | - | |
+ foo | regress_test_role_super | - | - | | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
-- DROP FOREIGN DATA WRAPPER
@@ -182,12 +182,12 @@ ERROR: foreign-data wrapper "nonexistent" does not exist
DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
NOTICE: foreign-data wrapper "nonexistent" does not exist, skipping
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------------+--------------------------+-------------------+-------------------
- dummy | foreign_data_user | - | |
- foo | regress_test_role_super | - | | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------------+---------+--------------------------+-------------------+-------------------
+ dummy | foreign_data_user | - | - | |
+ foo | regress_test_role_super | - | - | | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP ROLE regress_test_role_super; -- ERROR
@@ -202,23 +202,23 @@ ALTER ROLE regress_test_role_super SUPERUSER;
DROP FOREIGN DATA WRAPPER foo;
DROP ROLE regress_test_role_super;
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------
- dummy | foreign_data_user | - | |
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy | foreign_data_user | - | - | |
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(2 rows)
CREATE FOREIGN DATA WRAPPER foo;
CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
CREATE USER MAPPING FOR current_user SERVER s1;
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------
- dummy | foreign_data_user | - | |
- foo | foreign_data_user | - | |
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy | foreign_data_user | - | - | |
+ foo | foreign_data_user | - | - | |
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
\des+
@@ -250,11 +250,11 @@ NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to server s1
drop cascades to user mapping for foreign_data_user
\dew+
- List of foreign-data wrappers
- Name | Owner | Validator | Access privileges | Options
-------------+-------------------+--------------------------+-------------------+---------
- dummy | foreign_data_user | - | |
- postgresql | foreign_data_user | postgresql_fdw_validator | |
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | Options
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy | foreign_data_user | - | - | |
+ postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(2 rows)
\des+
@@ -669,6 +669,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 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;
@@ -1105,9 +1109,9 @@ NOTICE: drop cascades to server sc
\c
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
-SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
- fdwname | fdwvalidator | fdwoptions
----------+--------------+------------
+SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
+ fdwname | fdwvalidator | fdwhandler | fdwoptions
+---------+--------------+------------+------------
(0 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index 86b698a..2e0fa9a 100644
--- a/src/test/regress/sql/foreign_data.sql
+++ b/src/test/regress/sql/foreign_data.sql
@@ -24,7 +24,7 @@ CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
-SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;
@@ -271,6 +271,8 @@ COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
\d+ ft1
\det+
CREATE INDEX id_ft1_c2 ON ft1 (c2); -- ERROR
+SELECT * FROM ft1; -- ERROR
+EXPLAIN SELECT * FROM ft1; -- ERROR
-- ALTER FOREIGN TABLE
COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
@@ -453,6 +455,6 @@ DROP FOREIGN DATA WRAPPER dummy CASCADE;
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
-SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
+SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;