# contrib/postgres_fdw/Makefile
MODULE_big = postgres_fdw
-OBJS = postgres_fdw.o option.o deparse.o connection.o $(WIN32RES)
+OBJS = postgres_fdw.o option.o deparse.o connection.o shippable.o $(WIN32RES)
PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL"
PG_CPPFLAGS = -I$(libpq_srcdir)
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
-#include "access/transam.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
static bool foreign_expr_walker(Node *node,
foreign_glob_cxt *glob_cxt,
foreign_loc_cxt *outer_cxt);
-static bool is_builtin(Oid procid);
+static char *deparse_type_name(Oid type_oid, int32 typemod);
/*
* Functions to construct string representation of a node tree.
* In addition, *outer_cxt is updated with collation information.
*
* We must check that the expression contains only node types we can deparse,
- * that all types/functions/operators are safe to send (which we approximate
- * as being built-in), and that all collations used in the expression derive
- * from Vars of the foreign table. Because of the latter, the logic is
- * pretty close to assign_collations_walker() in parse_collate.c, though we
- * can assume here that the given expression is valid.
+ * that all types/functions/operators are safe to send (they are "shippable"),
+ * and that all collations used in the expression derive from Vars of the
+ * foreign table. Because of the latter, the logic is pretty close to
+ * assign_collations_walker() in parse_collate.c, though we can assume here
+ * that the given expression is valid. Note function mutability is not
+ * currently considered here.
*/
static bool
foreign_expr_walker(Node *node,
foreign_loc_cxt *outer_cxt)
{
bool check_type = true;
+ PgFdwRelationInfo *fpinfo;
foreign_loc_cxt inner_cxt;
Oid collation;
FDWCollateState state;
if (node == NULL)
return true;
+ /* May need server info from baserel's fdw_private struct */
+ fpinfo = (PgFdwRelationInfo *) (glob_cxt->foreignrel->fdw_private);
+
/* Set up inner_cxt for possible recursion to child nodes */
inner_cxt.collation = InvalidOid;
inner_cxt.state = FDW_COLLATE_NONE;
FuncExpr *fe = (FuncExpr *) node;
/*
- * If function used by the expression is not built-in, it
+ * If function used by the expression is not shippable, it
* can't be sent to remote because it might have incompatible
* semantics on remote side.
*/
- if (!is_builtin(fe->funcid))
+ if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo))
return false;
/*
OpExpr *oe = (OpExpr *) node;
/*
- * Similarly, only built-in operators can be sent to remote.
- * (If the operator is, surely its underlying function is
- * too.)
+ * Similarly, only shippable operators can be sent to remote.
+ * (If the operator is shippable, we assume its underlying
+ * function is too.)
*/
- if (!is_builtin(oe->opno))
+ if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
return false;
/*
ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
/*
- * Again, only built-in operators can be sent to remote.
+ * Again, only shippable operators can be sent to remote.
*/
- if (!is_builtin(oe->opno))
+ if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
return false;
/*
}
/*
- * 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 result type of given expression is not shippable, 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_shippable(exprType(node), TypeRelationId, fpinfo))
return false;
/*
}
/*
- * Return true if given object is one of PostgreSQL's built-in objects.
- *
- * We use FirstBootstrapObjectId as the cutoff, so that we only consider
- * objects with hand-assigned OIDs to be "built in", not for instance any
- * function or type defined in the information_schema.
+ * Convert type OID + typmod info into a type name we can ship to the remote
+ * server. Someplace else had better have verified that this type name is
+ * expected to be known on the remote end.
*
- * Our constraints for dealing with types are tighter than they are for
- * functions or operators: we want to accept only types that are in pg_catalog,
- * else format_type might incorrectly fail to schema-qualify their names.
- * (This could be fixed with some changes to format_type, but for now there's
- * no need.) Thus we must exclude information_schema types.
- *
- * XXX there is a problem with this, which is that the set of built-in
- * objects expands over time. Something that is built-in to us might not
- * be known to the remote server, if it's of an older version. But keeping
- * track of that would be a huge exercise.
+ * This is almost just format_type_with_typemod(), except that if left to its
+ * own devices, that function will make schema-qualification decisions based
+ * on the local search_path, which is wrong. We must schema-qualify all
+ * type names that are not in pg_catalog. We assume here that built-in types
+ * are all in pg_catalog and need not be qualified; otherwise, qualify.
*/
-static bool
-is_builtin(Oid oid)
+static char *
+deparse_type_name(Oid type_oid, int32 typemod)
{
- return (oid < FirstBootstrapObjectId);
+ if (is_builtin(type_oid))
+ return format_type_with_typemod(type_oid, typemod);
+ else
+ return format_type_with_typemod_qualified(type_oid, typemod);
}
{
appendStringInfoString(buf, "NULL");
appendStringInfo(buf, "::%s",
- format_type_with_typemod(node->consttype,
- node->consttypmod));
+ deparse_type_name(node->consttype,
+ node->consttypmod));
return;
}
}
if (needlabel)
appendStringInfo(buf, "::%s",
- format_type_with_typemod(node->consttype,
- node->consttypmod));
+ deparse_type_name(node->consttype,
+ node->consttypmod));
}
/*
deparseExpr((Expr *) linitial(node->args), context);
appendStringInfo(buf, "::%s",
- format_type_with_typemod(rettype, coercedTypmod));
+ deparse_type_name(rettype, coercedTypmod));
return;
}
deparseExpr(node->arg, context);
if (node->relabelformat != COERCE_IMPLICIT_CAST)
appendStringInfo(context->buf, "::%s",
- format_type_with_typemod(node->resulttype,
- node->resulttypmod));
+ deparse_type_name(node->resulttype,
+ node->resulttypmod));
}
/*
/* If the array is empty, we need an explicit cast to the array type. */
if (node->elements == NIL)
appendStringInfo(buf, "::%s",
- format_type_with_typemod(node->array_typeid, -1));
+ deparse_type_name(node->array_typeid, -1));
}
/*
deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
- char *ptypename = format_type_with_typemod(paramtype, paramtypmod);
+ char *ptypename = deparse_type_name(paramtype, paramtypmod);
appendStringInfo(buf, "$%d::%s", paramindex, ptypename);
}
deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
- char *ptypename = format_type_with_typemod(paramtype, paramtypmod);
+ char *ptypename = deparse_type_name(paramtype, paramtypmod);
appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename);
}
appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel,
List *pathkeys)
{
- ListCell *lcell;
- deparse_expr_cxt context;
- int nestlevel;
- char *delim = " ";
+ ListCell *lcell;
+ deparse_expr_cxt context;
+ int nestlevel;
+ char *delim = " ";
/* Set up context struct for recursion */
context.root = root;
appendStringInfo(buf, " ORDER BY");
foreach(lcell, pathkeys)
{
- PathKey *pathkey = lfirst(lcell);
- Expr *em_expr;
+ PathKey *pathkey = lfirst(lcell);
+ Expr *em_expr;
em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel);
Assert(em_expr != NULL);
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "commands/extension.h"
+#include "utils/builtins.h"
/*
errmsg("%s requires a non-negative numeric value",
def->defname)));
}
+ else if (strcmp(def->defname, "extensions") == 0)
+ {
+ /* check list syntax, warn about uninstalled extensions */
+ (void) ExtractExtensionList(defGetString(def), true);
+ }
}
PG_RETURN_VOID();
/* cost factors */
{"fdw_startup_cost", ForeignServerRelationId, false},
{"fdw_tuple_cost", ForeignServerRelationId, false},
+ /* shippable extensions */
+ {"extensions", ForeignServerRelationId, false},
/* updatable is available on both server and table */
{"updatable", ForeignServerRelationId, false},
{"updatable", ForeignTableRelationId, false},
}
return i;
}
+
+/*
+ * Parse a comma-separated string and return a List of the OIDs of the
+ * extensions named in the string. If any names in the list cannot be
+ * found, report a warning if warnOnMissing is true, else just silently
+ * ignore them.
+ */
+List *
+ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
+{
+ List *extensionOids = NIL;
+ List *extlist;
+ ListCell *lc;
+
+ /* SplitIdentifierString scribbles on its input, so pstrdup first */
+ if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
+ {
+ /* syntax error in name list */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" must be a list of extension names",
+ "extensions")));
+ }
+
+ foreach(lc, extlist)
+ {
+ const char *extension_name = (const char *) lfirst(lc);
+ Oid extension_oid = get_extension_oid(extension_name, true);
+
+ if (OidIsValid(extension_oid))
+ {
+ extensionOids = lappend_oid(extensionOids, extension_oid);
+ }
+ else if (warnOnMissing)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" is not installed",
+ extension_name)));
+ }
+ }
+
+ list_free(extlist);
+ return extensionOids;
+}
/* If no remote estimates, assume a sort costs 20% extra */
#define DEFAULT_FDW_SORT_MULTIPLIER 1.2
-/*
- * 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.
*
fpinfo->use_remote_estimate = false;
fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
+ fpinfo->shippable_extensions = NIL;
foreach(lc, fpinfo->server->options)
{
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)
+ fpinfo->shippable_extensions =
+ ExtractExtensionList(defGetString(def), false);
}
foreach(lc, fpinfo->table->options)
{
#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;
+ List *shippable_extensions; /* OIDs of whitelisted extensions */
+
+ /* 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);
extern int ExtractConnectionOptions(List *defelems,
const char **keywords,
const char **values);
+extern List *ExtractExtensionList(const char *extensionsString,
+ bool warnOnMissing);
/* in deparse.c */
extern void classifyConditions(PlannerInfo *root,
extern void deparseStringLiteral(StringInfo buf, const char *val);
extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern void appendOrderByClause(StringInfo buf, PlannerInfo *root,
- RelOptInfo *baserel, List *pathkeys);
+ RelOptInfo *baserel, List *pathkeys);
+
+/* in shippable.c */
+extern bool is_builtin(Oid objectId);
+extern bool is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo);
#endif /* POSTGRES_FDW_H */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * shippable.c
+ * Determine which database objects are shippable to a remote server.
+ *
+ * We need to determine whether particular functions, operators, and indeed
+ * data types are shippable to a remote server for execution --- that is,
+ * do they exist and have the same behavior remotely as they do locally?
+ * Built-in objects are generally considered shippable. Other objects can
+ * be shipped if they are white-listed by the user.
+ *
+ * Note: there are additional filter rules that prevent shipping mutable
+ * functions or functions using nonportable collations. Those considerations
+ * need not be accounted for here.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgres_fdw/shippable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "postgres_fdw.h"
+
+#include "access/transam.h"
+#include "catalog/dependency.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+
+
+/* Hash table for caching the results of shippability lookups */
+static HTAB *ShippableCacheHash = NULL;
+
+/*
+ * Hash key for shippability lookups. We include the FDW server OID because
+ * decisions may differ per-server. Otherwise, objects are identified by
+ * their (local!) OID and catalog OID.
+ */
+typedef struct
+{
+ /* XXX we assume this struct contains no padding bytes */
+ Oid objid; /* function/operator/type OID */
+ Oid classid; /* OID of its catalog (pg_proc, etc) */
+ Oid serverid; /* FDW server we are concerned with */
+} ShippableCacheKey;
+
+typedef struct
+{
+ ShippableCacheKey key; /* hash key - must be first */
+ bool shippable;
+} ShippableCacheEntry;
+
+
+/*
+ * Flush cache entries when pg_foreign_server is updated.
+ *
+ * We do this because of the possibility of ALTER SERVER being used to change
+ * a server's extensions option. We do not currently bother to check whether
+ * objects' extension membership changes once a shippability decision has been
+ * made for them, however.
+ */
+static void
+InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ ShippableCacheEntry *entry;
+
+ /*
+ * In principle we could flush only cache entries relating to the
+ * pg_foreign_server entry being outdated; but that would be more
+ * complicated, and it's probably not worth the trouble. So for now, just
+ * flush all entries.
+ */
+ hash_seq_init(&status, ShippableCacheHash);
+ while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (hash_search(ShippableCacheHash,
+ (void *) &entry->key,
+ HASH_REMOVE,
+ NULL) == NULL)
+ elog(ERROR, "hash table corrupted");
+ }
+}
+
+/*
+ * Initialize the backend-lifespan cache of shippability decisions.
+ */
+static void
+InitializeShippableCache(void)
+{
+ HASHCTL ctl;
+
+ /* Create the hash table. */
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(ShippableCacheKey);
+ ctl.entrysize = sizeof(ShippableCacheEntry);
+ ShippableCacheHash =
+ hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
+
+ /* Set up invalidation callback on pg_foreign_server. */
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+ InvalidateShippableCacheCallback,
+ (Datum) 0);
+}
+
+/*
+ * Returns true if given object (operator/function/type) is shippable
+ * according to the server options.
+ *
+ * Right now "shippability" is exclusively a function of whether the object
+ * belongs to an extension declared by the user. In the future we could
+ * additionally have a whitelist of functions/operators declared one at a time.
+ */
+static bool
+lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
+{
+ Oid extensionOid;
+
+ /*
+ * Is object a member of some extension? (Note: this is a fairly
+ * expensive lookup, which is why we try to cache the results.)
+ */
+ extensionOid = getExtensionOfObject(classId, objectId);
+
+ /* If so, is that extension in fpinfo->shippable_extensions? */
+ if (OidIsValid(extensionOid) &&
+ list_member_oid(fpinfo->shippable_extensions, extensionOid))
+ return true;
+
+ return false;
+}
+
+/*
+ * Return true if given object is one of PostgreSQL's built-in objects.
+ *
+ * We use FirstBootstrapObjectId as the cutoff, so that we only consider
+ * objects with hand-assigned OIDs to be "built in", not for instance any
+ * function or type defined in the information_schema.
+ *
+ * Our constraints for dealing with types are tighter than they are for
+ * functions or operators: we want to accept only types that are in pg_catalog,
+ * else deparse_type_name might incorrectly fail to schema-qualify their names.
+ * Thus we must exclude information_schema types.
+ *
+ * XXX there is a problem with this, which is that the set of built-in
+ * objects expands over time. Something that is built-in to us might not
+ * be known to the remote server, if it's of an older version. But keeping
+ * track of that would be a huge exercise.
+ */
+bool
+is_builtin(Oid objectId)
+{
+ return (objectId < FirstBootstrapObjectId);
+}
+
+/*
+ * is_shippable
+ * Is this object (function/operator/type) shippable to foreign server?
+ */
+bool
+is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
+{
+ ShippableCacheKey key;
+ ShippableCacheEntry *entry;
+
+ /* Built-in objects are presumed shippable. */
+ if (is_builtin(objectId))
+ return true;
+
+ /* Otherwise, give up if user hasn't specified any shippable extensions. */
+ if (fpinfo->shippable_extensions == NIL)
+ return false;
+
+ /* Initialize cache if first time through. */
+ if (!ShippableCacheHash)
+ InitializeShippableCache();
+
+ /* Set up cache hash key */
+ key.objid = objectId;
+ key.classid = classId;
+ key.serverid = fpinfo->server->serverid;
+
+ /* See if we already cached the result. */
+ entry = (ShippableCacheEntry *)
+ hash_search(ShippableCacheHash,
+ (void *) &key,
+ HASH_FIND,
+ NULL);
+
+ if (!entry)
+ {
+ /* Not found in cache, so perform shippability lookup. */
+ bool shippable = lookup_shippable(objectId, classId, fpinfo);
+
+ /*
+ * Don't create a new hash entry until *after* we have the shippable
+ * result in hand, as the underlying catalog lookups might trigger a
+ * cache invalidation.
+ */
+ entry = (ShippableCacheEntry *)
+ hash_search(ShippableCacheHash,
+ (void *) &key,
+ HASH_ENTER,
+ NULL);
+
+ entry->shippable = shippable;
+ }
+
+ return entry->shippable;
+}
</sect3>
+ <sect3>
+ <title>Remote Execution Options</title>
+
+ <para>
+ By default, only <literal>WHERE</> clauses using built-in operators and
+ functions will be considered for execution on the remote server. Clauses
+ involving non-built-in functions are checked locally after rows are
+ fetched. If such functions are available on the remote server and can be
+ relied on to produce the same results as they do locally, performance can
+ be improved by sending such <literal>WHERE</> clauses for remote
+ execution. This behavior can be controlled using the following option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>extensions</literal></term>
+ <listitem>
+ <para>
+ This option is a comma-separated list of names
+ of <productname>PostgreSQL</> extensions that are installed, in
+ compatible versions, on both the local and remote servers. Functions
+ and operators that are immutable and belong to a listed extension will
+ be considered shippable to the remote server.
+ This option can only be specified for foreign servers, not per-table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <para>
+ When using the <literal>extensions</literal> option, <emphasis>it is the
+ user's responsibility</> that the listed extensions exist and behave
+ identically on both the local and remote servers. Otherwise, remote
+ queries may fail or behave unexpectedly.
+ </para>
+
+ </sect3>
+
<sect3>
<title>Updatability Options</title>
execution, and by not retrieving table columns that are not needed for
the current query. To reduce the risk of misexecution of queries,
<literal>WHERE</> clauses are not sent to the remote server unless they use
- only built-in data types, operators, and functions. Operators and
- functions in the clauses must be <literal>IMMUTABLE</> as well.
+ only data types, operators, and functions that are built-in or belong to an
+ extension that's listed in the foreign server's <literal>extensions</>
+ option. Operators and functions in such clauses must
+ be <literal>IMMUTABLE</> as well.
</para>
<para>
}
/*
- * This version returns a name which is always qualified.
+ * This version returns a name that is always qualified (unless it's one
+ * of the SQL-keyword type names, such as TIMESTAMP WITH TIME ZONE).
*/
char *
format_type_be_qualified(Oid type_oid)
return format_type_internal(type_oid, typemod, true, false, false);
}
+/*
+ * This version allows a nondefault typemod to be specified, and forces
+ * qualification of normal type names.
+ */
+char *
+format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
+{
+ return format_type_internal(type_oid, typemod, true, false, true);
+}
+
+/*
+ * Common workhorse.
+ */
static char *
format_type_internal(Oid type_oid, int32 typemod,
bool typemod_given, bool allow_invalid,
extern char *format_type_be(Oid type_oid);
extern char *format_type_be_qualified(Oid type_oid);
extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
+extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
extern Datum oidvectortypes(PG_FUNCTION_ARGS);
extern int32 type_maximum_size(Oid type_oid, int32 typemod);