diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index fa9ee716067..fdadfb06ef9 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7121,6 +7121,33 @@ RESET enable_material; DROP FOREIGN TABLE remt2; DROP TABLE loct1; DROP TABLE loct2; +-- Test non-pushed-down UPDATE/DELETE on partitioned/inherited foreign tables +CREATE TABLE plt (a int, b int) PARTITION BY LIST(a); +CREATE TABLE plt_p1 PARTITION OF plt FOR VALUES IN (1); +CREATE TABLE plt_p2 PARTITION OF plt FOR VALUES IN (2); +CREATE FOREIGN TABLE inh_ft (a int, b int) SERVER loopback OPTIONS (table_name 'plt', inherited 'true'); +INSERT INTO inh_ft VALUES (1, 1), (2, 2); +SELECT tableoid::regclass, ctid, * FROM inh_ft; + tableoid | ctid | a | b +----------+-------+---+--- + inh_ft | (0,1) | 1 | 1 + inh_ft | (0,1) | 2 | 2 +(2 rows) + +-- Use random() so that UPDATE/DELETE is not pushed down to the remote +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE inh_ft SET b = (CASE WHEN random() <= 1 THEN 10 ELSE 100 END) WHERE a = 1; -- should fail +ERROR: cannot update foreign table "inh_ft" +UPDATE inh_ft SET b = (CASE WHEN random() <= 1 THEN 10 ELSE 100 END) WHERE a = 1; -- should fail +ERROR: cannot update foreign table "inh_ft" +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM inh_ft WHERE a = (CASE WHEN random() <= 1 THEN 1 ELSE 10 END); -- should fail +ERROR: cannot delete from foreign table "inh_ft" +DELETE FROM inh_ft WHERE a = (CASE WHEN random() <= 1 THEN 1 ELSE 10 END); -- should fail +ERROR: cannot delete from foreign table "inh_ft" +-- Cleanup +DROP FOREIGN TABLE inh_ft; +DROP TABLE plt; -- =================================================================== -- test check constraints -- =================================================================== diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index c2f936640bc..85b40b948c3 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -119,7 +119,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) /* * Validate option value, when we can do so without any context. */ - if (strcmp(def->defname, "use_remote_estimate") == 0 || + if (strcmp(def->defname, "inherited") == 0 || + strcmp(def->defname, "use_remote_estimate") == 0 || strcmp(def->defname, "updatable") == 0 || strcmp(def->defname, "truncatable") == 0 || strcmp(def->defname, "async_capable") == 0 || @@ -247,6 +248,7 @@ InitPgFdwOptions(void) {"schema_name", ForeignTableRelationId, false}, {"table_name", ForeignTableRelationId, false}, {"column_name", AttributeRelationId, false}, + {"inherited", ForeignTableRelationId, false}, /* use_remote_estimate is available on both server and table */ {"use_remote_estimate", ForeignServerRelationId, false}, {"use_remote_estimate", ForeignTableRelationId, false}, diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index e0a34b27c7c..e5d186dc62c 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -649,6 +649,7 @@ postgresGetForeignRelSize(PlannerInfo *root, * use_remote_estimate, fetch_size and async_capable override per-server * settings of them, respectively. */ + fpinfo->inherited = false; fpinfo->use_remote_estimate = false; fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; @@ -1794,6 +1795,28 @@ postgresPlanForeignModify(PlannerInfo *root, bool doNothing = false; int values_end_len = -1; + /* + * We don't currently support update/delete on partitioned/inherited + * foreign tables via ExecForeignUpdate()/ExecForeignDelete(); if the + * operation is update/delete, check whether the foreign table is + * partitioned/inherited or not, and if so, throw an error. Note that + * we would have already determined that the operation on the foreign + * table cannot be executed via DirectModify before we get here. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + RelOptInfo *rel = find_base_rel(root, resultRelation); + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + + if (fpinfo && fpinfo->inherited) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg((operation == CMD_UPDATE) ? + "cannot update foreign table \"%s\"" : + "cannot delete from foreign table \"%s\"", + get_rel_name(rte->relid)))); + } + initStringInfo(&sql); /* @@ -6292,7 +6315,9 @@ apply_table_options(PgFdwRelationInfo *fpinfo) { DefElem *def = (DefElem *) lfirst(lc); - if (strcmp(def->defname, "use_remote_estimate") == 0) + if (strcmp(def->defname, "inherited") == 0) + fpinfo->inherited = defGetBoolean(def); + else if (strcmp(def->defname, "use_remote_estimate") == 0) fpinfo->use_remote_estimate = defGetBoolean(def); else if (strcmp(def->defname, "fetch_size") == 0) (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); @@ -6414,6 +6439,8 @@ postgresGetForeignJoinPaths(PlannerInfo *root, joinrel->fdw_private = fpinfo; /* attrs_used is only for base relations. */ fpinfo->attrs_used = NULL; + /* inherited is only for base relations. */ + fpinfo->inherited = false; /* * If there is a possibility that EvalPlanQual will be executed, we need @@ -6780,6 +6807,8 @@ postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); fpinfo->pushdown_safe = false; + /* inherited is only for base relations. */ + fpinfo->inherited = false; fpinfo->stage = stage; output_rel->fdw_private = fpinfo; diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 81358f3bde7..7163b2966dd 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -76,10 +76,12 @@ typedef struct PgFdwRelationInfo Cost rel_total_cost; /* Options extracted from catalogs. */ + bool inherited; bool use_remote_estimate; Cost fdw_startup_cost; Cost fdw_tuple_cost; List *shippable_extensions; /* OIDs of shippable extensions */ + int fetch_size; /* fetch size for this remote table */ bool async_capable; /* Cached catalog information. */ @@ -87,8 +89,6 @@ typedef struct PgFdwRelationInfo ForeignServer *server; UserMapping *user; /* only set in use_remote_estimate mode */ - int fetch_size; /* fetch size for this remote table */ - /* * Name of the relation, for use while EXPLAINing ForeignScan. It is used * for join and upper relations but is set for all relations. For a base diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index b449e122abe..f447eba29dd 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1725,6 +1725,28 @@ DROP FOREIGN TABLE remt2; DROP TABLE loct1; DROP TABLE loct2; +-- Test non-pushed-down UPDATE/DELETE on partitioned/inherited foreign tables +CREATE TABLE plt (a int, b int) PARTITION BY LIST(a); +CREATE TABLE plt_p1 PARTITION OF plt FOR VALUES IN (1); +CREATE TABLE plt_p2 PARTITION OF plt FOR VALUES IN (2); +CREATE FOREIGN TABLE inh_ft (a int, b int) SERVER loopback OPTIONS (table_name 'plt', inherited 'true'); + +INSERT INTO inh_ft VALUES (1, 1), (2, 2); +SELECT tableoid::regclass, ctid, * FROM inh_ft; + +-- Use random() so that UPDATE/DELETE is not pushed down to the remote +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE inh_ft SET b = (CASE WHEN random() <= 1 THEN 10 ELSE 100 END) WHERE a = 1; -- should fail +UPDATE inh_ft SET b = (CASE WHEN random() <= 1 THEN 10 ELSE 100 END) WHERE a = 1; -- should fail + +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM inh_ft WHERE a = (CASE WHEN random() <= 1 THEN 1 ELSE 10 END); -- should fail +DELETE FROM inh_ft WHERE a = (CASE WHEN random() <= 1 THEN 1 ELSE 10 END); -- should fail + +-- Cleanup +DROP FOREIGN TABLE inh_ft; +DROP TABLE plt; + -- =================================================================== -- test check constraints -- ===================================================================