diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index d2b98e10f3a5e..dc164ee6e5735 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,10 @@ 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 +# Note: shippable tests require cube and seg +EXTRA_INSTALL = contrib/cube contrib/seg ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 697de60dfe51b..aa575b39ed078 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, ProcedureRelationId, fpinfo->server, 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, OperatorRelationId, fpinfo->server, 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, OperatorRelationId, fpinfo->server, 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), TypeRelationId, fpinfo->server, 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,21 @@ deparseConst(Const *node, deparse_expr_cxt *context) break; } if (needlabel) - appendStringInfo(buf, "::%s", - format_type_with_typemod(node->consttype, - node->consttypmod)); + { + /* + * References to extension types need to be fully qualified, + * but references to built-in types shouldn't be. + */ + if (!is_builtin(node->consttype) && + is_shippable(node->consttype, TypeRelationId, fpinfo->server, fpinfo->extensions)) + { + appendStringInfo(buf, "::%s", format_type_be_qualified(node->consttype)); + } + else + { + appendStringInfo(buf, "::%s", 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 0000000000000..abf924efe4ae4 --- /dev/null +++ b/contrib/postgres_fdw/expected/shippable.out @@ -0,0 +1,225 @@ +-- =================================================================== +-- 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) + +-- =================================================================== +-- add a second server with different extension shipping +-- =================================================================== +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_two FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_two; +CREATE EXTENSION seg; +CREATE TABLE seg_local ( + id integer, + s seg, + n text +); +INSERT INTO seg_local (id, s, n) VALUES (1, '1.0 .. 2.0', 'foo'); +INSERT INTO seg_local (id, s, n) VALUES (2, '3.0 .. 4.0', 'bar'); +INSERT INTO seg_local (id, s, n) VALUES (3, '5.0 .. 6.0', 'baz'); +ANALYZE seg_local; +CREATE FOREIGN TABLE seg_remote_two ( + id integer, + s seg, + n text +) SERVER loopback_two +OPTIONS (table_name 'seg_local'); +SELECT id FROM seg_local WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + id +---- + 3 +(1 row) + +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan on public.seg_remote_two (cost=100.00..157.88 rows=1 width=4) + Output: id + Filter: (seg_remote_two.s && '5.8 .. 6.2'::seg) + Remote SQL: SELECT id, s FROM public.seg_local WHERE ((n = 'baz'::text)) +(4 rows) + +ALTER SERVER loopback_two OPTIONS (ADD extensions 'seg'); +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.seg_remote_two (cost=100.00..153.89 rows=1 width=4) + Output: id + Remote SQL: SELECT id FROM public.seg_local WHERE ((s OPERATOR(public.&&) '5.8 .. 6.2'::public.seg)) AND ((n = 'baz'::text)) +(3 rows) + +CREATE FOREIGN TABLE seg_remote_one ( + id integer, + s seg, + n text +) SERVER loopback +OPTIONS (table_name 'seg_local'); +SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + id +---- + 3 +(1 row) + +EXPLAIN VERBOSE SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan on public.seg_remote_one (cost=100.00..157.88 rows=1 width=4) + Output: id + Filter: (seg_remote_one.s && '5.8 .. 6.2'::seg) + Remote SQL: SELECT id, s FROM public.seg_local WHERE ((n = 'baz'::text)) +(4 rows) + +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.seg_remote_two (cost=100.00..153.89 rows=1 width=4) + Output: id + Remote SQL: SELECT id FROM public.seg_local WHERE ((s OPERATOR(public.&&) '5.8 .. 6.2'::public.seg)) AND ((n = 'baz'::text)) +(3 rows) + +-- =================================================================== +-- clean up +-- =================================================================== +DROP FOREIGN TABLE seg_remote_one, seg_remote_two; +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_two; +DROP SERVER loopback_two; +DROP TABLE seg_local; +DROP FOREIGN TABLE shft1; +DROP TABLE "SH 1"."TBL 1"; +DROP SCHEMA "SH 1"; +DROP EXTENSION cube; +DROP EXTENSION seg; +ALTER SERVER loopback OPTIONS (DROP extensions); diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 7547ec28172e0..a20f626f62dfa 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 that the requested extensions are actually installed */ + (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" option 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 + * raise 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 e4d799cecd541..26147771199d7 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 3835ddb79ac61..f30d9d153f95d 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 Oids). */ + 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 objnumber, Oid classnumber, ForeignServer *server, 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 0000000000000..960a7eccc51e0 --- /dev/null +++ b/contrib/postgres_fdw/shippable.c @@ -0,0 +1,211 @@ +/*------------------------------------------------------------------------- + * + * 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 serverid; + Oid classid; + 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 listed in + * the server options. + */ +static bool +lookup_shippable(Oid objnumber, Oid classnumber, List *extension_list) +{ + static int nkeys = 2; + ScanKeyData key[nkeys]; + HeapTuple tup; + Relation depRel; + SysScanDesc scan; + bool is_shippable = false; + + /* Always return false if the user hasn't set the "extensions" option */ + if (extension_list == NIL) + return false; + + 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_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classnumber)); + + ScanKeyInit(&key[1], + 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, Oid classnumber, ForeignServer *server, List *extension_list) +{ + ShippableCacheKey key; + ShippableCacheEntry *entry; + + /* Always return false if the user hasn't set the "extensions" option */ + 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; + key.classid = classnumber; + key.serverid = server->serverid; + + 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, classnumber, extension_list); + + /* + * Don't create a new hash entry until *after* we have the shippable + * result in hand, as the shippable lookup might trigger a cache + * invalidation. + */ + 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 0000000000000..d3e03c219bcda --- /dev/null +++ b/contrib/postgres_fdw/sql/shippable.sql @@ -0,0 +1,133 @@ +-- =================================================================== +-- 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; + +-- =================================================================== +-- add a second server with different extension shipping +-- =================================================================== + +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_two FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_two; + +CREATE EXTENSION seg; + +CREATE TABLE seg_local ( + id integer, + s seg, + n text +); + +INSERT INTO seg_local (id, s, n) VALUES (1, '1.0 .. 2.0', 'foo'); +INSERT INTO seg_local (id, s, n) VALUES (2, '3.0 .. 4.0', 'bar'); +INSERT INTO seg_local (id, s, n) VALUES (3, '5.0 .. 6.0', 'baz'); + +ANALYZE seg_local; + +CREATE FOREIGN TABLE seg_remote_two ( + id integer, + s seg, + n text +) SERVER loopback_two +OPTIONS (table_name 'seg_local'); + +SELECT id FROM seg_local WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; +ALTER SERVER loopback_two OPTIONS (ADD extensions 'seg'); +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + +CREATE FOREIGN TABLE seg_remote_one ( + id integer, + s seg, + n text +) SERVER loopback +OPTIONS (table_name 'seg_local'); + +SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; +EXPLAIN VERBOSE SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; +EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz'; + + +-- =================================================================== +-- clean up +-- =================================================================== +DROP FOREIGN TABLE seg_remote_one, seg_remote_two; +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_two; +DROP SERVER loopback_two; +DROP TABLE seg_local; +DROP FOREIGN TABLE shft1; +DROP TABLE "SH 1"."TBL 1"; +DROP SCHEMA "SH 1"; +DROP EXTENSION cube; +DROP EXTENSION seg; +ALTER SERVER loopback OPTIONS (DROP extensions); diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index 7c922821e988f..1e7ec08c9b9db 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -373,6 +373,38 @@ foreign tables, see . + + + Extension Options + + + 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: + + + + + + extensions + + + 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. + + +CREATE SERVER foreign_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host '127.0.0.1', port '5432', dbname 'my_db', extensions 'cube, seg'); + + + + + + + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy