diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index d2b98e10f3a5e455886e6c605ae6e0bad5930799..354331247a82247ff7714b20c0ab87b8869850ec 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -1,7 +1,7 @@ # 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) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 3cb728fa693f6b2661bd3e46c2527893785c12a7..c8232f2c16f36866b015990d03542203233964ba 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -38,7 +38,6 @@ #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" @@ -102,7 +101,7 @@ typedef struct deparse_expr_cxt 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. @@ -220,11 +219,12 @@ is_foreign_expr(PlannerInfo *root, * 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, @@ -232,6 +232,7 @@ 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; @@ -240,6 +241,9 @@ foreign_expr_walker(Node *node, 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; @@ -377,11 +381,11 @@ foreign_expr_walker(Node *node, 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; /* @@ -425,11 +429,11 @@ foreign_expr_walker(Node *node, 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; /* @@ -467,9 +471,9 @@ foreign_expr_walker(Node *node, 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; /* @@ -616,10 +620,10 @@ 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 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; /* @@ -672,27 +676,23 @@ foreign_expr_walker(Node *node, } /* - * 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); } @@ -1358,8 +1358,8 @@ deparseConst(Const *node, deparse_expr_cxt *context) { appendStringInfoString(buf, "NULL"); appendStringInfo(buf, "::%s", - format_type_with_typemod(node->consttype, - node->consttypmod)); + deparse_type_name(node->consttype, + node->consttypmod)); return; } @@ -1432,8 +1432,8 @@ deparseConst(Const *node, deparse_expr_cxt *context) } if (needlabel) appendStringInfo(buf, "::%s", - format_type_with_typemod(node->consttype, - node->consttypmod)); + deparse_type_name(node->consttype, + node->consttypmod)); } /* @@ -1558,7 +1558,7 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) deparseExpr((Expr *) linitial(node->args), context); appendStringInfo(buf, "::%s", - format_type_with_typemod(rettype, coercedTypmod)); + deparse_type_name(rettype, coercedTypmod)); return; } @@ -1753,8 +1753,8 @@ deparseRelabelType(RelabelType *node, deparse_expr_cxt *context) 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)); } /* @@ -1834,7 +1834,7 @@ deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) /* 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)); } /* @@ -1850,7 +1850,7 @@ printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, 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); } @@ -1876,7 +1876,7 @@ printRemotePlaceholder(Oid paramtype, int32 paramtypmod, 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); } @@ -1890,10 +1890,10 @@ void 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; @@ -1907,8 +1907,8 @@ appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, 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); diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 7547ec28172e057f317a8cc782a6c795f09ad091..380ac80ab3a3c6b6323079ce05cd3279e5819137 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" /* @@ -124,6 +126,11 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) 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(); @@ -150,6 +157,8 @@ InitPgFdwOptions(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}, @@ -293,3 +302,48 @@ ExtractConnectionOptions(List *defelems, const char **keywords, } 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; +} diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 914e6704c26de4b715aae5b3adf4be516a1aad3d..cd4ed0c94dbc8e9ce3b2d1c3ea9f7c00b7fb1ce9 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -50,40 +50,6 @@ PG_MODULE_MAGIC; /* 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. * @@ -409,6 +375,7 @@ postgresGetForeignRelSize(PlannerInfo *root, 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) { @@ -420,6 +387,9 @@ 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) + fpinfo->shippable_extensions = + ExtractExtensionList(defGetString(def), false); } foreach(lc, fpinfo->table->options) { diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 8956cd2cf1c404020cc63b28a1534ce86ea3dab1..f243de8d6237822558422d76219c697490600547 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -20,6 +20,41 @@ #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); @@ -37,6 +72,8 @@ extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn, 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, @@ -76,6 +113,10 @@ extern void deparseAnalyzeSql(StringInfo buf, Relation rel, 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 */ diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c new file mode 100644 index 0000000000000000000000000000000000000000..ebb1f5e31770ccf99b6baf3b3b6fa42cef42c612 --- /dev/null +++ b/contrib/postgres_fdw/shippable.c @@ -0,0 +1,214 @@ +/*------------------------------------------------------------------------- + * + * 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; +} diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index 7c922821e988ffde8711e8323ff310c8166187d4..5a829d537a69816a31c645442866aa33303ee1d6 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -264,6 +264,46 @@ </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> @@ -427,8 +467,10 @@ 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> diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index a8519835cc66215aebd193ead8f682438c1d0d12..e046f05f28c659c0c988e429f3100fdecedb31ff 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -97,7 +97,8 @@ format_type_be(Oid type_oid) } /* - * 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) @@ -114,6 +115,19 @@ format_type_with_typemod(Oid type_oid, int32 typemod) 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, diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index fc1679ed462bbe602010f412a952463524e983d2..c193e4425ed9864cf2db82fec1ed90f8939a79bc 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1105,6 +1105,7 @@ extern Datum format_type(PG_FUNCTION_ARGS); 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);