Hi all,

Attached is a patch that implements the extension support discussed at
PgCon this year during the FDW unconference sesssion. Highlights:

* Pass extension operators and functions to the foreign server
* Only send ops/funcs if the foreign server is declared to support the
relevant extension, for example:

CREATE SERVER foreign_server
        FOREIGN DATA WRAPPER postgres_fdw
        OPTIONS (host '127.0.0.1', port '5432', dbname 'my_db',
extensions 'cube, seg');

Github branch is here:
  https://github.com/pramsey/postgres/tree/fdw-extension-suppport

Synthetic pull request for easy browsing/commenting is here:
  https://github.com/pramsey/postgres/pull/1

Thanks!

Paul
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 81cb2b4..bbe3c9d 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -34,11 +34,15 @@
 
 #include "postgres_fdw.h"
 
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_depend.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
@@ -49,8 +53,10 @@
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
 
@@ -136,6 +142,7 @@ static void printRemoteParam(int paramindex, Oid paramtype, 
int32 paramtypmod,
                                 deparse_expr_cxt *context);
 static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
                                           deparse_expr_cxt *context);
+static bool is_in_extension(Oid procid, PgFdwRelationInfo *fpinfo);
 
 
 /*
@@ -167,6 +174,7 @@ classifyConditions(PlannerInfo *root,
        }
 }
 
+
 /*
  * Returns true if given expr is safe to evaluate on the foreign server.
  */
@@ -177,7 +185,7 @@ is_foreign_expr(PlannerInfo *root,
 {
        foreign_glob_cxt glob_cxt;
        foreign_loc_cxt loc_cxt;
-
+       
        /*
         * Check that the expression consists of nodes that are safe to execute
         * remotely.
@@ -207,6 +215,8 @@ is_foreign_expr(PlannerInfo *root,
        return true;
 }
 
+
+
 /*
  * Check if expression is safe to execute remotely, and return true if so.
  *
@@ -229,6 +239,9 @@ foreign_expr_walker(Node *node,
        Oid                     collation;
        FDWCollateState state;
 
+       /* Access extension metadata from fpinfo on baserel */
+       PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo 
*)(glob_cxt->foreignrel->fdw_private);
+
        /* Need do nothing for empty subexpressions */
        if (node == NULL)
                return true;
@@ -361,7 +374,7 @@ foreign_expr_walker(Node *node,
                                 * can't be sent to remote because it might 
have incompatible
                                 * semantics on remote side.
                                 */
-                               if (!is_builtin(fe->funcid))
+                               if (!is_builtin(fe->funcid) && 
(!is_in_extension(fe->funcid, fpinfo)))
                                        return false;
 
                                /*
@@ -407,7 +420,7 @@ foreign_expr_walker(Node *node,
                                 * (If the operator is, surely its underlying 
function is
                                 * too.)
                                 */
-                               if (!is_builtin(oe->opno))
+                               if ( (!is_builtin(oe->opno)) && 
(!is_in_extension(oe->opno, fpinfo)) )
                                        return false;
 
                                /*
@@ -445,7 +458,7 @@ foreign_expr_walker(Node *node,
                                /*
                                 * Again, only built-in operators can be sent 
to remote.
                                 */
-                               if (!is_builtin(oe->opno))
+                               if (!is_builtin(oe->opno) && 
(!is_in_extension(oe->opno, fpinfo)))
                                        return false;
 
                                /*
@@ -591,7 +604,7 @@ foreign_expr_walker(Node *node,
         * If result type of given expression is not built-in, it can't be sent 
to
         * remote because it might have incompatible semantics on remote side.
         */
-       if (check_type && !is_builtin(exprType(node)))
+       if (check_type && !is_builtin(exprType(node)) && 
(!is_in_extension(exprType(node), fpinfo)) )
                return false;
 
        /*
@@ -643,6 +656,8 @@ foreign_expr_walker(Node *node,
        return true;
 }
 
+
+
 /*
  * Return true if given object is one of PostgreSQL's built-in objects.
  *
@@ -669,6 +684,67 @@ is_builtin(Oid oid)
 
 
 /*
+ * Returns true if given operator/function is part of an extension declared in 
the 
+ * server options.
+ */
+static bool
+is_in_extension(Oid procnumber, PgFdwRelationInfo *fpinfo)
+{
+       static int nkeys = 1;
+       ScanKeyData key[nkeys];
+       HeapTuple tup;
+       Relation depRel;
+       SysScanDesc scan;
+       int nresults = 0;
+
+       /* Always return false if we don't have any declared extensions */
+       if ( ! fpinfo->extensions )
+               return false;
+
+       /* We need this relation to scan */
+       depRel = heap_open(DependRelationId, RowExclusiveLock);
+
+       /* Scan the system dependency table for a all entries this operator */
+       /* depends on, then iterate through and see if one of them */
+       /* is a registered extension */
+       ScanKeyInit(&key[0],
+                               Anum_pg_depend_objid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(procnumber));
+
+       scan = systable_beginscan(depRel, DependDependerIndexId, true,
+                                                         
GetCatalogSnapshot(depRel->rd_id), nkeys, key);
+
+       while (HeapTupleIsValid(tup = systable_getnext(scan)))
+       {
+               Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+               if ( foundDep->deptype == DEPENDENCY_EXTENSION )
+               {
+                       List *extlist = fpinfo->extensions;
+                       ListCell *ext;
+
+                       foreach(ext, extlist)
+                       {
+                               Oid extension_oid = (Oid) lfirst(ext);
+                               if ( foundDep->refobjid == extension_oid )
+                               {
+                                       nresults++;
+                               }
+                       }
+               }
+               if ( nresults > 0 ) break;
+       }
+
+       systable_endscan(scan);
+       relation_close(depRel, RowExclusiveLock);
+
+       return nresults > 0;
+}
+
+
+
+/*
  * Construct a simple SELECT statement that retrieves desired columns
  * of the specified foreign table, and append it to "buf".  The output
  * contains just "SELECT ... FROM tablename".
@@ -1404,8 +1480,7 @@ deparseConst(Const *node, deparse_expr_cxt *context)
        }
        if (needlabel)
                appendStringInfo(buf, "::%s",
-                                                
format_type_with_typemod(node->consttype,
-                                                                               
                  node->consttypmod));
+                                                
format_type_be_qualified(node->consttype));
 }
 
 /*
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 7547ec2..29a3731 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -19,6 +19,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_user_mapping.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -105,7 +107,7 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
                 * Validate option value, when we can do so without any context.
                 */
                if (strcmp(def->defname, "use_remote_estimate") == 0 ||
-                       strcmp(def->defname, "updatable") == 0)
+                       strcmp(def->defname, "updatable") == 0 )
                {
                        /* these accept only boolean values */
                        (void) defGetBoolean(def);
@@ -124,6 +126,10 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
                                                 errmsg("%s requires a 
non-negative numeric value",
                                                                def->defname)));
                }
