On Thu, Oct 1, 2015 at 7:57 AM, Paul Ramsey wrote: > On September 30, 2015 at 3:32:21 PM, Michael Paquier wrote: > > OK. Once you can get a new patch done with a reworked > extractExtensionList, I'll get a new look at it in a timely fashion > and then let's move it to a committer's hands.
So, I had a final look at that, and finished with the attached. I have done mainly cosmetic changes such as adjusting some error messages, correcting the documentation that still referred to FDW supporting the option "extensions", rewording things. Also, it seems to me that it makes more sense to move extractExtensionList in option.c. You have as well forgotten to remove a couple of things related to the previous FDW interface: - In InitializeShippableCache, there is no need to check for FOREIGNDATAWRAPPEROID - PgFdwRelationInfo does not need wrapper - postgresGetForeignRelSize does not need to set up the wrapper OID by calling GetForeignDataWrapper I also had shared feelings about exposing PgFdwRelationInfo in postgres_fdw.h, but your approach looks to be the cleanest way because the extension list is available in the global context through foreign_rel when checking the shippability of an expression. And I guess that we definitely do not want to pass the extension list in a bunch of routines, like appendWhereClause, is_foreign_expr, classifyConditions, etc. That would be bug-prone thinking long-term. This patch is switched as "ready for committer". Regards, -- Michael
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index d2b98e1..f78fc64 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) @@ -10,7 +10,9 @@ SHLIB_LINK = $(libpq) EXTENSION = postgres_fdw DATA = postgres_fdw--1.0.sql -REGRESS = postgres_fdw +# Note: shippable tests depend on postgres_fdw tests setup +REGRESS = postgres_fdw shippable +EXTRA_INSTALL = contrib/cube ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 697de60..4c06e66 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -233,6 +233,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; @@ -378,7 +381,8 @@ 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_shippable(fe->funcid, fpinfo->extensions)) return false; /* @@ -426,7 +430,8 @@ 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_shippable(oe->opno, fpinfo->extensions)) return false; /* @@ -466,7 +471,8 @@ 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_shippable(oe->opno, fpinfo->extensions)) return false; /* @@ -616,7 +622,9 @@ 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_shippable(exprType(node), fpinfo->extensions)) return false; /* @@ -1351,6 +1359,9 @@ deparseConst(Const *node, deparse_expr_cxt *context) bool isfloat = false; bool needlabel; + /* Access extension metadata from fpinfo on baserel */ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)(context->foreignrel->fdw_private); + if (node->constisnull) { appendStringInfoString(buf, "NULL"); @@ -1428,9 +1439,16 @@ deparseConst(Const *node, deparse_expr_cxt *context) break; } if (needlabel) + { + /* + * References to extension types need to be fully qualified, + * but references to built-in types shouldn't be. + */ appendStringInfo(buf, "::%s", - format_type_with_typemod(node->consttype, - node->consttypmod)); + is_shippable(node->consttype, fpinfo->extensions) ? + format_type_be_qualified(node->consttype) : + format_type_with_typemod(node->consttype, node->consttypmod)); + } } /* diff --git a/contrib/postgres_fdw/expected/shippable.out b/contrib/postgres_fdw/expected/shippable.out new file mode 100644 index 0000000..73afbea --- /dev/null +++ b/contrib/postgres_fdw/expected/shippable.out @@ -0,0 +1,139 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== +-- Error, extension isn't installed yet +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); +ERROR: required extension "cube" is not installed +HINT: Extension must be installed locally before it can be used on a remote server. +-- Try again +CREATE EXTENSION cube; +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); +ALTER SERVER loopback OPTIONS (DROP extensions); +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== +CREATE SCHEMA "SH 1"; +CREATE TABLE "SH 1"."TBL 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 cube, + c4 timestamptz +); +INSERT INTO "SH 1"."TBL 1" + SELECT id, + 2 * id, + cube(id,2*id), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval + FROM generate_series(1, 1000) id; +ANALYZE "SH 1"."TBL 1"; +-- =================================================================== +-- create foreign table +-- =================================================================== +CREATE FOREIGN TABLE shft1 ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 cube, + c4 timestamptz +) SERVER loopback +OPTIONS (schema_name 'SH 1', table_name 'TBL 1'); +-- =================================================================== +-- simple queries +-- =================================================================== +-- without operator shipping +EXPLAIN (COSTS false) SELECT * FROM shft1 LIMIT 1; + QUERY PLAN +----------------------------- + Limit + -> Foreign Scan on shft1 +(2 rows) + +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); + QUERY PLAN +--------------------------------------------------------------------- + Foreign Scan on public.shft1 (cost=100.00..205.06 rows=15 width=4) + Output: c2 + Filter: (shft1.c3 && '(1.5),(2.5)'::cube) + Remote SQL: SELECT c2, c3 FROM "SH 1"."TBL 1" +(4 rows) + +SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); + c2 +---- + 2 + 4 +(2 rows) + +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + QUERY PLAN +--------------------------------------------------------------------- + Foreign Scan on public.shft1 (cost=100.00..205.06 rows=15 width=4) + Output: c2 + Filter: (shft1.c3 && '(1.5),(2.5)'::cube) + Remote SQL: SELECT c2, c3 FROM "SH 1"."TBL 1" +(4 rows) + +-- with operator shipping +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.shft1 (cost=100.00..146.86 rows=15 width=4) + Output: c2 + Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube)) +(3 rows) + +SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); + c2 +---- + 2 + 4 +(2 rows) + +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.shft1 (cost=100.00..146.86 rows=15 width=4) + Output: c2 + Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube)) +(3 rows) + +EXPLAIN VERBOSE SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.shft1 (cost=100.00..128.43 rows=7 width=32) + Output: cube_dim(c3) + Remote SQL: SELECT c3 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube)) +(3 rows) + +SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + cube_dim +---------- + 1 + 1 +(2 rows) + +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2; + QUERY PLAN +------------------------------------------------------------------------------------- + Limit (cost=100.00..107.22 rows=2 width=4) + Output: c2 + -> Foreign Scan on public.shft1 (cost=100.00..154.18 rows=15 width=4) + Output: c2 + Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((public.cube_dim(c3) = 1)) +(5 rows) + +SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2; + c2 +---- + 2 + 4 +(2 rows) + +-- =================================================================== +-- clean up +-- =================================================================== +DROP FOREIGN TABLE shft1; +DROP TABLE "SH 1"."TBL 1"; +DROP SCHEMA "SH 1"; +DROP EXTENSION cube; +ALTER SERVER loopback OPTIONS (DROP extensions); diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 7547ec2..864bf53 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) + { + /* this must have already-installed extensions */ + (void) ExtractExtensionList(defGetString(def), false); + } } PG_RETURN_VOID(); @@ -153,6 +160,8 @@ InitPgFdwOptions(void) /* updatable is available on both server and table */ {"updatable", ForeignServerRelationId, false}, {"updatable", ForeignTableRelationId, false}, + /* extensions is available on server */ + {"extensions", ForeignServerRelationId, false}, {NULL, InvalidOid, false} }; @@ -293,3 +302,49 @@ ExtractConnectionOptions(List *defelems, const char **keywords, } return i; } + +/* + * Parse a comma-separated string and return a List of the Oids of the + * extensions in the string. If an extension provided cannot be looked + * up in the catalog (it hasn't been installed or doesn't exist) then + * throw up an error. + */ +List * +ExtractExtensionList(char *extensionString, bool populateList) +{ + List *extlist; + List *extensionOids = NIL; + ListCell *l; + + if (!SplitIdentifierString(extensionString, ',', &extlist)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid extension list syntax"))); + } + + foreach(l, extlist) + { + const char *extension_name = (const char *) lfirst(l); + Oid extension_oid = get_extension_oid(extension_name, true); + + if (!OidIsValid(extension_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("required extension \"%s\" is not installed", + extension_name), + errhint("Extension must be installed locally before it can be used on a remote server."))); + else if (populateList) + { + /* + * Only add this extension OID to the list if it is not already + * in included. + */ + if (!list_member_oid(extensionOids, extension_oid)) + extensionOids = lappend_oid(extensionOids, extension_oid); + } + } + + list_free(extlist); + return extensionOids; +} diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index e4d799c..2614777 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -47,39 +47,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. @@ -405,6 +372,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->extensions = NIL; foreach(lc, fpinfo->server->options) { @@ -416,6 +384,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->extensions = + ExtractExtensionList(defGetString(def), true); } foreach(lc, fpinfo->table->options) { diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 3835ddb..27f6dad 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -20,6 +20,43 @@ #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; + + /* Optional extensions to support (list of oid) */ + List *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 +74,11 @@ 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(char *extensionString, + bool populateList); + +/* in shippable.c */ +extern bool is_shippable(Oid procnumber, List *extension_list); /* in deparse.c */ extern void classifyConditions(PlannerInfo *root, diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c new file mode 100644 index 0000000..c889507 --- /dev/null +++ b/contrib/postgres_fdw/shippable.c @@ -0,0 +1,198 @@ +/*------------------------------------------------------------------------- + * + * shippable.c + * Facility to track database objects shippable to a foreign server. + * + * Determine if functions and operators for non-built-in types/functions/ops + * are shippable to the remote server. + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/shippable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "postgres_fdw.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_depend.h" +#include "utils/fmgroids.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +/* Hash table for informations about remote objects we'll call */ +static HTAB *ShippableCacheHash = NULL; + +/* objid is the lookup key, must appear first */ +typedef struct +{ + /* extension the object appears within, or InvalidOid if none */ + Oid objid; +} ShippableCacheKey; + +typedef struct +{ + /* lookup key - must be first */ + ShippableCacheKey key; + bool shippable; +} ShippableCacheEntry; + +/* + * Flush all cache entries when pg_foreign_server is updated. + */ +static void +InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + ShippableCacheEntry *entry; + + 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 cache of functions we can ship to remote server. + */ +static void +InitializeShippableCache(void) +{ + HASHCTL ctl; + + /* Initialize the hash table. */ + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(ShippableCacheKey); + ctl.entrysize = sizeof(ShippableCacheEntry); + ShippableCacheHash = + hash_create("Shippable cache", 256, &ctl, HASH_ELEM); + + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, + InvalidateShippableCacheCallback, + (Datum) 0); +} + +/* + * Returns true if given operator/function is part of an extension declared in + * the server options. + */ +static bool +lookup_shippable(Oid objnumber, List *extension_list) +{ + static int nkeys = 1; + ScanKeyData key[nkeys]; + HeapTuple tup; + Relation depRel; + SysScanDesc scan; + bool is_shippable = false; + + /* Always return false if we don't have any declared extensions */ + if (extension_list == NIL) + return false; + + /* We need this relation to scan */ + depRel = heap_open(DependRelationId, RowExclusiveLock); + + /* + * Scan the system dependency table for all entries this object + * depends on, then iterate through and see if one of them + * is an extension declared by the user in the options + */ + ScanKeyInit(&key[0], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objnumber)); + + 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_member_oid(extension_list, foundDep->refobjid)) + { + is_shippable = true; + break; + } + } + + systable_endscan(scan); + relation_close(depRel, RowExclusiveLock); + + return is_shippable; +} + +/* + * is_shippable + * Is this object (proc/op/type) shippable to foreign server? + * Check cache first, then look-up whether (proc/op/type) is + * part of a declared extension if it is not cached. + */ +bool +is_shippable(Oid objnumber, List *extension_list) +{ + ShippableCacheKey key; + ShippableCacheEntry *entry; + + /* Always return false if we don't have any declared extensions */ + if (extension_list == NIL) + return false; + + /* Find existing cache, if any. */ + if (!ShippableCacheHash) + InitializeShippableCache(); + + /* Zero out the key */ + memset(&key, 0, sizeof(key)); + + key.objid = objnumber; + + entry = (ShippableCacheEntry *) + hash_search(ShippableCacheHash, + (void *) &key, + HASH_FIND, + NULL); + + /* Not found in ShippableCacheHash cache. Construct new entry. */ + if (!entry) + { + /* + * Right now "shippability" is exclusively a function of whether + * the obj (proc/op/type) is in an extension declared by the user. + * In the future we could additionally have a whitelist of functions + * declared one at a time. + */ + bool shippable = lookup_shippable(objnumber, extension_list); + + entry = (ShippableCacheEntry *) + hash_search(ShippableCacheHash, + (void *) &key, + HASH_ENTER, + NULL); + + entry->shippable = shippable; + } + + if (!entry) + return false; + else + return entry->shippable; +} diff --git a/contrib/postgres_fdw/sql/shippable.sql b/contrib/postgres_fdw/sql/shippable.sql new file mode 100644 index 0000000..83ee38c --- /dev/null +++ b/contrib/postgres_fdw/sql/shippable.sql @@ -0,0 +1,76 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== + +-- Error, extension isn't installed yet +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); + +-- Try again +CREATE EXTENSION cube; +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); +ALTER SERVER loopback OPTIONS (DROP extensions); + + +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== + +CREATE SCHEMA "SH 1"; +CREATE TABLE "SH 1"."TBL 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 cube, + c4 timestamptz +); + +INSERT INTO "SH 1"."TBL 1" + SELECT id, + 2 * id, + cube(id,2*id), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval + FROM generate_series(1, 1000) id; + +ANALYZE "SH 1"."TBL 1"; + +-- =================================================================== +-- create foreign table +-- =================================================================== + +CREATE FOREIGN TABLE shft1 ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 cube, + c4 timestamptz +) SERVER loopback +OPTIONS (schema_name 'SH 1', table_name 'TBL 1'); + +-- =================================================================== +-- simple queries +-- =================================================================== + +-- without operator shipping +EXPLAIN (COSTS false) SELECT * FROM shft1 LIMIT 1; +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); +SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + +-- with operator shipping +ALTER SERVER loopback OPTIONS (ADD extensions 'cube'); +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); +SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5); +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; +EXPLAIN VERBOSE SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; +SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube; + +EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2; +SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2; + +-- =================================================================== +-- clean up +-- =================================================================== + +DROP FOREIGN TABLE shft1; +DROP TABLE "SH 1"."TBL 1"; +DROP SCHEMA "SH 1"; +DROP EXTENSION cube; +ALTER SERVER loopback OPTIONS (DROP extensions); diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index 7c92282..1e7ec08 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -373,6 +373,38 @@ 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 controls the list of extensions that are expected to be + installed on the foreign server, using a comma-separated list of + extension names. Those extensions are also expected to be installed + on the local server too. This option is available for servers. + </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