review: FDW API
Hi,
what follows is a review of the FDW API patch from
http://archives.postgresql.org/message-id/20110114212358.82C7.6989961C@metrosystems.co.jp
All three patches apply cleanly and compile without warnings. Regression
tests pass.
Let me go patch by patch, starting with the first one that adds the
HANDLER option.
It adds one useless hunk in src/backend/commands/foreigncmds.c (changes
order if #includes).
There's a typo in a C commnent ("determin which validator to be used").
Other than that, it looks OK.
The third patch just adds a GetForeignTable helper function and it looks OK.
The second patch actually adds the API. First of all, I'd like say that
it's a really cool piece of code, allowing all kinds of awesome
funcionality to be added. I'm already excited by the things that this
will make possible. Congratulations!
To get a feel of the API I wrote a simple FDW wrapper that presents data
from the commitfest RSS feed, based heavily on twitter_fdw by Hitoshi
Harada. Please treat my thoughts as someone's who doesn't really know
*why* the API looks like it does, but has some observations about what
was missing or what felt strange when using it. I guess that's the
position a typical FDW wrapper writer will be in.
First of all, the C comments mention that PlanRelScan should put a tuple
descriptor in FdwPlan, but there's no such field in it. Also, comments
for PlanRelScan talk about the 'attnos' argument, which is not in the
function's signature. I guess the comments are just obsolete and should
be updated. I think it's actually a good thing you don't have to put a
TupleDesc in FdwPlan.
There are two API methods, PlanNative and PlanQuery that are ifdef'd out
using IN_THE_FUTURE. Isn't the standard symbol we use NOT_USED? Also,
the comments say you can implement either PlanRelScan or PlanNative, and
only the former is available for now. If we keep these methods
commented, they should be moved to the end of the struct, so that
uncommenting them will not break compatibility with existing FDWs.
The only documentation a FDW writer has is fdwapi.h, so comments there
need to be top-notch. We might contemplate writing a documentation
chapter explaining how FDW handlers should be written, like we do for C
SRFs and libpq programs, but I guess for now just the headers file would
be enough.
FdwExecutionState is just a struct around a void pointer, can we imagine
adding more fields there? If not, maybe we could just remove the
structure and pass void pointers around? OTOH that gives us some
compiler checking and possibility of extending the struct, so I guess we
could also just leave it like that.
The Iterate method gets passed a TupleTableSlot. Do we really need such
a low-level structure? It makes returning the result easy, because you
just form your tuple and call ExecStoreTuple, but perhaps we could
abstract that away a bit and add a helper method that will take a tuple
and call ExecStoreTuple for us, passing InvalidBuffer and false as the
remaining arguments. Or maybe make Iterate return the tuple and call
ExecStoreTuple internally? I don't have strong opinions, but
TupleTableSlot feels a bit too gutty - I'm having a hard time imagining
what fields from it would be useful for a FDW writer, and so perhaps you
don't need to expose it.
Why does BeginScan accept a ParamListInfo argument? First of all, it
feels like a parsing thing, not a relation scan thing, so perhaps it
should be available at some other, earlier stage. Second of all, what
would it be useful for anyway? Neither file_fdw nor my commitfest_fdw
does anything with it.
We could use comments about how to return tuples from Iterate and how to
finish returning them. I had to look at the example to figure out that
you need ExecClearTuple(slot) in your last Iterate. If Iterate's
interface were to change, we could just return NULL instead of a tuple
to say that we're done.
We could be a bit more explicit about how to allocate objects, for
instance if I'm allocating a FdwPlan in my PlanRelScan with a palloc,
will it not go away too soon, or too late (IOW leak)?
I ran into a problem when doing:
select i from generate_series(1, 100000) as g(i), pgcommitfest;
when I was trying to check for leaks. It returned four rows
(pgcommitfest is the foreign table that returns four rows). Explain
analyze shows a nested loop with a foreign scan as inner loop. Maybe
it's because I didn't implement ReScan, but the API says I don't have to.
If you don't implement Iterate you get a elog(ERROR). But if you don't
implement one of the other required methods, you segfault. Feels
inconsistent.
PlanRelScan looks like something that could use all kinds of information
to come up with a good plan. Maybe we could change its input argument to
a single struct that would contain all the current arguments, so it'll
be easier to extend when people will start writing FDWs and will find
out that they'd like more information available. Doing that would mean
that adding a field in a release would mean FDWs just need to be
recompiled and they keep working. Or do we opt for the "better an
explicitly compile error than a silent change" approach? If we'd be just
passing additional info to PlanRelScan I'd say keeping old source
compilable would not be a problem.
Storing private info in FdwPlan's private list is really awkward. I know
it's because you need copyObject support, but it's just a big pain if
you want to store several different things. Passing them around as a big
list and doing list_nth(private, MY_WIDGET_OFFSET) feels like writing
Lisp before you learn it has structures :) Is there really no other way?
Maybe PlanNative should get the foreign table OID, not the server OID,
to resemble PlanRelScan more. The server OID is just a syscache lookup
away, anyway.
If you do EXPLAIN SELECT * FROM foregintable, BeginScan is not called,
but EndScan is. That's weird, and I noticed because I got a segfault
when EndScan tried to free things that BeginScan allocated. Maybe just
don't call EndScan for EXPLAIN?
That's all as far as the API goes. Feel free to ignore most of these
remarks if you see a reason why your choices are better (or necessary).
I just thought I'd try to take a look at it as a user would (which is
what I am, as I don't fully understand the internals) and offer my
impressions.
In general, the feature looks great and I hope it'll make it into 9.1.
And it we'd get the possibility to write FDW handlers in other PLs than
C, it would rock so hard...
I'm going to mark this a Waiting for Author because of the typos, the
BeginScan/EndScan issue, and the nested loop stopping halfway issue. The
rest are suggestions or just thoughts, and if you don't see them as
justified, I'll mark the next patch Ready for Committer.
Cheers,
Jan
On Sun, 16 Jan 2011 01:55:19 +0100
Jan Urbański <wulczer@wulczer.org> wrote:
what follows is a review of the FDW API patch from
http://archives.postgresql.org/message-id/20110114212358.82C7.6989961C@metrosystems.co.jp
Thanks for the comments!
For now, I answer to the first half of your comments. I'll answer to
the rest soon.
All three patches apply cleanly and compile without warnings. Regression
tests pass.Let me go patch by patch, starting with the first one that adds the
HANDLER option.
Sure.
It adds one useless hunk in src/backend/commands/foreigncmds.c (changes
order if #includes).There's a typo in a C commnent ("determin which validator to be used").
Other than that, it looks OK.
Fixed in attached patch.
The third patch just adds a GetForeignTable helper function and it looks OK.
Thanks. This patch might be able to committed separately because it
would be small enough, and similar to existing lookup functions such
as GetForeignDataWrapper() and GetForeignServer().
The second patch actually adds the API. First of all, I'd like say that
it's a really cool piece of code, allowing all kinds of awesome
funcionality to be added. I'm already excited by the things that this
will make possible. Congratulations!To get a feel of the API I wrote a simple FDW wrapper that presents data
from the commitfest RSS feed, based heavily on twitter_fdw by Hitoshi
Harada. Please treat my thoughts as someone's who doesn't really know
*why* the API looks like it does, but has some observations about what
was missing or what felt strange when using it. I guess that's the
position a typical FDW wrapper writer will be in.
Sure, I think your point of view is very important.
First of all, the C comments mention that PlanRelScan should put a tuple
descriptor in FdwPlan, but there's no such field in it. Also, comments
for PlanRelScan talk about the 'attnos' argument, which is not in the
function's signature. I guess the comments are just obsolete and should
be updated. I think it's actually a good thing you don't have to put a
TupleDesc in FdwPlan.
Removed comments about 'attnos' and tuple descriptor.
There are two API methods, PlanNative and PlanQuery that are ifdef'd out
using IN_THE_FUTURE. Isn't the standard symbol we use NOT_USED? Also,
the comments say you can implement either PlanRelScan or PlanNative, and
only the former is available for now. If we keep these methods
commented, they should be moved to the end of the struct, so that
uncommenting them will not break compatibility with existing FDWs.
Agreed. Moved ifdef'd part to the end of struct.
The only documentation a FDW writer has is fdwapi.h, so comments there
need to be top-notch. We might contemplate writing a documentation
chapter explaining how FDW handlers should be written, like we do for C
SRFs and libpq programs, but I guess for now just the headers file would
be enough.
file_fdw and postgresql_fdw would be good samples for wrapper
developer if we could ship them as contrib modules.
FdwExecutionState is just a struct around a void pointer, can we imagine
adding more fields there? If not, maybe we could just remove the
structure and pass void pointers around? OTOH that gives us some
compiler checking and possibility of extending the struct, so I guess we
could also just leave it like that.
ISTM that using a struct as a interface is better than void*, as you
mentioned.
The Iterate method gets passed a TupleTableSlot. Do we really need such
a low-level structure? It makes returning the result easy, because you
just form your tuple and call ExecStoreTuple, but perhaps we could
abstract that away a bit and add a helper method that will take a tuple
and call ExecStoreTuple for us, passing InvalidBuffer and false as the
remaining arguments. Or maybe make Iterate return the tuple and call
ExecStoreTuple internally? I don't have strong opinions, but
TupleTableSlot feels a bit too gutty - I'm having a hard time imagining
what fields from it would be useful for a FDW writer, and so perhaps you
don't need to expose it.
This would be debatable issue. Currently Iterate() is expected to
return materialized HeapTuple through TupleTableSlot.
I think an advantage to use TupleTableSlot is that FDW can set shoudFree
flag for the tuple.
Why does BeginScan accept a ParamListInfo argument? First of all, it
feels like a parsing thing, not a relation scan thing, so perhaps it
should be available at some other, earlier stage. Second of all, what
would it be useful for anyway? Neither file_fdw nor my commitfest_fdw
does anything with it.
ParamListInfo is added to pass parameters of PREPARE/EXECUTE statement
to FDWs.
Plan for a prepared query is generated at PREPARE with placeholders,
and executed at EXECUTE with actual values. With ParamListInfo
parameter for BeginScan(), FDWs would be able to optimize their remote
query with actual parameter values.
Regard,
--
Shigeru Hanada
On Mon, 17 Jan 2011 22:13:19 +0900
Shigeru HANADA <hanada@metrosystems.co.jp> wrote:
Fixed in attached patch.
Sorry, I have not attached patch to last message.
I'll post revised patch in next message soon.
Regards,
--
Shigeru Hanada
On Sun, 16 Jan 2011 01:55:19 +0100
Jan Urbański <wulczer@wulczer.org> wrote:
<snip>
In general, the feature looks great and I hope it'll make it into 9.1.
And it we'd get the possibility to write FDW handlers in other PLs than
C, it would rock so hard...I'm going to mark this a Waiting for Author because of the typos, the
BeginScan/EndScan issue, and the nested loop stopping halfway issue. The
rest are suggestions or just thoughts, and if you don't see them as
justified, I'll mark the next patch Ready for Committer.
Thanks a lot for the comments. I've (hopefully) fixed issues above.
Please find attached patches.
== patch list ==
1) 20110118-no_fdw_perm_check.patch - This patch is not included in
last post. This had been proposed on 2011-01-05 first, but maybe has
not been reviewd yet. I re-propose this patch for SQL standard
conformance. This patch removes permission check that requires USAGE
on the foreign-data wrapper at CREATE FOREIGN TABLE.
Please see original post for details.
http://archives.postgresql.org/message-id/20110105145206.30FD.6989961C@metrosystems.co.jp
2) 20110118-fdw_catalog_lookup.patch - This patch adds GetForeignTables.
Fixed lack of pg_foreign_table.h inclusion.
3) 20110118-fdw_handler.patch - This patch adds support for HANDLER
option to FOREIGN DATA WRAPPER object.
4) 20110118-foreign_scan.patch - This patch adds ForeignScan executor
node and FDW API hooks based on Heikki's proposal. As Itagaki-san
suggested on 2010-12-21, FDW must generate information for EXPLAIN
VERBOSE every PlanRelScan() call. It's better to avoid such overhead,
so new EXPLAIN hook would be needed. I'll try to make it cleaner.
================
And I'll reply to the rest of your comments.
We could use comments about how to return tuples from Iterate and how to
finish returning them. I had to look at the example to figure out that
you need ExecClearTuple(slot) in your last Iterate. If Iterate's
interface were to change, we could just return NULL instead of a tuple
to say that we're done.
I've added some comments for FDW-developer to fdwapi.h, though they
wouldn't be enough.
We could be a bit more explicit about how to allocate objects, for
instance if I'm allocating a FdwPlan in my PlanRelScan with a palloc,
will it not go away too soon, or too late (IOW leak)?
For that example, the answer is no. Objects are allocated in
MessageContext if you don't switch context and released when the query
has been finished. I agree that more documentation or comments for
FDW-developers should be added.
Maybe PlanNative should get the foreign table OID, not the server OID,
to resemble PlanRelScan more. The server OID is just a syscache lookup
away, anyway.
You would missed a case that multiple foreign tables are used in a
query. Main purpose of PlanNative is to support pass-through
execution of remote query. In pass-through mode, you can use syntax
as if you have directly connected to external server, so can't use
PostgreSQL's parser.
If you do EXPLAIN SELECT * FROM foregintable, BeginScan is not called,
but EndScan is. That's weird, and I noticed because I got a segfault
when EndScan tried to free things that BeginScan allocated. Maybe just
don't call EndScan for EXPLAIN?
Fixed to not call EndScan if it was EXPLAIN execution.
Regards,
--
Shigeru Hanada
On 18.01.2011 17:26, Shigeru HANADA wrote:
1) 20110118-no_fdw_perm_check.patch - This patch is not included in
last post. This had been proposed on 2011-01-05 first, but maybe has
not been reviewd yet. I re-propose this patch for SQL standard
conformance. This patch removes permission check that requires USAGE
on the foreign-data wrapper at CREATE FOREIGN TABLE.
Please see original post for details.
http://archives.postgresql.org/message-id/20110105145206.30FD.6989961C@metrosystems.co.jp
Committed this part.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, Jan 21, 2011 at 10:17 AM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
On 18.01.2011 17:26, Shigeru HANADA wrote:
1) 20110118-no_fdw_perm_check.patch - This patch is not included in
last post. This had been proposed on 2011-01-05 first, but maybe has
not been reviewd yet. I re-propose this patch for SQL standard
conformance. This patch removes permission check that requires USAGE
on the foreign-data wrapper at CREATE FOREIGN TABLE.
Please see original post for details.http://archives.postgresql.org/message-id/20110105145206.30FD.6989961C@metrosystems.co.jp
Committed this part.
How much review have you done of parts (3) and (4)? The key issue for
all of the FDW work in progress seems to be what the handler API is
going to look like, and so once we get that committed it will unblock
a lot of other things.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 21.01.2011 17:57, Robert Haas wrote:
How much review have you done of parts (3) and (4)?
Not much. I'm getting there..
The key issue for
all of the FDW work in progress seems to be what the handler API is
going to look like, and so once we get that committed it will unblock
a lot of other things.
Yep. The API that's there now was originally suggested by me, so I
probably won't have big complaints about it. I'll have to also look at
the PostgreSQL and file implementations of it to see that it really fits
the bill.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On 18.01.2011 17:26, Shigeru HANADA wrote:
3) 20110118-fdw_handler.patch - This patch adds support for HANDLER
option to FOREIGN DATA WRAPPER object.
Some quick comments on that:
* I wonder if CREATE FOREIGN DATA WRAPPER should automatically create
the handler function, if it doesn't exist yet. That's what CREATE
LANGUAGE does, which is similar. Although it doesn't seem to be
documented for CREATE LANGUAGE either, is it deprecated?
* The elogs in parse_func_options() should be ereports.
* pg_dump should check the version number and only try to select
fdwhandler column if >= 9.1. See the other functions there for example
of that.
* dumpForeignDataWrapper() in pg_dump checks if fdwhandler field is "-".
I don't think we use that as magic value there, do we? Same with validator.
* Please check that the HANDLER and VALIDATOR options that pg_dump
creates properly quoted.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
Some quick comments on that:
* I wonder if CREATE FOREIGN DATA WRAPPER should automatically create
the handler function, if it doesn't exist yet. That's what CREATE
LANGUAGE does, which is similar. Although it doesn't seem to be
documented for CREATE LANGUAGE either, is it deprecated?
Doing that would require the equivalent of pg_pltemplate for FDWs, no?
I think we're a long way from wanting to do that. Also, it seems to me
that add-on FDWs are likely to end up getting packaged as extensions,
so the extension machinery will probably render the question moot pretty
soon.
regards, tom lane
On Sat, Jan 22, 2011 at 07:20, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
* I wonder if CREATE FOREIGN DATA WRAPPER should automatically create
the handler function, if it doesn't exist yet.Doing that would require the equivalent of pg_pltemplate for FDWs, no?
I think we're a long way from wanting to do that. Also, it seems to me
that add-on FDWs are likely to end up getting packaged as extensions,
The proposed file_fdw.sql actually creates a default FDW on installation.
So I think the installation scripts work as a template even if we don't
have FDW template catalogs.
+ /* contrib/file_fdw/file_fdw.sql.in */
+ -- create wrapper with validator and handler
+ CREATE OR REPLACE FUNCTION file_fdw_validator (text[], oid)
+ CREATE OR REPLACE FUNCTION file_fdw_handler ()
+ CREATE FOREIGN DATA WRAPPER file_fdw
+ VALIDATOR file_fdw_validator HANDLER file_fdw_handler;
--
Itagaki Takahiro
On 21.01.2011 17:57, Robert Haas wrote:
On Fri, Jan 21, 2011 at 10:17 AM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:On 18.01.2011 17:26, Shigeru HANADA wrote:
1) 20110118-no_fdw_perm_check.patch - This patch is not included in
last post. This had been proposed on 2011-01-05 first, but maybe has
not been reviewd yet. I re-propose this patch for SQL standard
conformance. This patch removes permission check that requires USAGE
on the foreign-data wrapper at CREATE FOREIGN TABLE.
Please see original post for details.http://archives.postgresql.org/message-id/20110105145206.30FD.6989961C@metrosystems.co.jp
Committed this part.
How much review have you done of parts (3) and (4)? The key issue for
all of the FDW work in progress seems to be what the handler API is
going to look like, and so once we get that committed it will unblock
a lot of other things.
I've gone through the code in a bit more detail now. I did a bunch of
cosmetic changes along the way, patch attached. I also added a few
paragraphs in the docs. We need more extensive documentation, but this
at least marks the places where I think the docs need to go.
Comments:
* How can a FDW fetch only the columns required by the scan? The file
FDW has to read the whole file anyhow, but it could perhaps skip calling
the input function for unnecessary columns. But more importantly, with
something like the postgresql_fdw you don't want to fetch any extra
columns across the wire. I gather the way to do it is to copy
RelOptInfo->attr_needed to private storage at plan stage, and fill the
not-needed attributes with NULLs in Iterate. That gets a bit awkward,
you need to transform attr_needed to something that can be copied for
starters. Or is that information somehow available at execution phase
otherwise?
* I think we need something in RelOptInfo to mark foreign tables. At the
moment, you need to call IsForeignTable() which does a catalog lookup.
Maybe a new RTEKind, or a boolean flag.
* Can/should we make ReScan optional? Could the executor just do
EndScan+BeginScan if there's no ReScan function?
* Is there any point in allowing a FDW without a handler? It's totally
useless, isn't it? We had the CREATE FOREIGN DATA WRAPPER syntax in
previous versions, and it allowed it, but it has always been totally
useless so I don't think we need to worry much about
backwards-compatibility here.
* Is there any use case for changing the handler or validator function
of an existign FDW with ALTER? To me it just seems like an unnecessary
complication.
* IMHO the "FDW-info" should always be displayed, without VERBOSE. In my
experience with another DBMS that had this feature, the SQL being sent
to the remote server was almost always the key piece of information that
I was looking for in the query plans.
* this check in expand_inherited_rtentry seems misplaced:
/*
* SELECT FOR UPDATE/SHARE is not allowed on 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")));
I don't understand why we'd need to do that for inherited tables in
particular. And it's not working for regular non-inherited foreign tables:
postgres=# SELECT * FROM filetbl2 FOR UPDATE;
ERROR: could not open file "base/11933/16397": No such file or directory
* Need to document how the FDW interacts with transaction
commit/rollback. In particular, I believe EndScan is never called if the
transaction is aborted. That needs to be noted explicitly, and need to
suggest how to clean up any external resources in that case. For
example, postgresql_fdw will need to close any open cursors or result sets.
In general, I think the design is sound. What we need is more
documentation. It'd also be nice to see the postgresql_fdw brought back
to shape so that it works against this latest version of the api patch.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
small-fdw-changes.patchtext/x-diff; name=small-fdw-changes.patchDownload
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..8ef93bc
--- /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.
+ </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 99437ac..c2f4169 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -82,10 +82,11 @@
<!entity geqo SYSTEM "geqo.sgml">
<!entity gist SYSTEM "gist.sgml">
<!entity gin SYSTEM "gin.sgml">
-<!entity planstats SYSTEM "planstats.sgml">
+<!entity planstats SYSTEM "planstats.sgml">
<!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/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 360016c..6acdc9c 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -330,6 +330,9 @@ lookup_fdw_validator_func(DefElem *validator)
/* return value is ignored, so we don't check the type */
}
+/*
+ * Convert a handler function name passed from the parser to an Oid
+ */
static Oid
lookup_fdw_handler_func(DefElem *handler)
{
@@ -365,19 +368,21 @@ parse_func_options(List *func_options, DefElem **validator, DefElem **handler)
if (pg_strcasecmp(def->defname, "validator") == 0)
{
if (*validator)
- elog(ERROR, "duplicated VALIDATOR");
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
*validator = def;
}
else if (pg_strcasecmp(def->defname, "handler") == 0)
{
if (*handler)
- elog(ERROR, "duplicated HANDLER");
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
*handler = def;
}
else
- {
- elog(ERROR, "invalid option");
- }
+ elog(ERROR, "option \"%s\" not recognized", def->defname);
}
}
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index b27de28..9f7b138 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,8 +21,8 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
-#include "executor/nodeFunctionscan.h"
#include "executor/nodeForeignscan.h"
+#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 92b83ac..f275b41 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -85,8 +85,8 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
-#include "executor/nodeFunctionscan.h"
#include "executor/nodeForeignscan.h"
+#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index e6354c7..3183f43 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* nodeForeignscan.c
- * Support routines for sequential scans of foreign tables.
+ * 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
@@ -14,9 +14,9 @@
*/
/*
* INTERFACE ROUTINES
- * ExecForeignScan sequentially scans a foreign table.
- * ExecForeignNext retrieve next tuple in sequential order.
- * ExecInitForeignScan creates and initializes a seqscan node.
+ * ExecForeignScan scans a foreign table.
+ * ExecForeignNext retrieve next tuple.
+ * ExecInitForeignScan creates and initializes a foreign scan node.
* ExecEndForeignScan releases any storage allocated.
* ExecForeignReScan rescans the foreign table
* ExecForeignMarkPos marks scan position
@@ -76,6 +76,8 @@ ForeignNext(ForeignScanState *node)
ExecMaterializeSlot(slot);
/* overwrite only tableoid of the tuple */
+ /* XXX: Isn't this a bit dirty? We're scribbling over the field in
+ * the heap tuple that might've been allocated by the FDW. */
slot->tts_tuple->t_tableOid =
RelationGetRelid(node->ss.ss_currentRelation);
}
@@ -180,8 +182,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->routine = routine;
/*
- * If this execution was not for EXPLAIN w/o ANALYZE flag, initiate the
- * foreign scan.
+ * Tell the FDW to initiate the foreign scan, unless we're just doing
+ * EXPLAIN (ie, aren't going to run the plan).
*/
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
{
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 1fdd925..3da8541 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -263,10 +263,7 @@ GetForeignTable(Oid relid)
Datum datum;
bool isnull;
- tp = SearchSysCache(FOREIGNTABLEREL,
- ObjectIdGetDatum(relid),
- 0, 0, 0);
-
+ tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign table %u", relid);
@@ -297,10 +294,10 @@ GetForeignTable(Oid relid)
FdwRoutine *
GetFdwRoutine(Oid fdwhandler)
{
- FmgrInfo flinfo;
- FunctionCallInfoData fcinfo;
- Datum result;
- FdwRoutine *routine;
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+ Datum result;
+ FdwRoutine *routine;
if (fdwhandler == InvalidOid)
elog(ERROR, "foreign-data wrapper has no handler");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 79c71c4..61c8599 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -572,9 +572,6 @@ _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);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dee46ba..4602ced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -546,8 +546,8 @@ _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_FLOAT_FIELD(startup_cost, "%.2f");
+ WRITE_FLOAT_FIELD(total_cost, "%.2f");
WRITE_NODE_FIELD(private);
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index ae2e134..c196ab6 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -258,7 +258,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
if (IsForeignTable(rte->relid))
{
- /* only foreign scan path is applyable to foreign table */
+ /* only foreign scan path is applicable to a foreign table */
add_path(rel, create_foreignscan_path(root, rel));
}
else
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 938b8af..53ae495 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -352,9 +352,9 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
case T_ForeignScan:
plan = (Plan *) create_foreignscan_plan(root,
- best_path,
- tlist,
- scan_clauses);
+ best_path,
+ tlist,
+ scan_clauses);
break;
default:
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 61f6d5f..2605efb 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1238,13 +1238,14 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * SELECT FOR UPDATE/SHARE is not allowd to foreign tables because
+ * SELECT FOR UPDATE/SHARE is not allowed on 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")));
+ 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.
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 134ec7e..d53f7aa 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1435,7 +1435,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
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)
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 1d60af8..8d477a6 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -455,7 +455,7 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
*tuples = 1;
break;
case RELKIND_FOREIGN_TABLE:
- /* foreign tables has no storage, trust statistics */
+ /* foreign tables have no storage, trust statistics */
*pages = rel->rd_rel->relpages;
*tuples = rel->rd_rel->reltuples;
break;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2a381a0..23cc95a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5986,14 +5986,30 @@ 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, "
- "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);
+ 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, "
+ "0::oid AS fdwhandler, 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);
@@ -6026,7 +6042,6 @@ getForeignDataWrappers(int *numForeignDataWrappers)
fdwinfo[i].fdwoptions = strdup(PQgetvalue(res, i, i_fdwoptions));
fdwinfo[i].fdwacl = strdup(PQgetvalue(res, i, i_fdwacl));
-
/* Decide whether we want to dump it */
selectDumpableObject(&(fdwinfo[i].dobj));
}
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 7be1999..9afdbe7 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -21,7 +21,7 @@
* context with copyObject. It means that FdwPlan, a part of ForeignScan plan
* node, and its contents must have copyObject support too.
*/
-struct FdwPlan
+typedef struct
{
NodeTag type;
@@ -46,20 +46,18 @@ struct FdwPlan
#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.
+ * FDW-private data. Anything stored here needs to have copyObject
+ * support. If FDW needs to store arbitrary data such as a non-Node
+ * structure, Const of bytea can be used as a container.
*/
- List *private;
-};
-typedef struct FdwPlan FdwPlan;
+ Node *private;
+} FdwPlan;
-struct FdwExecutionState
+typedef struct
{
- /* FDW-private data */
+ /* FDW-private data. This doesn't need to have copyObject support. */
void *private;
-};
-typedef struct FdwExecutionState FdwExecutionState;
+} FdwExecutionState;
/*
* Common interface routines of FDW, inspired by the FDW API in the SQL/MED
@@ -72,7 +70,7 @@ typedef struct FdwExecutionState FdwExecutionState;
* with BeginScan. The implementation should fill in the cost estimates in
* FdwPlan, and may store private information.
*/
-struct FdwRoutine
+typedef struct
{
/*
* Plan a scan on a foreign table. 'foreigntableid' identifies the foreign
@@ -144,7 +142,6 @@ struct FdwRoutine
*/
FdwPlan *(*PlanQuery)(PlannerInfo *root, Query query);
#endif
-};
-typedef struct FdwRoutine FdwRoutine;
+} FdwRoutine;
#endif /* FDWAPI_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7d1c681..561d512 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1415,7 +1415,7 @@ typedef struct ForeignScanState
{
ScanState ss; /* its first field is NodeTag */
FdwRoutine *routine;
- FdwExecutionState *fstate; /* private data for each data wrapper */
+ FdwExecutionState *fstate; /* private state of the wrapper */
} ForeignScanState;
/* ----------------------------------------------------------------
On Mon, Jan 24, 2011 at 8:08 AM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
* Is there any point in allowing a FDW without a handler? It's totally
useless, isn't it? We had the CREATE FOREIGN DATA WRAPPER syntax in previous
versions, and it allowed it, but it has always been totally useless so I
don't think we need to worry much about backwards-compatibility here.
Aren't things like dblink using this in its existing form?
* Is there any use case for changing the handler or validator function of an
existign FDW with ALTER? To me it just seems like an unnecessary
complication.
+1.
* IMHO the "FDW-info" should always be displayed, without VERBOSE. In my
experience with another DBMS that had this feature, the SQL being sent to
the remote server was almost always the key piece of information that I was
looking for in the query plans.
+1.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Jan 24, 2011 at 8:08 AM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
How much review have you done of parts (3) and (4)? The key issue for
all of the FDW work in progress seems to be what the handler API is
going to look like, and so once we get that committed it will unblock
a lot of other things.I've gone through the code in a bit more detail now. I did a bunch of
cosmetic changes along the way, patch attached. I also added a few
paragraphs in the docs. We need more extensive documentation, but this at
least marks the places where I think the docs need to go.Comments:
I haven't seen any responses to these comments. Time grows short to
get this committed to PostgreSQL 9.1. We need responses to these
comments and an updated patch ASAP.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, 21 Jan 2011 18:28:19 +0200
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> wrote:
On 18.01.2011 17:26, Shigeru HANADA wrote:
3) 20110118-fdw_handler.patch - This patch adds support for HANDLER
option to FOREIGN DATA WRAPPER object.Some quick comments on that:
Thanks for the comments.
I'll post revised version of patches soon.
* The elogs in parse_func_options() should be ereports.
* pg_dump should check the version number and only try to select
fdwhandler column if >= 9.1. See the other functions there for example
of that.
Fixed.
* dumpForeignDataWrapper() in pg_dump checks if fdwhandler field is "-".
I don't think we use that as magic value there, do we? Same with validator.
That magic value, "-", is used as "no-function-was-set" in
dumpForeignDataWrapper() and dumpForeignServer(), and I followed them.
Agreed that magic value should be removed, but it would be a refactoring
issue about pg_dump.
* Please check that the HANDLER and VALIDATOR options that pg_dump
creates properly quoted.
I checked quoting for HANDLER and VALIDATOR with file_fdw functions,
and it seems works fine. The pg_dump generats:
------------
CREATE FOREIGN DATA WRAPPER dummy_fdw VALIDATOR public."File_Fdw_Validator"
HANDLER public."FILE_FDW_HANDLER";
------------
from these DDLs:
------------
CREATE OR REPLACE FUNCTION "File_Fdw_Validator" (text[], oid)
RETURNS bool
AS '$libdir/file_fdw','file_fdw_validator'
LANGUAGE C STRICT;
CREATE OR REPLACE FUNCTION "FILE_FDW_HANDLER" ()
RETURNS fdw_handler
AS '$libdir/file_fdw','file_fdw_handler'
LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER dummy_fdw
VALIDATOR "File_Fdw_Validator" HANDLER "FILE_FDW_HANDLER";
------------
Regard,
--
Shigeru Hanada
On Mon, 24 Jan 2011 15:08:11 +0200
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> wrote:
I've gone through the code in a bit more detail now. I did a bunch of
cosmetic changes along the way, patch attached. I also added a few
paragraphs in the docs. We need more extensive documentation, but this
at least marks the places where I think the docs need to go.Comments:
Thanks for the comments!
* How can a FDW fetch only the columns required by the scan? The file
FDW has to read the whole file anyhow, but it could perhaps skip calling
the input function for unnecessary columns. But more importantly, with
something like the postgresql_fdw you don't want to fetch any extra
columns across the wire. I gather the way to do it is to copy
RelOptInfo->attr_needed to private storage at plan stage, and fill the
not-needed attributes with NULLs in Iterate. That gets a bit awkward,
you need to transform attr_needed to something that can be copied for
starters. Or is that information somehow available at execution phase
otherwise?
I thought that RelOptInfo->reltargetlist, a list of Var, can be used
for that purpose. FdwPlan can copy it with copyObject(), and pass
it to Iterate through FdwPlan->fdw_private.
Then, postgresql_fdw would be able to retrieve only necessary columns,
or just use "NULL" for unnecessary columns in the SELECT clause to
avoid mapping values to columns. Each way would be decrease amount of
data transfer.
* I think we need something in RelOptInfo to mark foreign tables. At the
moment, you need to call IsForeignTable() which does a catalog lookup.
Maybe a new RTEKind, or a boolean flag.
We can avoid catalog lookup with checking table type in get_relation_info()
and updating RelOptInfo->is_foreign_table if the target was a foreign
table.
* Can/should we make ReScan optional? Could the executor just do
EndScan+BeginScan if there's no ReScan function?
Right, we have enough information to call BeginScan again. Will fix.
* Is there any point in allowing a FDW without a handler? It's totally
useless, isn't it? We had the CREATE FOREIGN DATA WRAPPER syntax in
previous versions, and it allowed it, but it has always been totally
useless so I don't think we need to worry much about
backwards-compatibility here.
dblink (and possibly other external modules) uses FDW without a
handler.
* Is there any use case for changing the handler or validator function
of an existign FDW with ALTER? To me it just seems like an unnecessary
complication.
AFAICS, the only case for that is upgrading FDW to new one without
re-creating foreign tables. I don't have strong opinion for this
issue, and it seems reasonable to remove ALTER feature in first
version.
* IMHO the "FDW-info" should always be displayed, without VERBOSE. In my
experience with another DBMS that had this feature, the SQL being sent
to the remote server was almost always the key piece of information that
I was looking for in the query plans.
Agreed, will fix to show FDW-info always. Is it reasonable to show
"FDW-info" row even if a FDW set explainInfo to NULL?
* this check in expand_inherited_rtentry seems misplaced:
/*
* SELECT FOR UPDATE/SHARE is not allowed on 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")));I don't understand why we'd need to do that for inherited tables in
particular. And it's not working for regular non-inherited foreign tables:postgres=# SELECT * FROM filetbl2 FOR UPDATE;
ERROR: could not open file "base/11933/16397": No such file or directory
It's a remnants of table inheritance support for foreign tables. This
check should be removed from here, and another check should be added
to avoid above error.
* Need to document how the FDW interacts with transaction
commit/rollback. In particular, I believe EndScan is never called if the
transaction is aborted. That needs to be noted explicitly, and need to
suggest how to clean up any external resources in that case. For
example, postgresql_fdw will need to close any open cursors or result sets.
I agree that resource cleanup is an important issue when writing FDW.
FDW should use transaction-safe resources like VirtualFile, or use
ResourceOwner callback mechanism. Is it reasonable to add new page
under "Chapter 35. Extending SQL"?
In general, I think the design is sound. What we need is more
documentation. It'd also be nice to see the postgresql_fdw brought back
to shape so that it works against this latest version of the api patch.
I'll post FDW API patches which reflect comments first, and then I'll
rebase postgresql_fdw against them.
Regards,
--
Shigeru Hanada
On Mon, Jan 31, 2011 at 8:00 AM, Shigeru HANADA
<hanada@metrosystems.co.jp> wrote:
* Is there any use case for changing the handler or validator function
of an existign FDW with ALTER? To me it just seems like an unnecessary
complication.AFAICS, the only case for that is upgrading FDW to new one without
re-creating foreign tables. I don't have strong opinion for this
issue, and it seems reasonable to remove ALTER feature in first
version.
-1. I don't think that removing the ability to change this is going
to save a measurable amount of complexity, and it certainly will suck
if you need it and don't have it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, 31 Jan 2011 22:00:55 +0900
Shigeru HANADA <hanada@metrosystems.co.jp> wrote:
I'll post FDW API patches which reflect comments first, and then I'll
rebase postgresql_fdw against them.
Sorry for late, attached are revised version of FDW API patches which
reflect Heikki's comments except removing catalog lookup via
IsForeignTable(). ISTM that the point is avoiding catalog lookup
during planning, but I have not found when we can set "foreign table
flag" without catalog lookup during RelOptInfo generation.
Please apply attached patches in this order.
1) fdw_catalog_lookup.patch
2) fdw_handler.patch
3) foreign_scan.patch
To execute SELECT quereis for foreign tables, you need a FDW which has
valid fdwhandler function. The file_fdw which is posted in another
thread "SQL/MED file_fdw" would help.
Changes from last patches are:
1) Now SELECT FOR UPDATE check for foreign tables are done properly in
executor phase, in ExecLockTuple(). Or such check should be done in
parser or planner?
2) Server version is checked in pg_dump (>= 90100).
3) ReScan is not required now. If ReScan is not supplied, ForeignScan
uses EndScan + BeginSacn instead.
4) FDW-Info in EXPLAIN is shown always, except FDW set NULL to
explainInfo.
Regards,
--
Shigeru Hanada
On 07.02.2011 08:00, Shigeru HANADA wrote:
Sorry for late, attached are revised version of FDW API patches which
reflect Heikki's comments except removing catalog lookup via
IsForeignTable(). ISTM that the point is avoiding catalog lookup
during planning, but I have not found when we can set "foreign table
flag" without catalog lookup during RelOptInfo generation.
In get_relation_info(), you do the catalog lookup anyway and have the
Relation object at hand. Add a flag to RelOptInfo indicating if it's a
foreign table or not, and set that in get_relation_info().
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Mon, 07 Feb 2011 09:37:37 +0100
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> wrote:
On 07.02.2011 08:00, Shigeru HANADA wrote:
Sorry for late, attached are revised version of FDW API patches which
reflect Heikki's comments except removing catalog lookup via
IsForeignTable(). ISTM that the point is avoiding catalog lookup
during planning, but I have not found when we can set "foreign table
flag" without catalog lookup during RelOptInfo generation.In get_relation_info(), you do the catalog lookup anyway and have the
Relation object at hand. Add a flag to RelOptInfo indicating if it's a
foreign table or not, and set that in get_relation_info().
Thanks a lot.
Attached is a revised version of foreign_scan patch. This still
requires fdw_handler patch which was attached to the orginal post.
Avoid_catalog_lookup.patch is attached for review purpose.
This patch includes changes for this fix.
Regards,
--
Shigeru Hanada
Attachments:
avoid_catalog_lookup.patchapplication/octet-stream; name=avoid_catalog_lookup.patchDownload
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 1fdd925..2e9f3f1 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -362,30 +362,6 @@ GetFdwRoutineByRelId(Oid relid)
/*
- * Determine the relation is a foreign table.
- */
-bool
-IsForeignTable(Oid relid)
-{
- HeapTuple tuple;
- Form_pg_class classForm;
- char relkind;
-
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation with OID %u does not exist", relid)));
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- relkind = classForm->relkind;
- ReleaseSysCache(tuple);
-
- return (relkind == RELKIND_FOREIGN_TABLE);
-}
-
-
-
-/*
* deflist_to_tuplestore - Helper function to convert DefElem list to
* tuplestore usable in SRF.
*/
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6fc6825..b8e4c1f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1676,6 +1676,7 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
WRITE_NODE_FIELD(subplan);
WRITE_NODE_FIELD(subrtable);
WRITE_NODE_FIELD(subrowmark);
+ WRITE_BOOL_FIELD(is_foreign_table);
WRITE_NODE_FIELD(baserestrictinfo);
WRITE_NODE_FIELD(joininfo);
WRITE_BOOL_FIELD(has_eclass_joins);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index ae2e134..780e64c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -256,7 +256,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
* least one dimension of cost or sortedness.
*/
- if (IsForeignTable(rte->relid))
+ if (rel->is_foreign_table)
{
/* only foreign scan path is applyable to foreign table */
add_path(rel, create_foreignscan_path(root, rel));
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 1d60af8..f86bc5f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -339,6 +339,13 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
rel->indexlist = indexinfos;
+ /*
+ * We should remember whether the relation is a foreign table or not to
+ * avoid catalog lookup in the phase of path generation.
+ */
+ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ rel->is_foreign_table = true;
+
heap_close(relation, NoLock);
/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index b7a5845..7c2a39d 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -85,6 +85,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->subplan = NULL;
rel->subrtable = NIL;
rel->subrowmark = NIL;
+ rel->is_foreign_table = false;
rel->baserestrictinfo = NIL;
rel->baserestrictcost.startup = 0;
rel->baserestrictcost.per_tuple = 0;
@@ -339,6 +340,7 @@ build_join_rel(PlannerInfo *root,
joinrel->subplan = NULL;
joinrel->subrtable = NIL;
joinrel->subrowmark = NIL;
+ joinrel->is_foreign_table = false;
joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0;
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index fe299b3..b0cb37c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -79,6 +79,5 @@ extern Oid GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
extern ForeignTable *GetForeignTable(Oid relid);
extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
-extern bool IsForeignTable(Oid relid);
#endif /* FOREIGN_H */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 133a2bd..5abbb07 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -395,6 +395,7 @@ typedef struct RelOptInfo
struct Plan *subplan; /* if subquery */
List *subrtable; /* if subquery */
List *subrowmark; /* if subquery */
+ bool is_foreign_table; /* T means relation is a foreign table */
/* used by various scans and joins: */
List *baserestrictinfo; /* RestrictInfo structures (if base
On 08.02.2011 13:07, Shigeru HANADA wrote:
On Mon, 07 Feb 2011 09:37:37 +0100
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> wrote:In get_relation_info(), you do the catalog lookup anyway and have the
Relation object at hand. Add a flag to RelOptInfo indicating if it's a
foreign table or not, and set that in get_relation_info().Thanks a lot.
Attached is a revised version of foreign_scan patch. This still
requires fdw_handler patch which was attached to the orginal post.Avoid_catalog_lookup.patch is attached for review purpose.
This patch includes changes for this fix.
Thanks.
I spent some more time reviewing this, and working on the PostgreSQL FDW
in tandem. Here's an updated API patch, with a bunch of cosmetic
changes, and a bug fix for FOR SHARE/UPDATE. FOR SHARE/UPDATE on a
foreign table should behave the same as on other relations that can't be
locked, like a function scan. If you explicitly specify such a relation
with "FOR UPDATE OF foo", you should get an error, and if you just have
an unspecified "FOR UPDATE", it should be silently ignored for foreign
tables.
Doing that requires that we can distinguish foreign tables from other
relations in transformLockingClause(), but we don't have a RelOptInfo at
that stage yet. I just used get_rel_relkind() there (and elsewhere
instead of the IsForeignTable() function), but I've got a nagging
feeling that sooner or later we'll have to bite the bullet and add that
field to RangeTblEntry, or introduce a whole new rtekind for foreign tables.
As for the PostgreSQL FDW, I'm trying to make it do two basic tricks, to
validate the API:
1. Only fetch those columns that are actually needed by the query. This
involves examining the baserel->reltargetlist, also paying attention to
whole-row Vars.
2. Push down simple quals, like "column = 'foo'". To do that, I'm trying
to use the deparsing code from ruleutils.c.
That's pretty much what Shigeru's original postgresql_fdw patch also
did, but I've changed the implementation quite heavily to make it work
with the new API. That said, it's still a mess, but I think it validates
that the API is usable. We could offer a lot more help for FDW authors
to make those things easier, but I think this is acceptable for now.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
fdw-api-1.patchtext/x-diff; name=fdw-api-1.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index be132f2..f4cb885 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2819,6 +2819,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..8ef93bc
--- /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.
+ </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..3f003c8 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 3a0ea9a..fc1d9b4 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -317,16 +317,69 @@ 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 (pg_strcasecmp(def->defname, "validator") == 0)
+ {
+ if (*validator)
+ ereport(ERROR, (errmsg("duplicated VALIDATOR")));
+ *validator = def;
+ }
+ else if (pg_strcasecmp(def->defname, "handler") == 0)
+ {
+ if (*handler)
+ ereport(ERROR, (errmsg("duplicated HANDLER")));
+ *handler = def;
+ }
+ else
+ {
+ ereport(ERROR, (errmsg("invalid option")));
+ }
+ }
+}
/*
* Create a foreign-data wrapper
@@ -339,7 +392,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;
@@ -375,12 +431,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;
+ /* determine which validator to be used (or not used at all) */
+ 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 +473,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);
/* Post creation hook for new foreign data wrapper */
@@ -440,7 +512,10 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
Oid fdwId;
bool isnull;
Datum datum;
+ DefElem *defvalidator;
+ DefElem *defhandler;
Oid fdwvalidator;
+ Oid fdwhandler;
/* Must be super user */
if (!superuser())
@@ -464,9 +539,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;
@@ -474,7 +551,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")));
@@ -492,6 +569,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..92b83ac 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -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
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 662916d..2c5bb10 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -548,6 +548,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
@@ -3202,7 +3240,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;
@@ -3214,8 +3252,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;
@@ -3760,6 +3797,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 b7dc450..26df9ac 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1629,7 +1629,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;
@@ -1639,8 +1639,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 c8eccce..30dc6da 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -533,6 +533,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");
@@ -1476,6 +1497,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");
@@ -2618,6 +2647,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;
@@ -2820,6 +2855,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..9a7bc4b 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)
+ {
+ /* 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 +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 c74125f..1ed7255 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -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;
+}
+
/*****************************************************************************
*
@@ -2253,10 +2304,10 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
return NULL;
if (IsA(node, Var))
{
- Var *var = (Var *) node;
- Param *param;
+ Var *var = (Var *) node;
+ Param *param;
NestLoopParam *nlp;
- ListCell *lc;
+ ListCell *lc;
/* Upper-level Vars should be long gone at this point */
Assert(var->varlevelsup == 0);
@@ -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)
{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b52eebe..2004057 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1911,7 +1911,7 @@ preprocess_rowmarks(PlannerInfo *root)
newrc = makeNode(PlanRowMark);
newrc->rti = newrc->prti = i;
/* 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 02f5cab..ef2833a 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -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:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index febec1e..54846da 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -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;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d1010af..658e856 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,45 @@ 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,
+ (errmsg("foreign-data wrapper must support PlanRelScan to scan foreign table")));
+
+ ftable = GetForeignTable(rte->relid);
+
+ /* Let the FDW do its planning */
+ pathnode->fplan = routine->PlanRelScan(rte->relid, root, rel);
+ if (pathnode->fplan == NULL)
+ elog(ERROR, "PlanRelScan returned NULL");
+ 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 adbe45c..1d60af8 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;
@@ -460,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;
@@ -759,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/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 070e4c1..32bd5f7 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"
@@ -2163,9 +2164,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,
@@ -2212,6 +2217,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 2178282..9e0c148 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -306,6 +306,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_func_options func_options
+%type <defelt> fdw_option
+
%type <range> OptTempTableName
%type <into> into_clause create_as_target
@@ -3196,16 +3199,33 @@ DropTableSpaceStmt: DROP TABLESPACE name
*
*****************************************************************************/
-CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_validator create_generic_options
+CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_func_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); }
+ ;
+
+func_options:
+ fdw_option { $$ = list_make1($1); }
+ | func_options fdw_option { $$ = lappend($1, $2); }
+ ;
+
+opt_func_options:
+ func_options { $$ = $1; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
/*****************************************************************************
*
* QUERY :
@@ -3238,28 +3258,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_func_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 func_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 c12d4cb..c72db50 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1390,8 +1390,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 d3eb766..8baf79f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5985,6 +5985,7 @@ getForeignDataWrappers(int *numForeignDataWrappers)
int i_fdwname;
int i_rolname;
int i_fdwvalidator;
+ int i_fdwhandler;
int i_fdwacl;
int i_fdwoptions;
@@ -5998,13 +5999,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);
@@ -6019,6 +6034,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");
@@ -6032,6 +6049,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));
@@ -10337,6 +10358,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 43fd1ad..c630a1d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -421,6 +421,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 ada04de..57718eb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3480,9 +3480,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 f8b5d4d..05fc57c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3899,6 +3899,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 620f7b4..ee40705 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -623,6 +623,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 _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 _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 546b581..7d1c681 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"
@@ -1402,6 +1404,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 456e8e2..5b065b5 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,
@@ -216,6 +219,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 483f225..2db3c48 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1531,7 +1531,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;
@@ -1539,8 +1539,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 011c686..fcfd203 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"
@@ -434,6 +435,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 8b14838..133a2bd 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -745,6 +745,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 4cb6c5c..eaaf7d0 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -518,6 +518,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;
postgres_fdw-2.patchtext/x-diff; name=postgres_fdw-2.patchDownload
diff --git a/contrib/Makefile b/contrib/Makefile
index 2b31450..3eab73d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -41,6 +41,7 @@ SUBDIRS = \
pgcrypto \
pgrowlocks \
pgstattuple \
+ postgresql_fdw \
seg \
spi \
tablefunc \
diff --git a/contrib/README b/contrib/README
index 3c4e324..ccee6a4 100644
--- a/contrib/README
+++ b/contrib/README
@@ -159,6 +159,9 @@ pgstattuple -
space within a table
by Tatsuo Ishii <ishii@sraoss.co.jp>
+postgresql_fdw -
+ Foreign-data wrapper for external PostgreSQL servers
+
seg -
Confidence-interval datatype (GiST indexing example)
by Gene Selkov, Jr. <selkovjr@mcs.anl.gov>
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 19b98fb..b1509dc 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2309,6 +2309,9 @@ get_connect_string(const char *servername)
{
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
@@ -2317,6 +2320,9 @@ get_connect_string(const char *servername)
{
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
@@ -2326,6 +2332,9 @@ get_connect_string(const char *servername)
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
diff --git a/contrib/postgresql_fdw/.gitignore b/contrib/postgresql_fdw/.gitignore
new file mode 100644
index 0000000..4a8bf17
--- /dev/null
+++ b/contrib/postgresql_fdw/.gitignore
@@ -0,0 +1,3 @@
+/postgresql_fdw.sql
+# Generated subdirectories
+/results/
diff --git a/contrib/postgresql_fdw/Makefile b/contrib/postgresql_fdw/Makefile
new file mode 100644
index 0000000..b9cf9ec
--- /dev/null
+++ b/contrib/postgresql_fdw/Makefile
@@ -0,0 +1,22 @@
+# contrib/postgresql_fdw/Makefile
+
+MODULE_big = postgresql_fdw
+PG_CPPFLAGS = -I$(libpq_srcdir)
+OBJS = postgresql_fdw.o
+SHLIB_LINK = $(libpq)
+
+DATA_built = postgresql_fdw.sql
+DATA = uninstall_postgresql_fdw.sql
+REGRESS = postgresql_fdw
+
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/postgresql_fdw
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/postgresql_fdw/expected/postgresql_fdw.out b/contrib/postgresql_fdw/expected/postgresql_fdw.out
new file mode 100644
index 0000000..bcc6a3c
--- /dev/null
+++ b/contrib/postgresql_fdw/expected/postgresql_fdw.out
@@ -0,0 +1,116 @@
+SET SEARCH_PATH = public;
+SET DATESTYLE = 'Postgres, MDY';
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+-- connect database for regression test
+\c contrib_regression
+-- install postgresql_fdw module
+SET client_min_messages = warning;
+\set ECHO none
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+CREATE TABLE t1(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+COPY t1 FROM stdin;
+CREATE TABLE t2(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+COPY t2 FROM stdin;
+CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS (colname 'invalid'),
+ c2 text OPTIONS (colname 'C2'),
+ c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (SET colname 'C1');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c2 OPTIONS (DROP colname);
+CREATE FOREIGN TABLE ft2 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970
+ 3 | buz | 01-03-1970
+(3 rows)
+
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR: could not connect to server "loopback2"
+DETAIL: FATAL: role "invalid" does not exist
+
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR: could not execute foreign query
+DETAIL: ERROR: relation "public.invalid" does not exist
+LINE 1: SELECT c1, c2, c3 FROM public.invalid ft2
+ ^
+
+HINT: SELECT c1, c2, c3 FROM public.invalid ft2
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+ 12 | bar | 01-02-1970
+ 13 | buz | 01-03-1970
+(3 rows)
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+(1 row)
+
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970 | 2 | bar | 01-02-1970
+ 3 | buz | 01-03-1970 | 3 | buz | 01-03-1970
+(3 rows)
+
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970 | | |
+ 3 | buz | 01-03-1970 | | |
+(3 rows)
+
+-- WHERE clause push-down
+set client_min_messages = debug1;
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now();
+DEBUG: deparsed SQL is "SELECT C1, c2, c3 FROM public.t1 ft1 WHERE ((c1 = 1) AND (c2 = 'foo'::text))"
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+(1 row)
+
+reset client_min_messages;
+-- clean up
+DROP FOREIGN DATA WRAPPER postgresql_fdw CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to server loopback1
+drop cascades to user mapping for public
+drop cascades to foreign table ft1
+drop cascades to server loopback2
+drop cascades to user mapping for public
+drop cascades to foreign table ft2
+DROP TABLE t1 CASCADE;
diff --git a/contrib/postgresql_fdw/postgresql_fdw.c b/contrib/postgresql_fdw/postgresql_fdw.c
new file mode 100644
index 0000000..4522d31
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.c
@@ -0,0 +1,972 @@
+/*-------------------------------------------------------------------------
+ *
+ * postgresql_fdw.c
+ * foreign-data wrapper for PostgreSQL
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgresql_fdw/postgresql_fdw.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "libpq-fe.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "parser/parsetree.h"
+#include "parser/scansup.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/syscache.h"
+
+#include "postgresql_fdw.h"
+
+PG_MODULE_MAGIC;
+
+
+/*
+ * PostgreSQL specific portion of a foreign query request
+ */
+typedef struct pgFdwExecutionState
+{
+ PGconn *conn;
+ PGresult *res;
+ List *colmap;
+ int nextrow;
+} pgFdwExecutionState;
+
+extern Datum postgresql_fdw_handler(PG_FUNCTION_ARGS);
+
+/*
+ * FDW routines
+ */
+static FdwPlan *pgPlanRelScan(Oid foreigntableid, PlannerInfo *root,
+ RelOptInfo *baserel);
+static FdwExecutionState *pgBeginScan(FdwPlan *plan, ParamListInfo params);
+static void pgIterate(FdwExecutionState *state, TupleTableSlot *slot);
+static void pgReScan(FdwExecutionState *state);
+static void pgEndScan(FdwExecutionState *state);
+
+/* helper for deparsing a request into SQL statement */
+
+static bool is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+static char *deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel, List **colmap);
+
+static void storeResult(pgFdwExecutionState *priv, TupleTableSlot *slot, int rowno);
+
+/*
+ * Connection management
+ */
+static PGconn *GetConnection(ForeignServer *server, UserMapping *user);
+static void ReleaseConnection(PGconn *conn);
+static void check_conn_params(const char **keywords, const char **values);
+static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
+static void cleanup_connection(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg);
+
+/*
+ * FdwRoutine of PostgreSQL wrapper
+ */
+static FdwRoutine postgresql_fdw_routine =
+{
+ pgPlanRelScan,
+ pgBeginScan,
+ pgIterate,
+ pgReScan,
+ pgEndScan
+};
+
+/*
+ * return foreign-data wrapper handler object to execute foreign-data wrapper
+ * routines.
+ */
+PG_FUNCTION_INFO_V1(postgresql_fdw_handler);
+Datum
+postgresql_fdw_handler(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_POINTER(&postgresql_fdw_routine);
+}
+
+static FdwPlan *
+pgPlanRelScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
+{
+ FdwPlan *plan = makeNode(FdwPlan);
+ char *sql;
+ List *colmap;
+
+ sql = deparseSql(foreigntableid, root, baserel, &colmap);
+
+ plan->explainInfo = sql;
+ plan->fdw_private = list_make2(makeString(sql), colmap);
+
+ return plan;
+}
+
+typedef struct
+{
+ PlannerInfo *root;
+ RelOptInfo *foreignrel;
+} remotely_executable_cxt;
+
+static bool
+is_proc_remotely_executable(Oid procid)
+{
+ /* assume that only built-in functions can be pushed down */
+ if (get_func_namespace(procid) != PG_CATALOG_NAMESPACE)
+ return false;
+ /* we don't check volatility here, that's done once at the top-level */
+
+ return true;
+}
+
+static bool
+is_not_remotely_executable_walker(Node *node, remotely_executable_cxt *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ return true;
+
+ switch (nodeTag(node))
+ {
+ case T_Query:
+ /* give up on subqueries */
+ return true;
+ case T_Param:
+ /* TODO: pass internal parameters to the foreign server */
+ {
+ ParamKind paramkind = ((Param *) node)->paramkind;
+ elog(DEBUG1, "%s() param=%s", __FUNCTION__,
+ paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
+ paramkind == PARAM_EXEC ? "PARAM_EXEC" :
+ paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
+ }
+ if (((Param *) node)->paramkind != PARAM_EXTERN)
+ return true;
+ break;
+ case T_OpExpr:
+ {
+ OpExpr *op = (OpExpr *) node;
+ if (!is_proc_remotely_executable(op->opfuncid))
+ return true;
+ else
+ return false;
+ }
+ case T_FuncExpr:
+ {
+ FuncExpr *fe = (FuncExpr *) node;
+ if (!is_proc_remotely_executable(fe->funcid))
+ return true;
+ else
+ return false;
+ }
+
+ case T_TargetEntry:
+ case T_PlaceHolderVar:
+ case T_AppendRelInfo:
+ case T_PlaceHolderInfo:
+ /* TODO: research whether those complex nodes are evaluatable. */
+ return true;
+ case T_Var:
+ {
+ Var *var = (Var *) node;
+ if (var->varno != context->foreignrel->relid || var->varlevelsup != 0)
+ return true;
+ else
+ return false;
+ }
+
+ default:
+ break;
+ }
+
+ return expression_tree_walker(node, is_not_remotely_executable_walker,
+ context);
+}
+
+
+/*
+ * Check whether the ExprState node can be evaluated in foreign server.
+ *
+ * An expression which consists of expressions below can be evaluated in
+ * the foreign server.
+ * - constant value
+ * - variable (foreign table column)
+ * - external parameter (parameter of prepared statement)
+ * - array
+ * - bool expression (AND/OR/NOT)
+ * - NULL test (IS [NOT] NULL)
+ * - operator
+ * - IMMUTABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - function
+ * - IMMUTABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - scalar array operator (ANY/ALL)
+ */
+static bool
+is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+{
+ remotely_executable_cxt context;
+
+ context.root = root;
+ context.foreignrel = baserel;
+ if (is_not_remotely_executable_walker((Node *) expr, &context))
+ return false;
+ if (contain_volatile_functions((Node *) expr))
+ return false;
+
+ return true;
+}
+
+/*
+ * Deparse query request into SQL statement.
+ *
+ * If an expression in PlanState.qual list satisfies is_foreign_qual(), the
+ * expression is:
+ * - deparsed into WHERE clause of remote SQL statement to evaluate that
+ * expression on remote side
+ * - removed from PlanState.qual list to avoid duplicate evaluation, on
+ * remote side and local side
+ */
+static char *
+deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel,
+ List **colmap)
+{
+ List *context;
+ StringInfoData sql;
+ ForeignTable *table = GetForeignTable(foreigntableid);
+ ListCell *lc;
+ char *nspname = NULL;
+ char *relname = NULL;
+ bool *requiredatts;
+ RangeTblEntry *rte;
+ AttrNumber attno;
+
+ /* extract ForeignScan and RangeTblEntry */
+
+ /* prepare to deparse plan */
+ initStringInfo(&sql);
+
+ /*
+ * deparse SELECT target list. We only fetch the necessary columns from
+ * the remote server, but the result tuple needs to include all columns.
+ * So we need a mapping of the columns actually fetched to the full
+ * row type of the relation. Build that map here too.
+ */
+ *colmap = NIL;
+ appendStringInfoString(&sql, "SELECT ");
+ requiredatts = palloc0((baserel->max_attr + 1) * sizeof(bool));
+ foreach (lc, baserel->reltargetlist)
+ {
+ Var *var = lfirst(lc);
+
+ if (var->varlevelsup != 0)
+ elog(ERROR, "unexpected varlevelsup: %d", var->varlevelsup);
+ if (var->varno != baserel->relid)
+ elog(ERROR, "reference to another relation in reltargetlist");
+ if (var->varattno < 0)
+ elog(ERROR, "fetching remote system attributes is not supported");
+ if (var->varattno > baserel->max_attr)
+ elog(ERROR, "unexpected attribute number %d", var->varattno);
+
+ requiredatts[var->varattno] = true;
+ }
+
+ /* whole row */
+ if (requiredatts[0] == true)
+ MemSet(requiredatts, true, baserel->max_attr + 1);
+
+ rte = rt_fetch(baserel->relid, root->parse->rtable);
+ for (attno = 1; attno <= baserel->max_attr; attno++)
+ {
+ if (requiredatts[attno])
+ {
+ char *attname;
+
+ if (*colmap != NIL)
+ appendStringInfoString(&sql, ", ");
+
+ attname = get_rte_attribute_name(rte, attno);
+ appendStringInfoString(&sql, quote_identifier(attname));
+
+ *colmap = lappend_int(*colmap, attno);
+ }
+ }
+
+ /*
+ * Deparse FROM
+ *
+ * If the foreign table has generic option "nspname" and/or "relname", use
+ * them in the foreign query. Otherwise, use local catalog names.
+ */
+ foreach(lc, table->options)
+ {
+ DefElem *opt = lfirst(lc);
+ if (strcmp(opt->defname, "nspname") == 0)
+ nspname = pstrdup(strVal(opt->arg));
+ else if (strcmp(opt->defname, "relname") == 0)
+ relname = pstrdup(strVal(opt->arg));
+ }
+ if (nspname == NULL)
+ nspname = get_namespace_name(get_rel_namespace(foreigntableid));
+ if (relname == NULL)
+ relname = get_rel_name(foreigntableid);
+ appendStringInfo(&sql, " FROM %s.%s",
+ quote_identifier(nspname),
+ quote_identifier(relname));
+
+
+ /*
+ * deparse WHERE cluase
+ *
+ * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
+ * clause of result SQL string, and they could be removed from qual of
+ * PlanState to avoid duplicate evaluation at ExecScan().
+ *
+ * We never change the qual in the Plan node which was made by PREPARE
+ * statement to make following EXECUTE statements work properly. The Plan
+ * node is used repeatedly to create PlanState for each EXECUTE statement.
+ */
+ if (baserel->baserestrictinfo)
+ {
+ List *local_qual = NIL;
+ List *foreign_expr = NIL;
+ ListCell *lc;
+
+ /*
+ * Divide qual of PlanState into two lists, one for local evaluation
+ * and one for foreign evaluation.
+ */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (is_foreign_qual(root, baserel, ri->clause))
+ {
+ /* XXX: deparse and add to sql here */
+ foreign_expr = lappend(foreign_expr, ri->clause);
+ }
+ else
+ local_qual = lappend(local_qual, ri);
+ }
+ /*
+ * XXX: If the remote side is not reliable enough, we can keep the qual
+ * in PlanState as is and evaluate them on local side too. If so, just
+ * omit replacement below.
+ */
+ baserel->baserestrictinfo = local_qual;
+
+ /*
+ * Deparse quals to be evaluated in the foreign server if any.
+ * TODO: modify deparse_expression() to deparse conditions which use
+ * internal parameters.
+ */
+ if (foreign_expr != NIL)
+ {
+ Node *node;
+
+ context = deparse_context_for("foreigntable", foreigntableid);
+
+ node = (Node *) make_ands_explicit(foreign_expr);
+ node = copyObject(node);
+
+ /*
+ * XXX: deparse_context_for creates a context where the relation
+ * is relation no 1 in the range table. But the quals we have
+ * use something else to refer to this foreign table. Make a
+ * temporary copy of the quals and adjust varnos to 1.
+ */
+ OffsetVarNodes(node, 1 - baserel->relid, 0);
+ appendStringInfo(&sql, " WHERE %s",
+ deparse_expression(node, context, false, false));
+ /*
+ * The contents of the list MUST NOT be free-ed because they are
+ * referenced from Plan.qual list.
+ */
+ list_free(foreign_expr);
+ }
+ }
+
+ elog(DEBUG1, "deparsed SQL is \"%s\"", sql.data);
+
+ return sql.data;
+}
+
+/*
+ * Initiate actual scan on a foreign table.
+ * This function is called just after pgOpen() if the ForeignScan was executed
+ * for a real query or EXPLAIN statement with ANALYZE option.
+ */
+static FdwExecutionState *
+pgBeginScan(FdwPlan *plan, ParamListInfo params)
+{
+ FdwExecutionState *result = palloc(sizeof(FdwExecutionState));
+ pgFdwExecutionState *state;
+ PGconn *conn;
+ PGresult *res;
+ int numParams = params ? params->numParams : 0;
+ Oid *types = NULL;
+ const char **values = NULL;
+ const char *sql = strVal(linitial((List *) plan->fdw_private));
+ List *colmap = lsecond((List *) plan->fdw_private);
+
+ state = palloc(sizeof(pgFdwExecutionState));
+ state->colmap = colmap;
+
+ conn = GetConnection(GetForeignServer(plan->serverid),
+ GetUserMapping(plan->userid, plan->serverid));
+ state->conn = conn;
+
+ result->fdw_private = state;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ /* construct parameter array in text format */
+ /* TODO: omit unused parameter */
+ if (numParams > 0)
+ {
+ int i;
+
+ types = palloc0(sizeof(Oid) * numParams);
+ values = palloc0(sizeof(char *) * numParams);
+ for (i = 0; i < numParams; i++)
+ {
+ types[i] = params->params[i].ptype;
+ if (params->params[i].isnull)
+ values[i] = NULL;
+ else
+ {
+ Oid out_func_oid;
+ bool isvarlena;
+ FmgrInfo func;
+
+ /* TODO: cache FmgrInfo to use it again after pgReOpen() */
+ /* TODO: send parameters in binary format rather than text */
+ getTypeOutputInfo(types[i], &out_func_oid, &isvarlena);
+ fmgr_info(out_func_oid, &func);
+ values[i] =
+ OutputFunctionCall(&func, params->params[i].value);
+ }
+ }
+ }
+
+ /*
+ * Execute query with the parameters.
+ * TODO: support internal parameters(PARAM_EXTERN)
+ * TODO: support cursor mode for huge result sets.
+ */
+ res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
+ if (numParams > 0)
+ {
+ int i;
+ pfree(types);
+ for (i = 0; i < numParams; i++)
+ pfree((char *) values[i]);
+ pfree(values);
+ }
+
+ /*
+ * If the query has failed, reporting details is enough here.
+ * Connections which are used by this query (including other scans) will
+ * be cleaned up by the foreign connection manager.
+ */
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ char *msg;
+
+ msg = pstrdup(PQerrorMessage(conn));
+ PQclear(res);
+ ereport(ERROR,
+ (errmsg("could not execute foreign query"),
+ errdetail("%s", msg),
+ errhint("%s", sql)));
+ }
+
+ state->res = res;
+ state->nextrow = 0;
+
+ return result;
+}
+
+/*
+ * return tuples one by one.
+ * - execute SQL statement which was deparsed in pgOpen()
+ *
+ * The all of result are fetched at once when pgIterate() is called first after
+ * pgOpen() or pgReOpen().
+ * pgIterate() moves the next tuple from tupstore to TupleTableSlot in
+ * ScanState.
+ */
+static void
+pgIterate(FdwExecutionState *state, TupleTableSlot *slot)
+{
+ pgFdwExecutionState *priv = state->fdw_private;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ if (priv->nextrow == PQntuples(priv->res))
+ ExecClearTuple(slot);
+ else
+ storeResult(priv, slot, priv->nextrow++);
+}
+
+static void
+pgReScan(FdwExecutionState *state)
+{
+ ((pgFdwExecutionState *)state->fdw_private)->nextrow = 0;
+}
+
+static void
+pgEndScan(FdwExecutionState *state)
+{
+ pgFdwExecutionState *priv = (pgFdwExecutionState *) state->fdw_private;
+
+ PQclear(priv->res);
+ pfree(priv);
+}
+
+/*
+ * Store a PGresult into tuplestore.
+ */
+static void
+storeResult(pgFdwExecutionState *priv, TupleTableSlot *slot, int rowno)
+{
+ int nfields;
+ char **values;
+ AttInMetadata *attinmeta;
+ Form_pg_attribute *attrs;
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ HeapTuple tuple;
+ ListCell *lc;
+ int i;
+
+ nfields = PQnfields(priv->res);
+ attrs = tupdesc->attrs;
+
+ /* buffer should include dropped columns */
+ values = palloc0(sizeof(char *) * tupdesc->natts);
+
+ /* store tuple in the slot */
+ attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ i = 0;
+ foreach(lc, priv->colmap)
+ {
+ AttrNumber attno = lfirst_int(lc);
+
+ /* check result and tuple descriptor have the same number of columns */
+ if (attno < 1 || attno > tupdesc->natts)
+ elog(ERROR, "invalid attribute number %d", attno);
+
+ /* skip dropped columns. */
+ if (attrs[attno - 1]->attisdropped ||
+ PQgetisnull(priv->res, rowno, i))
+ {
+ values[attno - 1] = NULL;
+ }
+ else
+ values[attno - 1] = PQgetvalue(priv->res, rowno, i);
+
+ i++;
+ }
+
+ /* build the tuple and put it into the tuplestore. */
+ tuple = BuildTupleFromCStrings(attinmeta, values);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false /* no need to pfree because we run in the per-tuple context */);
+
+ pfree(values);
+}
+
+#ifdef NOT_USED
+/*
+ * Retrieve cost-factors of the foreign server from catalog.
+ */
+static void
+get_server_costs(Oid relid, double *connection_cost, double *transfer_cost)
+{
+ ForeignTable *table;
+ ForeignServer *server;
+ int n;
+ const char **keywords;
+ const char **values;
+ int i;
+
+ /*
+ * Retrieve generic options from the target table and its server to correct
+ * costs.
+ */
+ table = GetForeignTable(relid);
+ server = GetForeignServer(table->serverid);
+ n = list_length(table->options) + list_length(server->options) + 1;
+ keywords = (const char **) palloc(sizeof(char *) * n);
+ values = (const char **) palloc(sizeof(char *) * n);
+ n = 0;
+ n += flatten_generic_options(server->options, keywords + n, values + n);
+ n += flatten_generic_options(table->options, keywords + n, values + n);
+ keywords[n] = values[n] = NULL;
+
+ for (i = 0; keywords[i]; i++)
+ {
+ if (pg_strcasecmp(keywords[i], "connection_cost") == 0)
+ *connection_cost = strtod(values[i], NULL);
+ else if (pg_strcasecmp(keywords[i], "transfer_cost") == 0)
+ *transfer_cost = strtod(values[i], NULL);
+ }
+
+ pfree(keywords);
+ pfree(values);
+}
+#endif
+
+/*
+ * Estimate costs of scanning on a foreign table.
+ *
+ * baserel->baserestrictinfo can be used to examine quals on the relation.
+ */
+static void
+pgEstimateCosts(ForeignPath *path, PlannerInfo *root, RelOptInfo *baserel)
+{
+ RangeTblEntry *rte;
+ double connection_cost = 0.0;
+ double transfer_cost = 0.0;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ /*
+ * Use cost_seqscan() to get approximate value.
+ */
+ cost_seqscan(&path->path, root, baserel);
+
+ /* Get cost factor from catalog to correct costs. */
+ rte = planner_rt_fetch(baserel->relid, root);
+#ifdef NOT_USED
+ get_server_costs(rte->relid, &connection_cost, &transfer_cost);
+#endif
+ /* XXX arbitrary guesses */
+ connection_cost = 10.0;
+ transfer_cost = 1.0;
+ path->path.startup_cost += connection_cost;
+ path->path.total_cost += connection_cost;
+ path->path.total_cost += transfer_cost *
+ path->path.parent->width * path->path.parent->rows;
+}
+
+/* ============================================================================
+ * Connection management functions
+ * ==========================================================================*/
+
+/*
+ * Connection cache entry managed with hash table.
+ */
+typedef struct ConnCacheEntry
+{
+ /* hash key must be first */
+ char name[NAMEDATALEN]; /* connection name; used as hash key */
+ int refs; /* reference counter */
+ PGconn *conn; /* foreign server connection */
+} ConnCacheEntry;
+
+/*
+ * Hash table which is used to cache connection to PostgreSQL servers, will be
+ * initialized before first attempt to connect PostgreSQL server by the backend.
+ */
+static HTAB *FSConnectionHash;
+
+/*
+ * Get a PGconn which can be used to execute foreign query on the remote
+ * PostgreSQL server with the user's authorization. If this was the first
+ * request for the server, new connection is established.
+ */
+static PGconn *
+GetConnection(ForeignServer *server, UserMapping *user)
+{
+ const char *conname = server->servername;
+ bool found;
+ ConnCacheEntry *entry;
+ PGconn *conn = NULL;
+
+ /* initialize connection cache if it isn't */
+ if (FSConnectionHash == NULL)
+ {
+ HASHCTL ctl;
+
+ /* hash key is the name of the connection */
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = NAMEDATALEN;
+ ctl.entrysize = sizeof(ConnCacheEntry);
+ /* allocate FSConnectionHash in the cache context */
+ ctl.hcxt = CacheMemoryContext;
+ FSConnectionHash = hash_create("Foreign Connections", 32,
+ &ctl,
+ HASH_ELEM | HASH_CONTEXT);
+ }
+
+ /* Is there any cached and valid connection with such name? */
+ entry = hash_search(FSConnectionHash, conname, HASH_ENTER, &found);
+ if (found)
+ {
+ if (entry->conn != NULL)
+ {
+ entry->refs++;
+ elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+ return entry->conn;
+ }
+
+ /*
+ * Connection cache entry was found but connection in it is invalid.
+ * We reuse entry to store newly established connection later.
+ */
+ }
+ else
+ {
+ /*
+ * Use ResourceOner to clean the connection up on error including
+ * user interrupt.
+ */
+ entry->refs = 0;
+ entry->conn = NULL;
+ RegisterResourceReleaseCallback(cleanup_connection, entry);
+ }
+
+ /*
+ * Here we have to establish new connection.
+ * Use PG_TRY block to ensure closing connection on error.
+ */
+ PG_TRY();
+ {
+ /* Connect to the foreign PostgreSQL server */
+ conn = connect_pg_server(server, user);
+
+ /*
+ * Initialize the cache entry to keep new connection.
+ * Note: entry->name has been initialized in hash_search(HASH_ENTER).
+ */
+ entry->refs = 1;
+ entry->conn = conn;
+ elog(DEBUG3, "connected to %s (%d)", entry->name, entry->refs);
+ }
+ PG_CATCH();
+ {
+ PQfinish(conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return conn;
+}
+
+/*
+ * For non-superusers, insist that the connstr specify a password. This
+ * prevents a password from being picked up from .pgpass, a service file,
+ * the environment, etc. We don't want the postgres user's passwords
+ * to be accessible to non-superusers.
+ */
+static void
+check_conn_params(const char **keywords, const char **values)
+{
+ int i;
+
+ /* no check required if superuser */
+ if (superuser())
+ return;
+
+ /* ok if params contain a non-empty password */
+ for (i = 0; keywords[i] != NULL; i++)
+ {
+ if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0')
+ return;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superusers must provide a password in the connection string.")));
+}
+
+static int
+flatten_generic_options(List *defelems, const char **keywords, const char **values)
+{
+ ListCell *lc;
+ int i;
+
+ i = 0;
+ foreach(lc, defelems)
+ {
+ DefElem *d = (DefElem *) lfirst(lc);
+ keywords[i] = d->defname;
+ values[i] = strVal(d->arg);
+ i++;
+ }
+ return i;
+}
+
+static PGconn *
+connect_pg_server(ForeignServer *server, UserMapping *user)
+{
+ const char *conname = server->servername;
+ PGconn *conn;
+ const char **all_keywords;
+ const char **all_values;
+ const char **keywords;
+ const char **values;
+ int n;
+ int i, j;
+
+ /*
+ * Construct connection params from generic options of ForeignServer and
+ * UserMapping. Generic options might not be a one of connection options.
+ */
+ n = list_length(server->options) + list_length(user->options) + 1;
+ all_keywords = (const char **) palloc(sizeof(char *) * n);
+ all_values = (const char **) palloc(sizeof(char *) * n);
+ keywords = (const char **) palloc(sizeof(char *) * n);
+ values = (const char **) palloc(sizeof(char *) * n);
+ n = 0;
+ n += flatten_generic_options(server->options,
+ all_keywords + n, all_values + n);
+ n += flatten_generic_options(user->options,
+ all_keywords + n, all_values + n);
+ all_keywords[n] = all_values[n] = NULL;
+
+ for (i = 0, j = 0; all_keywords[i]; i++)
+ {
+ /* Use only libpq connection options. */
+ if (!is_libpq_connection_option(all_keywords[i]))
+ continue;
+ keywords[j] = all_keywords[i];
+ values[j] = all_values[i];
+ j++;
+ }
+ keywords[j] = values[j] = NULL;
+ pfree(all_keywords);
+ pfree(all_values);
+
+ /* verify connection parameters and do connect */
+ check_conn_params(keywords, values);
+ conn = PQconnectdbParams(keywords, values, 0);
+ if (!conn || PQstatus(conn) != CONNECTION_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
+ errmsg("could not connect to server \"%s\"", conname),
+ errdetail("%s", PQerrorMessage(conn))));
+ pfree(keywords);
+ pfree(values);
+
+ return conn;
+}
+
+/*
+ * Mark the connection as "unused", and close it if the caller was the last
+ * user of the connection.
+ */
+static void
+ReleaseConnection(PGconn *conn)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ if (conn == NULL)
+ return;
+
+ /*
+ * We need to scan seqencially since we use the address to find appropriate
+ * PGconn from the hash table.
+ */
+ hash_seq_init(&scan, FSConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ if (entry->conn == conn)
+ break;
+ }
+ hash_seq_term(&scan);
+
+ /*
+ * If the released connection was an orphan, just close it.
+ */
+ if (entry == NULL)
+ {
+ PQfinish(conn);
+ return;
+ }
+
+ /* If the caller was the last referer, unregister it from cache. */
+ entry->refs--;
+ elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+ if (entry->refs == 0)
+ {
+ elog(DEBUG3, "closing connection \"%s\"", entry->name);
+ PQfinish(entry->conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ }
+}
+
+/*
+ * Clean the connection up via ResourceOwner when pgClose couldn't close the
+ * connection gracefully.
+ */
+static void
+cleanup_connection(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg)
+{
+ ConnCacheEntry *entry = (ConnCacheEntry *) arg;
+
+ /*
+ * If the transaction was committed, the connection has been closed via
+ * pgClose() and ReleaseConnection().
+ */
+ if (isCommit)
+ return;
+
+ /*
+ * We clean the connection up on post-lock because foreign connections are
+ * backend-internal resource.
+ */
+ if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
+ return;
+
+ /*
+ * We ignore cleanup for ResourceOwners other than transaction. At this
+ * point, such a ResourceOwner is only Portal.
+ */
+ if (CurrentResourceOwner != CurTransactionResourceOwner)
+ return;
+
+ /*
+ * We don't care whether we are in TopTransaction or Subtransaction.
+ * Anyway, we close the connection and reset the reference counter.
+ */
+ if (entry->conn != NULL)
+ {
+ elog(DEBUG3, "closing connection to %s", entry->name);
+ PQfinish(entry->conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ }
+ else
+ elog(DEBUG3, "connection to %s already closed", entry->name);
+}
diff --git a/contrib/postgresql_fdw/postgresql_fdw.h b/contrib/postgresql_fdw/postgresql_fdw.h
new file mode 100644
index 0000000..0af367c
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.h
@@ -0,0 +1,18 @@
+/*
+ * postgresql_fdw.h
+ *
+ * Foreign-data wrapper handler for PostgreSQL
+ *
+ * contrib/postgresql_fdw/postgresql_fdw.h
+ * Copyright (c) 2010, PostgreSQL Global Development Group
+ * ALL RIGHTS RESERVED;
+ *
+ */
+
+#ifndef POSTGRESQL_FDW_H
+#define POSTGRESQL_FDW_H
+
+/* Connection name used for unnamed connection */
+#define UNNAMED_CONN_NAME "unnamed"
+
+#endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgresql_fdw/postgresql_fdw.sql.in b/contrib/postgresql_fdw/postgresql_fdw.sql.in
new file mode 100644
index 0000000..93d5926
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.sql.in
@@ -0,0 +1,14 @@
+/* contrib/postgresql/postgresql.sql.in */
+
+-- Adjust this setting to control where the objects get created.
+set search_path = public;
+
+CREATE OR REPLACE FUNCTION postgresql_fdw_handler ()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME','postgresql_fdw_handler'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgresql_fdw
+VALIDATOR postgresql_fdw_validator
+HANDLER postgresql_fdw_handler;
+
diff --git a/contrib/postgresql_fdw/sql/postgresql_fdw.sql b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
new file mode 100644
index 0000000..291c7b6
--- /dev/null
+++ b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
@@ -0,0 +1,84 @@
+SET SEARCH_PATH = public;
+SET DATESTYLE = 'Postgres, MDY';
+
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+-- connect database for regression test
+\c contrib_regression
+
+-- install postgresql_fdw module
+SET client_min_messages = warning;
+\set ECHO none
+\i postgresql_fdw.sql
+\set ECHO all
+
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+
+CREATE TABLE t1(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+
+COPY t1 FROM stdin;
+1 foo 1970-01-01
+2 bar 1970-01-02
+3 buz 1970-01-03
+\.
+
+CREATE TABLE t2(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+
+COPY t2 FROM stdin;
+1 foo 1970-01-01
+12 bar 1970-01-02
+13 buz 1970-01-03
+\.
+
+CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS (colname 'invalid'),
+ c2 text OPTIONS (colname 'C2'),
+ c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (SET colname 'C1');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c2 OPTIONS (DROP colname);
+
+CREATE FOREIGN TABLE ft2 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+-- WHERE clause push-down
+set client_min_messages = debug1;
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now();
+reset client_min_messages;
+
+-- clean up
+DROP FOREIGN DATA WRAPPER postgresql_fdw CASCADE;
+DROP TABLE t1 CASCADE;
diff --git a/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql b/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql
new file mode 100644
index 0000000..a24bbd4
--- /dev/null
+++ b/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql
@@ -0,0 +1,7 @@
+/* contrib/postgresql_fdw/uninstall_postgresql_fdw.sql */
+
+-- Adjust this setting to control where the objects get dropped.
+set search_path = public;
+
+DROP FOREIGN DATA WRAPPER postgresql_fdw;
+DROP FUNCTION postgresql_fdw_handler ();
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 75d08d5..d9bfc99 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -115,6 +115,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql
&pgtestfsync;
&pgtrgm;
&pgupgrade;
+ &postgresql-fdw;
&seg;
&sepgsql;
&contrib-spi;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 659bcba..657e348 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -128,6 +128,7 @@
<!entity pgtestfsync SYSTEM "pgtestfsync.sgml">
<!entity pgtrgm SYSTEM "pgtrgm.sgml">
<!entity pgupgrade SYSTEM "pgupgrade.sgml">
+<!entity postgresql-fdw SYSTEM "postgresql-fdw.sgml">
<!entity seg SYSTEM "seg.sgml">
<!entity contrib-spi SYSTEM "contrib-spi.sgml">
<!entity sepgsql SYSTEM "sepgsql.sgml">
diff --git a/doc/src/sgml/postgresql-fdw.sgml b/doc/src/sgml/postgresql-fdw.sgml
new file mode 100644
index 0000000..139ea9b
--- /dev/null
+++ b/doc/src/sgml/postgresql-fdw.sgml
@@ -0,0 +1,795 @@
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ PostgreSQL in type of <type>fdw_handler</type>.
+ Since fdw_hanlder is a pseudo type, postgresql_fdw_handler can't be
+ called from a SQL statement.
+ </para>
+ <para>
+ Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+ object which has set of foreign-data wrapper API functions for handling
+ foreign scan on the external PostgreSQL server. Functions other than
+ Iterate can be NULL if the foreign-data wrapper has nothing to do in the
+ function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Details of postgresql_fdw</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ The postgresql_fdw retrieves connection information from generic options of
+ user mapping and foriegn server. All of generic options of these objects
+ are passed to <function>PQconnectdbParams()</function>.
+ </para>
+ <para>
+ Currently, all of the generic options which are allowed in the context of
+ user mapping and foreign server are libpq connection options.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Connection management</title>
+ <para>
+ The postgresql_fdw connects to a remote PostgreSQL server when
+ <function>pgConnectServer()</function> is called for the foreign server
+ first time in the local query. The connection is used by all of remote
+ queries which are executed on same remote PostgreSQL server.
+ If the local query uses multiple foreign PostgreSQL servers, connections
+ are established for each server (not for each foreign table) and all of
+ them will be closed at the end of the query. This also means that
+ connection pooling is not implemented in postgresql_fdw.
+ </para>
+ <para>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Transaction management</title>
+ <para>
+ The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+ <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+ executed in each transaction when '<varname>autocommit</>' was set to 'on'.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Retrieving all tuples at once</title>
+ <para>
+ The postgresql_fdw retrieves all of the result tuples at once via libpq
+ when the query was executed. Note that huge result set causes huge memory
+ consumption. The memory for the result set will be freed at the end of the
+ each query.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>WHERE clause push-down</title>
+ <para>
+ The postgresql_fdw pushes some part of WHERE clause down to the remote
+ server, only if the evaluating the part of clause doesn't break the
+ consistency of the query. If a clause consist of elements below, the
+ clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>DISTINCT operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ PostgreSQL in type of <type>fdw_handler</type>.
+ Since fdw_hanlder is a pseudo type, postgresql_fdw_handler can't be
+ called from a SQL statement.
+ </para>
+ <para>
+ Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+ object which has set of foreign-data wrapper API functions for handling
+ foreign scan on the external PostgreSQL server. Functions other than
+ Iterate can be NULL if the foreign-data wrapper has nothing to do in the
+ function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Details of postgresql_fdw</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ The postgresql_fdw retrieves connection information from generic options of
+ user mapping and foriegn server. All of generic options of these objects
+ are passed to <function>PQconnectdbParams()</function>.
+ </para>
+ <para>
+ Currently, all of the generic options which are allowed in the context of
+ user mapping and foreign server are libpq connection options.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Connection management</title>
+ <para>
+ The postgresql_fdw connects to a remote PostgreSQL server when
+ <function>pgConnectServer()</function> is called for the foreign server
+ first time in the local query. The connection is used by all of remote
+ queries which are executed on same remote PostgreSQL server.
+ If the local query uses multiple foreign PostgreSQL servers, connections
+ are established for each server (not for each foreign table) and all of
+ them will be closed at the end of the query. This also means that
+ connection pooling is not implemented in postgresql_fdw.
+ </para>
+ <para>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Transaction management</title>
+ <para>
+ The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+ <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+ executed in each transaction when '<varname>autocommit</>' was set to 'on'.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Retrieving all tuples at once</title>
+ <para>
+ The postgresql_fdw retrieves all of the result tuples at once via libpq
+ when the query was executed. Note that huge result set causes huge memory
+ consumption. The memory for the result set will be freed at the end of the
+ each query.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>WHERE clause push-down</title>
+ <para>
+ The postgresql_fdw pushes some part of WHERE clause down to the remote
+ server, only if the evaluating the part of clause doesn't break the
+ consistency of the query. If a clause consist of elements below, the
+ clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>DISTINCT operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ PostgreSQL in type of <type>fdw_handler</type>.
+ Since fdw_hanlder is a pseudo type, postgresql_fdw_handler can't be
+ called from a SQL statement.
+ </para>
+ <para>
+ Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+ object which has set of foreign-data wrapper API functions for handling
+ foreign scan on the external PostgreSQL server. Functions other than
+ Iterate can be NULL if the foreign-data wrapper has nothing to do in the
+ function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Details of postgresql_fdw</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ The postgresql_fdw retrieves connection information from generic options of
+ user mapping and foriegn server. All of generic options of these objects
+ are passed to <function>PQconnectdbParams()</function>.
+ </para>
+ <para>
+ Currently, all of the generic options which are allowed in the context of
+ user mapping and foreign server are libpq connection options.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Connection management</title>
+ <para>
+ The postgresql_fdw connects to a remote PostgreSQL server when
+ <function>pgConnectServer()</function> is called for the foreign server
+ first time in the local query. The connection is used by all of remote
+ queries which are executed on same remote PostgreSQL server.
+ If the local query uses multiple foreign PostgreSQL servers, connections
+ are established for each server (not for each foreign table) and all of
+ them will be closed at the end of the query. This also means that
+ connection pooling is not implemented in postgresql_fdw.
+ </para>
+ <para>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Transaction management</title>
+ <para>
+ The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+ <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+ executed in each transaction when '<varname>autocommit</>' was set to 'on'.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Retrieving all tuples at once</title>
+ <para>
+ The postgresql_fdw retrieves all of the result tuples at once via libpq
+ when the query was executed. Note that huge result set causes huge memory
+ consumption. The memory for the result set will be freed at the end of the
+ each query.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>WHERE clause push-down</title>
+ <para>
+ The postgresql_fdw pushes some part of WHERE clause down to the remote
+ server, only if the evaluating the part of clause doesn't break the
+ consistency of the query. If a clause consist of elements below, the
+ clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>DISTINCT operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ PostgreSQL in type of <type>fdw_handler</type>.
+ Since fdw_hanlder is a pseudo type, postgresql_fdw_handler can't be
+ called from a SQL statement.
+ </para>
+ <para>
+ Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+ object which has set of foreign-data wrapper API functions for handling
+ foreign scan on the external PostgreSQL server. Functions other than
+ Iterate can be NULL if the foreign-data wrapper has nothing to do in the
+ function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Details of postgresql_fdw</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ The postgresql_fdw retrieves connection information from generic options of
+ user mapping and foriegn server. All of generic options of these objects
+ are passed to <function>PQconnectdbParams()</function>.
+ </para>
+ <para>
+ Currently, all of the generic options which are allowed in the context of
+ user mapping and foreign server are libpq connection options.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Connection management</title>
+ <para>
+ The postgresql_fdw connects to a remote PostgreSQL server when
+ <function>pgConnectServer()</function> is called for the foreign server
+ first time in the local query. The connection is used by all of remote
+ queries which are executed on same remote PostgreSQL server.
+ If the local query uses multiple foreign PostgreSQL servers, connections
+ are established for each server (not for each foreign table) and all of
+ them will be closed at the end of the query. This also means that
+ connection pooling is not implemented in postgresql_fdw.
+ </para>
+ <para>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Transaction management</title>
+ <para>
+ The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+ <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+ executed in each transaction when '<varname>autocommit</>' was set to 'on'.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Retrieving all tuples at once</title>
+ <para>
+ The postgresql_fdw retrieves all of the result tuples at once via libpq
+ when the query was executed. Note that huge result set causes huge memory
+ consumption. The memory for the result set will be freed at the end of the
+ each query.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>WHERE clause push-down</title>
+ <para>
+ The postgresql_fdw pushes some part of WHERE clause down to the remote
+ server, only if the evaluating the part of clause doesn't break the
+ consistency of the query. If a clause consist of elements below, the
+ clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>DISTINCT operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ PostgreSQL in type of <type>fdw_handler</type>.
+ Since fdw_hanlder is a pseudo type, postgresql_fdw_handler can't be
+ called from a SQL statement.
+ </para>
+ <para>
+ Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+ object which has set of foreign-data wrapper API functions for handling
+ foreign scan on the external PostgreSQL server. Functions other than
+ Iterate can be NULL if the foreign-data wrapper has nothing to do in the
+ function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Details of postgresql_fdw</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ The postgresql_fdw retrieves connection information from generic options of
+ user mapping and foriegn server. All of generic options of these objects
+ are passed to <function>PQconnectdbParams()</function>.
+ </para>
+ <para>
+ Currently, all of the generic options which are allowed in the context of
+ user mapping and foreign server are libpq connection options.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Connection management</title>
+ <para>
+ The postgresql_fdw connects to a remote PostgreSQL server when
+ <function>pgConnectServer()</function> is called for the foreign server
+ first time in the local query. The connection is used by all of remote
+ queries which are executed on same remote PostgreSQL server.
+ If the local query uses multiple foreign PostgreSQL servers, connections
+ are established for each server (not for each foreign table) and all of
+ them will be closed at the end of the query. This also means that
+ connection pooling is not implemented in postgresql_fdw.
+ </para>
+ <para>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Transaction management</title>
+ <para>
+ The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+ <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+ executed in each transaction when '<varname>autocommit</>' was set to 'on'.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Retrieving all tuples at once</title>
+ <para>
+ The postgresql_fdw retrieves all of the result tuples at once via libpq
+ when the query was executed. Note that huge result set causes huge memory
+ consumption. The memory for the result set will be freed at the end of the
+ each query.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>WHERE clause push-down</title>
+ <para>
+ The postgresql_fdw pushes some part of WHERE clause down to the remote
+ server, only if the evaluating the part of clause doesn't break the
+ consistency of the query. If a clause consist of elements below, the
+ clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>DISTINCT operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Immutable function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 8089fc6..65e0c32 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -433,55 +433,82 @@ pg_options_to_table(PG_FUNCTION_ARGS)
/*
* Describes the valid options for postgresql FDW, server, and user mapping.
*/
-struct ConnectionOption
+struct PgFdwOption
{
const char *optname;
Oid optcontext; /* Oid of catalog in which option may appear */
+ bool is_conn_opt; /* True if the option is a connection option */
};
/*
- * Copied from fe-connect.c PQconninfoOptions.
- *
+ * Valid options for postgresql_fdw.
+ * Connection options are copied from fe-connect.c PQconninfoOptions.
* The list is small - don't bother with bsearch if it stays so.
*/
-static struct ConnectionOption libpq_conninfo_options[] = {
- {"authtype", ForeignServerRelationId},
- {"service", ForeignServerRelationId},
- {"user", UserMappingRelationId},
- {"password", UserMappingRelationId},
- {"connect_timeout", ForeignServerRelationId},
- {"dbname", ForeignServerRelationId},
- {"host", ForeignServerRelationId},
- {"hostaddr", ForeignServerRelationId},
- {"port", ForeignServerRelationId},
- {"tty", ForeignServerRelationId},
- {"options", ForeignServerRelationId},
- {"requiressl", ForeignServerRelationId},
- {"sslmode", ForeignServerRelationId},
- {"gsslib", ForeignServerRelationId},
- {NULL, InvalidOid}
+static struct PgFdwOption valid_options[] = {
+ /* Connection Options */
+ {"authtype", ForeignServerRelationId, true},
+ {"service", ForeignServerRelationId, true},
+ {"user", UserMappingRelationId, true},
+ {"password", UserMappingRelationId, true},
+ {"connect_timeout", ForeignServerRelationId, true},
+ {"dbname", ForeignServerRelationId, true},
+ {"host", ForeignServerRelationId, true},
+ {"hostaddr", ForeignServerRelationId, true},
+ {"port", ForeignServerRelationId, true},
+ {"tty", ForeignServerRelationId, true},
+ {"options", ForeignServerRelationId, true},
+ {"requiressl", ForeignServerRelationId, true},
+ {"sslmode", ForeignServerRelationId, true},
+ {"gsslib", ForeignServerRelationId, true},
+
+ /* Catalog options */
+ {"nspname", ForeignTableRelationId, false},
+ {"relname", ForeignTableRelationId, false},
+ {"colname", AttributeRelationId, false},
+
+ /* Planner cost options */
+ {"connection_cost", ForeignServerRelationId, false},
+ {"transfer_cost", ForeignServerRelationId, false},
+
+ /* Centinel */
+ {NULL, InvalidOid, false}
};
-
/*
- * Check if the provided option is one of libpq conninfo options.
+ * Check if the provided option is one of valid options.
* context is the Oid of the catalog the option came from, or 0 if we
* don't care.
*/
static bool
-is_conninfo_option(const char *option, Oid context)
+is_valid_option(const char *option, Oid context)
{
- struct ConnectionOption *opt;
+ struct PgFdwOption *opt;
- for (opt = libpq_conninfo_options; opt->optname; opt++)
+ for (opt = valid_options; opt->optname; opt++)
if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
return true;
return false;
}
+/*
+ * Check if the provided option is one of libpq conninfo options.
+ * XXX: Should be moved to interface/libpq or backend/libpq?
+ */
+bool
+is_libpq_connection_option(const char *option)
+{
+ struct PgFdwOption *opt;
+
+ for (opt = valid_options; opt->optname; opt++)
+ if (opt->is_conn_opt && strcmp(opt->optname, option) == 0)
+ return true;
+ return false;
+}
/*
- * Validate the generic option given to SERVER or USER MAPPING.
+ * Validate the generic option given to FOREIGN DATA WRAPPER, SERVER, USER
+ * MAPPING or FOREIGN TABLE.
* Raise an ERROR if the option or its value is considered
* invalid.
*
@@ -500,9 +527,9 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
{
DefElem *def = lfirst(cell);
- if (!is_conninfo_option(def->defname, catalog))
+ if (!is_valid_option(def->defname, catalog))
{
- struct ConnectionOption *opt;
+ struct PgFdwOption *opt;
StringInfoData buf;
/*
@@ -510,7 +537,7 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
* with list of valid options for the object.
*/
initStringInfo(&buf);
- for (opt = libpq_conninfo_options; opt->optname; opt++)
+ for (opt = valid_options; opt->optname; opt++)
if (catalog == opt->optcontext)
appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
opt->optname);
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index b0cb37c..e2ef32c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -80,4 +80,6 @@ extern ForeignTable *GetForeignTable(Oid relid);
extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
+extern bool is_libpq_connection_option(const char *option);
+
#endif /* FOREIGN_H */
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 8f7eac5..9e4f3e0 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -284,7 +284,7 @@ CREATE SERVER s6 VERSION '16.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbna
CREATE SERVER s7 TYPE 'oracle' VERSION '17.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (foo '1'); -- ERROR
ERROR: invalid option "foo"
-HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib, connection_cost, transfer_cost
CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db');
\des+
List of foreign servers
@@ -395,7 +395,7 @@ ERROR: permission denied for foreign-data wrapper foo
RESET ROLE;
ALTER SERVER s8 OPTIONS (foo '1'); -- ERROR option validation
ERROR: invalid option "foo"
-HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib, connection_cost, transfer_cost
ALTER SERVER s8 OPTIONS (connect_timeout '30', SET dbname 'db1', DROP host);
SET ROLE regress_test_role;
ALTER SERVER s1 OWNER TO regress_test_indirect; -- ERROR
On 11.02.2011 22:50, Heikki Linnakangas wrote:
On 08.02.2011 13:07, Shigeru HANADA wrote:
On Mon, 07 Feb 2011 09:37:37 +0100
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> wrote:In get_relation_info(), you do the catalog lookup anyway and have the
Relation object at hand. Add a flag to RelOptInfo indicating if it's a
foreign table or not, and set that in get_relation_info().Thanks a lot.
Attached is a revised version of foreign_scan patch. This still
requires fdw_handler patch which was attached to the orginal post.Avoid_catalog_lookup.patch is attached for review purpose.
This patch includes changes for this fix.Thanks.
I spent some more time reviewing this, and working on the PostgreSQL FDW
in tandem. Here's an updated API patch, with a bunch of cosmetic
changes, and a bug fix for FOR SHARE/UPDATE.
Another version, rebased against master branch and with a bunch of small
cosmetic fixes.
I guess this is as good as this is going to get for 9.1. I'm sure we'll
have to revisit the API in the future as people write more FDWs and we
know their needs better, but there's one detail I'd like to have a
second opinion on before I commit this:
As the patch stands, we have to do get_rel_relkind() in a couple of
places in parse analysis and the planner to distinguish a foreign table
from a regular one. As the patch stands, there's nothing in
RangeTblEntry (which is what we have in transformLockingClause) or
RelOptInfo (for set_plain_rel_pathlist) to directly distinguish them.
I'm actually surprised we don't need to distinguish them in more places,
but nevertheless it feels like we should have that info available more
conveniently, and without requiring a catalog lookup like
get_rel_relkind() does. At first I thought we should add a field to
RelOptInfo, but that doesn't help transformLockingClause. Adding the
field to RangeTblEntry seems quite invasive, and it doesn't feel like
the right place to cache that kind of information anyway. Thoughts on that?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
fdw-api-2.patchtext/x-diff; name=fdw-api-2.patchDownload
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;
On Tue, Feb 15, 2011 at 1:40 PM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
I'm actually surprised we don't need to distinguish them in more places, but
nevertheless it feels like we should have that info available more
conveniently, and without requiring a catalog lookup like get_rel_relkind()
does. At first I thought we should add a field to RelOptInfo, but that
doesn't help transformLockingClause. Adding the field to RangeTblEntry seems
quite invasive, and it doesn't feel like the right place to cache that kind
of information anyway. Thoughts on that?
Maybe it should be a new RTEKind.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
As the patch stands, we have to do get_rel_relkind() in a couple of
places in parse analysis and the planner to distinguish a foreign table
from a regular one. As the patch stands, there's nothing in
RangeTblEntry (which is what we have in transformLockingClause) or
RelOptInfo (for set_plain_rel_pathlist) to directly distinguish them.
Hmm. I don't have a problem with adding relkind to the planner's
RelOptInfo, but it seems to me that if parse analysis needs to know
this, you have put functionality into parse analysis that does not
belong there.
(No, I haven't read the patch...)
regards, tom lane
On 15.02.2011 21:13, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
As the patch stands, we have to do get_rel_relkind() in a couple of
places in parse analysis and the planner to distinguish a foreign table
from a regular one. As the patch stands, there's nothing in
RangeTblEntry (which is what we have in transformLockingClause) or
RelOptInfo (for set_plain_rel_pathlist) to directly distinguish them.Hmm. I don't have a problem with adding relkind to the planner's
RelOptInfo, but it seems to me that if parse analysis needs to know
this, you have put functionality into parse analysis that does not
belong there.
Possibly. We throw the existing errors, for example if you try to do
"FOR UPDATE OF foo" where foo is a set-returning function, in
transformLockingClause(), so it seemed like the logical place to check
for foreign tables too.
Hmm, one approach would be to go ahead and create the RowMarkClauses for
all relations in the parse analysis phase, foreign or not, and throw the
error later, in preprocess_rowmarks(). preprocess_rowmarks() doesn't
currently know if each RowMarkClause was created by "... FOR UPDATE" or
"FOR UPDATE OF foo", but to be consistent with the logic in
transformLockingClause() it would need to. For the former case, it would
need to just ignore foreign tables but for the latter it would need to
throw an error. I guess we could add an "explicit" flag to RowMarkClause
for that.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On 15.02.2011 21:00, Robert Haas wrote:
On Tue, Feb 15, 2011 at 1:40 PM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:I'm actually surprised we don't need to distinguish them in more places, but
nevertheless it feels like we should have that info available more
conveniently, and without requiring a catalog lookup like get_rel_relkind()
does. At first I thought we should add a field to RelOptInfo, but that
doesn't help transformLockingClause. Adding the field to RangeTblEntry seems
quite invasive, and it doesn't feel like the right place to cache that kind
of information anyway. Thoughts on that?Maybe it should be a new RTEKind.
That would be even more invasive.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 15.02.2011 21:13, Tom Lane wrote:
Hmm. I don't have a problem with adding relkind to the planner's
RelOptInfo, but it seems to me that if parse analysis needs to know
this, you have put functionality into parse analysis that does not
belong there.
Possibly. We throw the existing errors, for example if you try to do
"FOR UPDATE OF foo" where foo is a set-returning function, in
transformLockingClause(), so it seemed like the logical place to check
for foreign tables too.
Hmm, one approach would be to go ahead and create the RowMarkClauses for
all relations in the parse analysis phase, foreign or not, and throw the
error later, in preprocess_rowmarks().
I think moving the error check downstream would be a good thing.
Consider for example the case of applying FOR UPDATE to a view. You
can't know what that entails until after the rewriter expands the view.
IIRC, at the moment we're basically duplicating the tests between parse
analysis and the planner, but it's not clear what the value of that is.
regards, tom lane
On 15.02.2011 23:00, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
On 15.02.2011 21:13, Tom Lane wrote:
Hmm. I don't have a problem with adding relkind to the planner's
RelOptInfo, but it seems to me that if parse analysis needs to know
this, you have put functionality into parse analysis that does not
belong there.Possibly. We throw the existing errors, for example if you try to do
"FOR UPDATE OF foo" where foo is a set-returning function, in
transformLockingClause(), so it seemed like the logical place to check
for foreign tables too.Hmm, one approach would be to go ahead and create the RowMarkClauses for
all relations in the parse analysis phase, foreign or not, and throw the
error later, in preprocess_rowmarks().I think moving the error check downstream would be a good thing.
Ok, I tried moving the error checks to preprocess_rowmarks().
Unfortunately RelOptInfos haven't been built at that stage yet, so you
still have to do the catalog lookup to get the relkind. That defeats the
purpose.
We could delay the error checking further, but preprocess_rowmarks()
would need to distinguish foreign tables anyway, so that it can mark
them with ROW_MARK_COPY instead of ROW_MARK_REFERENCE.
IIRC, at the moment we're basically duplicating the tests between parse
analysis and the planner, but it's not clear what the value of that is.
There's duplicate logic in parse analysis and rewriter, to be precise.
And then there's this one check in make_outerjoininfo:
/*
* Presently the executor cannot support FOR UPDATE/SHARE marking of
rels
* appearing on the nullable side of an outer join. (It's somewhat
unclear
* what that would mean, anyway: what should we mark when a result
row is
* generated from no element of the nullable relation?) So, complain if
* any nullable rel is FOR UPDATE/SHARE.
*
* You might be wondering why this test isn't made far upstream in the
* parser. It's because the parser hasn't got enough info --- consider
* FOR UPDATE applied to a view. Only after rewriting and flattening do
* we know whether the view contains an outer join.
*
* We use the original RowMarkClause list here; the PlanRowMark list
would
* list everything.
*/
foreach(l, root->parse->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);if (bms_is_member(rc->rti, right_rels) ||
(jointype == JOIN_FULL && bms_is_member(rc->rti, left_rels)))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to the
nullable side of an outer join")));
}
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 15.02.2011 23:00, Tom Lane wrote:
I think moving the error check downstream would be a good thing.
Ok, I tried moving the error checks to preprocess_rowmarks().
Unfortunately RelOptInfos haven't been built at that stage yet, so you
still have to do the catalog lookup to get the relkind. That defeats the
purpose.
Mph. It seems like the right fix here is to add relkind to
RangeTblEntry: it could be filled in for free in addRangeTableEntry, for
example. The main downside of that is that relation relkinds would have
to become fixed (because there would be no practical way of updating
RTEs in stored rules), which means the "convert relation to view" hack
would have to go away. Offhand I think no one cares about that anymore,
but ...
In any case, this is looking like a performance optimization that should
be dealt with in a separate patch. I'd suggest leaving the code in the
form with the extra relkind lookups for the initial commit. It's
possible that no one would notice the extra lookups anyway --- have you
benchmarked it?
regards, tom lane
On Fri, Feb 18, 2011 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 15.02.2011 23:00, Tom Lane wrote:
I think moving the error check downstream would be a good thing.
Ok, I tried moving the error checks to preprocess_rowmarks().
Unfortunately RelOptInfos haven't been built at that stage yet, so you
still have to do the catalog lookup to get the relkind. That defeats the
purpose.Mph. It seems like the right fix here is to add relkind to
RangeTblEntry: it could be filled in for free in addRangeTableEntry, for
example.
Heikki and I came to the same conclusion yesterday while chatting
about this on IM.
The main downside of that is that relation relkinds would have
to become fixed (because there would be no practical way of updating
RTEs in stored rules), which means the "convert relation to view" hack
would have to go away. Offhand I think no one cares about that anymore,
but ...
That actually sounds like a possible problem, because it's possible to
create views with circular dependencies using CORV, and they won't
dump-and-reload properly without that hack. It's not a particularly
useful thing to do, of course, and I think we could reengineer pg_dump
to not need the hack even if someone does do it, but that sounds like
more work than we want to tackle right now.
In any case, this is looking like a performance optimization that should
be dealt with in a separate patch. I'd suggest leaving the code in the
form with the extra relkind lookups for the initial commit. It's
possible that no one would notice the extra lookups anyway --- have you
benchmarked it?
This is a good point... although I think this results in at least one
extra syscache lookup per table per SELECT, even when no foreign
tables are involved.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Fri, Feb 18, 2011 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
The main downside of that is that relation relkinds would have
to become fixed (because there would be no practical way of updating
RTEs in stored rules), which means the "convert relation to view" hack
would have to go away. �Offhand I think no one cares about that anymore,
but ...
That actually sounds like a possible problem, because it's possible to
create views with circular dependencies using CORV, and they won't
dump-and-reload properly without that hack. It's not a particularly
useful thing to do, of course, and I think we could reengineer pg_dump
to not need the hack even if someone does do it, but that sounds like
more work than we want to tackle right now.
Urgh. That's problematic, because even if we changed pg_dump (which
would not be that hard I think), we'd still have to cope with dump files
emitted by existing versions of pg_dump. The time constant before that
stops being an issue is measured in years. I'm not at all sure whether
the circular dependency case is infrequent enough that we could get away
with saying "tough luck" to people who hit the case.
[ thinks a bit ... ] But we can probably hack our way around that:
teach the rule rewriter to update relkind in any RTE it brings in from a
stored rule. We already do something similar in some other cases where
a stored parsetree node contains information that could become obsolete.
But that conclusion just makes it even clearer that fixing this
performance problem, if it even is real, should be a separate patch.
regards, tom lane
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
Another version, rebased against master branch and with a bunch of small
cosmetic fixes.
I guess this is as good as this is going to get for 9.1.
This is *badly* in need of another cleanup pass; it's full of typos,
contradictory comments, #ifdef NOT_USED stuff, etc etc. And the
documentation is really inadequate. If you're out of energy to go
over it, I guess I should step up.
Question after first look: what is the motivation for passing
estate->es_param_list_info to BeginScan? AFAICS, even if there is a
reason for that function to need that, it isn't receiving any info that
would be sufficient to let it know what's in there. What seems more
likely to be useful is to pass in the EState pointer, as for example
being able to look at es_query_cxt seems like a good idea.
BTW, I see no particularly good reason to let the FDW omit ReScan.
If it wants to implement that as end-and-begin, it can do so internally.
It would be a lot clearer to just insist that all the function pointers
be valid, as indeed some (not all) of the comments say already.
regards, tom lane
On 18.02.2011 22:16, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
Another version, rebased against master branch and with a bunch of small
cosmetic fixes.I guess this is as good as this is going to get for 9.1.
This is *badly* in need of another cleanup pass; it's full of typos,
contradictory comments, #ifdef NOT_USED stuff, etc etc. And the
documentation is really inadequate. If you're out of energy to go
over it, I guess I should step up.
If you have the energy, by all means, thanks.
Question after first look: what is the motivation for passing
estate->es_param_list_info to BeginScan? AFAICS, even if there is a
reason for that function to need that, it isn't receiving any info that
would be sufficient to let it know what's in there.
The idea is that when the query is planned, the FDW can choose to push
down a qual that contains a parameter marker, like "WHERE remotecol =
$1". At execution time, it needs the value of the parameter to send it
to the remote server. The PostgreSQL FDW does that, although I didn't
test it so it might well be broken.
What seems more
likely to be useful is to pass in the EState pointer, as for example
being able to look at es_query_cxt seems like a good idea.
By "look at", you mean allocate stuff in it?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 18.02.2011 22:16, Tom Lane wrote:
Question after first look: what is the motivation for passing
estate->es_param_list_info to BeginScan? AFAICS, even if there is a
reason for that function to need that, it isn't receiving any info that
would be sufficient to let it know what's in there.
The idea is that when the query is planned, the FDW can choose to push
down a qual that contains a parameter marker, like "WHERE remotecol =
$1". At execution time, it needs the value of the parameter to send it
to the remote server. The PostgreSQL FDW does that, although I didn't
test it so it might well be broken.
s/might well be/is/ --- there's no guarantee that parameters are valid
at executor setup time. The place that needs to be grabbing the
parameter value for that purpose is BeginScan.
What seems more
likely to be useful is to pass in the EState pointer, as for example
being able to look at es_query_cxt seems like a good idea.
By "look at", you mean allocate stuff in it?
Right. I suppose you're going to comment that CurrentMemoryContext is
probably the same thing, but in general it's not going to pay to make
this API run with blinders on. My feeling is it'd be best to pass down
all the information the executor node has got --- probably we should
just pass the ForeignScanState node itself, and leave a void * in that
for FDW-private data, and be done with it. Otherwise we're going to be
adding missed stuff back to the API every time somebody notices that
their FDW can't do X because they don't have access to the necessary
information. That definitional instability will trump any ABI stability
that might be gained from not relying on executor node types. (And it's
not like changing ScanState in a released version is an entirely safe
thing to do even today --- there are lots of loadable modules that know
about that struct.)
regards, tom lane
I wrote:
... My feeling is it'd be best to pass down
all the information the executor node has got --- probably we should
just pass the ForeignScanState node itself, and leave a void * in that
for FDW-private data, and be done with it. Otherwise we're going to be
adding missed stuff back to the API every time somebody notices that
their FDW can't do X because they don't have access to the necessary
information.
Attached is a rewritten version of fdwhandler.sgml that specifies what I
think is a more future-proof API for the callback functions. Barring
objections, I'll push ahead with editing the code to match.
regards, tom lane
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 11.02.2011 22:50, Heikki Linnakangas wrote:
I spent some more time reviewing this, and working on the PostgreSQL FDW
in tandem. Here's an updated API patch, with a bunch of cosmetic
changes, and a bug fix for FOR SHARE/UPDATE.
Another version, rebased against master branch and with a bunch of small
cosmetic fixes.
I've applied this after a moderate amount of editorialization.
The question of avoiding extra relkind lookups is still open. We talked
about adding relkind to RangeTblEntry, but I wonder whether adding a new
RTEKind would be a better idea. Haven't researched it yet.
I have a hacked-up version of contrib/file_fdw that I've been using to
test it with. That needs some more cleanup before committing, but I
think it should not take too long. The one thing that is kind of
annoying is that the regression tests need generated files (to insert
absolute paths) and it seems like the PGXS infrastructure doesn't know
how to clean up the generated files afterwards. Anybody have any
thoughts about fixing that?
regards, tom lane
I wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Fri, Feb 18, 2011 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
The main downside of that is that relation relkinds would have
to become fixed (because there would be no practical way of updating
RTEs in stored rules), which means the "convert relation to view" hack
would have to go away.
That actually sounds like a possible problem, because it's possible to
create views with circular dependencies using CORV, and they won't
dump-and-reload properly without that hack.
Urgh. That's problematic, because even if we changed pg_dump (which
would not be that hard I think), we'd still have to cope with dump files
emitted by existing versions of pg_dump.
[ thinks a bit ... ] But we can probably hack our way around that:
teach the rule rewriter to update relkind in any RTE it brings in from a
stored rule. We already do something similar in some other cases where
a stored parsetree node contains information that could become obsolete.
I did a bit of poking around here, and came to the following
conclusions:
1. We don't want to add another RTEKind. RTE_RELATION basically has the
semantics of "anything with a pg_class OID", so it ought to include
foreign tables. Therefore the fix ought to be to add relkind to
RangeTblEntry. (BTW, so far as I can tell, RTE_SPECIAL is obsolete:
there are assorted switch cases that handle it, but no place that can
generate the value. I'm inclined to delete it while we are messing
with this.)
2. In the current code layout, to make sense of relkind you need to
#include catalog/pg_class.h where the values for relkind are #defined.
I dislike the idea of that being true for a field of such a widely-known
struct as RangeTblEntry. Accordingly, I suggest that we move those
values into parsenodes.h. (Perhaps we could convert them to an enum,
too, though still keeping the same ASCII values.)
3. We can have the rewriter update an RTE's stored value of relkind
during AcquireRewriteLocks: it opens the rel for each RTE_RELATION entry
anyway, so copying over the relkind is essentially free. While it's not
instantly obvious that that is "soon enough", I think that it is, since
up to the point of acquiring a lock there we can't assume that the rel
isn't being changed or dropped undeneath us --- that is, any earlier
test on an RTE's relkind might be testing just-obsoleted state anyway.
4. I had hoped that we might be able to get rid of some pre-existing
syscache lookups, but at least so far as the parse/plan/execute chain
is concerned, there don't seem to be any other places that need to
fetch a relkind based on just an RTE entry.
So point #4 is a bit discouraging, but other that, this seems like
a fairly straightforward exercise. I'm inclined to go ahead and do it,
unless there are objections.
regards, tom lane
I wrote:
I did a bit of poking around here, and came to the following
conclusions:
1. We don't want to add another RTEKind. RTE_RELATION basically has the
semantics of "anything with a pg_class OID", so it ought to include
foreign tables. Therefore the fix ought to be to add relkind to
RangeTblEntry. (BTW, so far as I can tell, RTE_SPECIAL is obsolete:
there are assorted switch cases that handle it, but no place that can
generate the value. I'm inclined to delete it while we are messing
with this.)
2. In the current code layout, to make sense of relkind you need to
#include catalog/pg_class.h where the values for relkind are #defined.
I dislike the idea of that being true for a field of such a widely-known
struct as RangeTblEntry. Accordingly, I suggest that we move those
values into parsenodes.h. (Perhaps we could convert them to an enum,
too, though still keeping the same ASCII values.)
3. We can have the rewriter update an RTE's stored value of relkind
during AcquireRewriteLocks: it opens the rel for each RTE_RELATION entry
anyway, so copying over the relkind is essentially free. While it's not
instantly obvious that that is "soon enough", I think that it is, since
up to the point of acquiring a lock there we can't assume that the rel
isn't being changed or dropped undeneath us --- that is, any earlier
test on an RTE's relkind might be testing just-obsoleted state anyway.
4. I had hoped that we might be able to get rid of some pre-existing
syscache lookups, but at least so far as the parse/plan/execute chain
is concerned, there don't seem to be any other places that need to
fetch a relkind based on just an RTE entry.
So point #4 is a bit discouraging, but other that, this seems like
a fairly straightforward exercise. I'm inclined to go ahead and do it,
unless there are objections.
Applied, except I ended up not moving the RELKIND #defines as suggested
in point #2. Those #defines are used by pg_dump, and having pg_dump.c
#include parsenodes.h seemed like a bad idea.
regards, tom lane