+               else if (strcmp(def->defname, "extensions") == 0)
+               {
+                       extractExtensionList(defGetString(def), NULL);
+               }
        }
 
        PG_RETURN_VOID();
@@ -153,6 +159,8 @@ InitPgFdwOptions(void)
                /* updatable is available on both server and table */
                {"updatable", ForeignServerRelationId, false},
                {"updatable", ForeignTableRelationId, false},
+               /* use_remote_estimate is available on both server and table */
+               {"extensions", ForeignServerRelationId, false},
                {NULL, InvalidOid, false}
        };
 
@@ -293,3 +301,46 @@ ExtractConnectionOptions(List *defelems, const char 
**keywords,
        }
        return i;
 }
+
+
+bool 
+extractExtensionList(char *extensionString, List **extensionOids)
+{
+       List *extlist;
+       ListCell   *l;
+
+       if ( ! SplitIdentifierString(extensionString, ',', &extlist) )
+       {
+               list_free(extlist);
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("unable to parse extension list '%s'",
+                               extensionString)));
+       }
+
+       if ( extensionOids )
+               *extensionOids = NIL;
+
+       foreach(l, extlist)
+       {
+               char *extension_name = (char *) lfirst(l);
+               Oid extension_oid = get_extension_oid(extension_name, true);
+               if ( extension_oid == InvalidOid )
+               {
+                       ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("the '%s' extension must be installed 
locally before it can be used on a remote server",
+                                       extension_name)));
+               }
+               else
+               {
+                       if ( extensionOids )
+                       {
+                               *extensionOids = lappend_oid(*extensionOids, 
extension_oid);
+                       }
+               }
+       }
+
+       list_free(extlist);
+       return true;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.c 
b/contrib/postgres_fdw/postgres_fdw.c
index e4d799c..2b1c240 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/explain.h"
 #include "commands/vacuum.h"
 #include "foreign/fdwapi.h"
@@ -47,39 +48,6 @@ PG_MODULE_MAGIC;
 /* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */
 #define DEFAULT_FDW_TUPLE_COST         0.01
 
-/*
- * FDW-specific planner information kept in RelOptInfo.fdw_private for a
- * foreign table.  This information is collected by postgresGetForeignRelSize.
- */
-typedef struct PgFdwRelationInfo
-{
-       /* baserestrictinfo clauses, broken down into safe and unsafe subsets. 
*/
-       List       *remote_conds;
-       List       *local_conds;
-
-       /* Bitmap of attr numbers we need to fetch from the remote server. */
-       Bitmapset  *attrs_used;
-
-       /* Cost and selectivity of local_conds. */
-       QualCost        local_conds_cost;
-       Selectivity local_conds_sel;
-
-       /* Estimated size and cost for a scan with baserestrictinfo quals. */
-       double          rows;
-       int                     width;
-       Cost            startup_cost;
-       Cost            total_cost;
-
-       /* Options extracted from catalogs. */
-       bool            use_remote_estimate;
-       Cost            fdw_startup_cost;
-       Cost            fdw_tuple_cost;
-
-       /* Cached catalog information. */
-       ForeignTable *table;
-       ForeignServer *server;
-       UserMapping *user;                      /* only set in 
use_remote_estimate mode */
-} PgFdwRelationInfo;
 
 /*
  * Indexes of FDW-private information stored in fdw_private lists.
@@ -416,16 +384,15 @@ postgresGetForeignRelSize(PlannerInfo *root,
                        fpinfo->fdw_startup_cost = strtod(defGetString(def), 
NULL);
                else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
                        fpinfo->fdw_tuple_cost = strtod(defGetString(def), 
NULL);
+               else if (strcmp(def->defname, "extensions") == 0)
+                       extractExtensionList(defGetString(def), 
&(fpinfo->extensions));
        }
        foreach(lc, fpinfo->table->options)
        {
                DefElem    *def = (DefElem *) lfirst(lc);
 
                if (strcmp(def->defname, "use_remote_estimate") == 0)
-               {
                        fpinfo->use_remote_estimate = defGetBoolean(def);
-                       break;                          /* only need the one 
value */
-               }
        }
 
        /*
@@ -2994,3 +2961,5 @@ conversion_error_callback(void *arg)
                                   NameStr(tupdesc->attrs[errpos->cur_attno - 
1]->attname),
                                   RelationGetRelationName(errpos->rel));
 }
+
+
diff --git a/contrib/postgres_fdw/postgres_fdw.h 
b/contrib/postgres_fdw/postgres_fdw.h
index 3835ddb..d27ed37 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -20,6 +20,46 @@
 
 #include "libpq-fe.h"
 
+/*
+ * FDW-specific planner information kept in RelOptInfo.fdw_private for a
+ * foreign table.  This information is collected by postgresGetForeignRelSize.
+ */
+typedef struct PgFdwRelationInfo
+{
+       /* baserestrictinfo clauses, broken down into safe and unsafe subsets. 
*/
+       List       *remote_conds;
+       List       *local_conds;
+
+       /* Bitmap of attr numbers we need to fetch from the remote server. */
+       Bitmapset  *attrs_used;
+
+       /* Cost and selectivity of local_conds. */
+       QualCost        local_conds_cost;
+       Selectivity local_conds_sel;
+
+       /* Estimated size and cost for a scan with baserestrictinfo quals. */
+       double          rows;
+       int                     width;
+       Cost            startup_cost;
+       Cost            total_cost;
+
+       /* Options extracted from catalogs. */
+       bool            use_remote_estimate;
+       Cost            fdw_startup_cost;
+       Cost            fdw_tuple_cost;
+       
+       /* PostGIS metadata */
+       List        *extensions;
+       bool        use_postgis;
+       Oid         postgis_oid;
+
+       /* Cached catalog information. */
+       ForeignTable *table;
+       ForeignServer *server;
+       UserMapping *user;                      /* only set in 
use_remote_estimate mode */
+} PgFdwRelationInfo;
+
+
 /* in postgres_fdw.c */
 extern int     set_transmission_modes(void);
 extern void reset_transmission_modes(int nestlevel);
@@ -37,6 +77,8 @@ extern void pgfdw_report_error(int elevel, PGresult *res, 
PGconn *conn,
 extern int ExtractConnectionOptions(List *defelems,
                                                 const char **keywords,
                                                 const char **values);
+extern bool extractExtensionList(char *extensionString,
+                                                List **extensionOids);
 
 /* in deparse.c */
 extern void classifyConditions(PlannerInfo *root,
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 14b12e3..7b3d8c7 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -373,6 +373,37 @@
     foreign tables, see <xref linkend="sql-createforeigntable">.
    </para>
   </sect3>
+  
+  <sect3>
+   <title>Extension Options</title>
+
+   <para>
+    By default only built-in operators and functions will be sent from the 
+    local to the foreign server. This may be overridden using the following 
option:
+   </para>
+
+   <variablelist>
+
+    <varlistentry>
+     <term><literal>extensions</literal></term>
+     <listitem>
+      <para>
+       This option allows you to declare what extensions you expect are 
+       installed on the foreign server, using a comma-separated list of 
+       extension names. The extensions are also expected to be installed
+       on the local server too.
+      </para>
+<programlisting>
+CREATE SERVER foreign_server
+        FOREIGN DATA WRAPPER postgres_fdw
+        OPTIONS (host '127.0.0.1', port '5432', dbname 'my_db', extensions 
'cube, seg');
+</programlisting>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
+  </sect3>  
+  
  </sect2>
 
  <sect2>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to