From dc3eb5638349e74a6628130a5101ce866455f4a3 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Wed, 12 Jun 2013 17:52:54 -0400 Subject: [PATCH] Improve updatability checking for views and foreign tables. Extend the FDW API (which we already changed for 9.3) so that an FDW can report whether specific foreign tables are insertable/updatable/deletable. The default assumption continues to be that they're updatable if the relevant executor callback function is supplied by the FDW, but finer granularity is now possible. As a test case, add an "updatable" option to contrib/postgres_fdw. This patch also fixes the information_schema views, which previously did not think that foreign tables were ever updatable, and fixes view_is_auto_updatable() so that a view on a foreign table can be auto-updatable. initdb forced due to changes in information_schema views and the functions they rely on. This is a bit unfortunate to do post-beta1, but if we don't change this now then we'll have another API break for FDWs when we do change it. Dean Rasheed, somewhat editorialized on by Tom Lane --- .../postgres_fdw/expected/postgres_fdw.out | 1 + contrib/postgres_fdw/option.c | 8 +- contrib/postgres_fdw/postgres_fdw.c | 47 ++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 1 + doc/src/sgml/fdwhandler.sgml | 27 +++++ doc/src/sgml/postgres-fdw.sgml | 37 +++++++ src/backend/catalog/information_schema.sql | 15 ++- src/backend/executor/execMain.c | 18 ++++ src/backend/rewrite/rewriteHandler.c | 102 +++++++++++++----- src/backend/utils/adt/misc.c | 51 ++++++--- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.h | 8 +- src/include/foreign/fdwapi.h | 3 + src/include/rewrite/rewriteHandler.h | 2 +- src/include/utils/builtins.h | 4 +- 15 files changed, 272 insertions(+), 54 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 7a13d011d5c..38c6cf81623 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -79,6 +79,7 @@ ALTER FOREIGN TABLE ft2 DROP COLUMN cx; -- configure options ALTER SERVER testserver1 OPTIONS ( use_remote_estimate 'false', + updatable 'true', fdw_startup_cost '123.456', fdw_tuple_cost '0.123', service 'value', diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 123cb4f0104..e1d4c477339 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -106,9 +106,10 @@ 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, "use_remote_estimate") == 0 || + strcmp(def->defname, "updatable") == 0) { - /* use_remote_estimate accepts only boolean values */ + /* these accept only boolean values */ (void) defGetBoolean(def); } else if (strcmp(def->defname, "fdw_startup_cost") == 0 || @@ -151,6 +152,9 @@ InitPgFdwOptions(void) /* cost factors */ {"fdw_startup_cost", ForeignServerRelationId, false}, {"fdw_tuple_cost", ForeignServerRelationId, false}, + /* updatable is available on both server and table */ + {"updatable", ForeignServerRelationId, false}, + {"updatable", ForeignTableRelationId, false}, {NULL, InvalidOid, false} }; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index cbfecc4dd42..1c93e0c5ac3 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -277,6 +277,7 @@ static TupleTableSlot *postgresExecForeignDelete(EState *estate, TupleTableSlot *planSlot); static void postgresEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo); +static int postgresIsForeignRelUpdatable(Relation rel); static void postgresExplainForeignScan(ForeignScanState *node, ExplainState *es); static void postgresExplainForeignModify(ModifyTableState *mtstate, @@ -355,6 +356,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS) routine->ExecForeignUpdate = postgresExecForeignUpdate; routine->ExecForeignDelete = postgresExecForeignDelete; routine->EndForeignModify = postgresEndForeignModify; + routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable; /* Support functions for EXPLAIN */ routine->ExplainForeignScan = postgresExplainForeignScan; @@ -1596,6 +1598,51 @@ postgresEndForeignModify(EState *estate, fmstate->conn = NULL; } +/* + * postgresIsForeignRelUpdatable + * Determine whether a foreign table supports INSERT, UPDATE and/or + * DELETE. + */ +static int +postgresIsForeignRelUpdatable(Relation rel) +{ + bool updatable; + ForeignTable *table; + ForeignServer *server; + ListCell *lc; + + /* + * By default, all postgres_fdw foreign tables are assumed updatable. This + * can be overridden by a per-server setting, which in turn can be + * overridden by a per-table setting. + */ + updatable = true; + + table = GetForeignTable(RelationGetRelid(rel)); + server = GetForeignServer(table->serverid); + + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "updatable") == 0) + updatable = defGetBoolean(def); + } + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "updatable") == 0) + updatable = defGetBoolean(def); + } + + /* + * Currently "updatable" means support for INSERT, UPDATE and DELETE. + */ + return updatable ? + (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0; +} + /* * postgresExplainForeignScan * Produce extra output for EXPLAIN of a ForeignScan on a foreign table diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 19221680bf1..ce8bb7597ba 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -88,6 +88,7 @@ ALTER FOREIGN TABLE ft2 DROP COLUMN cx; -- configure options ALTER SERVER testserver1 OPTIONS ( use_remote_estimate 'false', + updatable 'true', fdw_startup_cost '123.456', fdw_tuple_cost '0.123', service 'value', diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index 912ca8663ef..6c06f1a4367 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -565,6 +565,33 @@ EndForeignModify (EState *estate, <literal>NULL</>, no action is taken during executor shutdown. </para> + <para> +<programlisting> +int +IsForeignRelUpdatable (Relation rel); +</programlisting> + + Report which update operations the specified foreign table supports. + The return value should be a bitmask of rule event numbers indicating + which operations are supported by the foreign table, using the + <literal>CmdType</> enumeration; that is, + <literal>(1 << CMD_UPDATE) = 4</> for <command>UPDATE</>, + <literal>(1 << CMD_INSERT) = 8</> for <command>INSERT</>, and + <literal>(1 << CMD_DELETE) = 16</> for <command>DELETE</>. + </para> + + <para> + If the <function>IsForeignRelUpdatable</> pointer is set to + <literal>NULL</>, foreign tables are assumed to be insertable, updatable, + or deletable if the FDW provides <function>ExecForeignInsert</>, + <function>ExecForeignUpdate</>, or <function>ExecForeignDelete</> + respectively. This function is only needed if the FDW supports some + tables that are updatable and some that are not. (Even then, it's + permissible to throw an error in the execution routine instead of + checking in this function. However, this function is used to determine + updatability for display in the <literal>information_schema</> views.) + </para> + </sect2> <sect2 id="fdw-callbacks-explain"> diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index a1c3bebb097..35924f19f26 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -254,6 +254,43 @@ </para> </sect3> + + <sect3> + <title>Updatability Options</title> + + <para> + By default all foreign tables using <filename>postgres_fdw</> are assumed + to be updatable. This may be overridden using the following option: + </para> + + <variablelist> + + <varlistentry> + <term><literal>updatable</literal></term> + <listitem> + <para> + This option controls whether <filename>postgres_fdw</> allows foreign + tables to be modified using <command>INSERT</>, <command>UPDATE</> and + <command>DELETE</> commands. It can be specified for a foreign table + or a foreign server. A table-level option overrides a server-level + option. + The default is <literal>true</>. + </para> + + <para> + Of course, if the remote table is not in fact updatable, an error + would occur anyway. Use of this option primarily allows the error to + be thrown locally without querying the remote server. Note however + that the <literal>information_schema</> views will report a + <filename>postgres_fdw</> foreign table to be updatable (or not) + according to the setting of this option, without any check of the + remote server. + </para> + </listitem> + </varlistentry> + + </variablelist> + </sect3> </sect2> <sect2> diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 230758654cc..e1f8e7f4b1c 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -731,7 +731,8 @@ CREATE VIEW columns AS CAST(null AS character_data) AS generation_expression, CAST(CASE WHEN c.relkind = 'r' OR - (c.relkind = 'v' AND pg_view_is_updatable(c.oid)) + (c.relkind IN ('v', 'f') AND + pg_column_is_updatable(c.oid, a.attnum, false)) THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable FROM (pg_attribute a LEFT JOIN pg_attrdef ad ON attrelid = adrelid AND attnum = adnum) @@ -1895,7 +1896,9 @@ CREATE VIEW tables AS CAST(t.typname AS sql_identifier) AS user_defined_type_name, CAST(CASE WHEN c.relkind = 'r' OR - (c.relkind = 'v' AND pg_view_is_insertable(c.oid)) + (c.relkind IN ('v', 'f') AND + -- 1 << CMD_INSERT + pg_relation_is_updatable(c.oid, false) & 8 = 8) THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into, CAST(CASE WHEN t.typname IS NOT NULL THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_typed, @@ -2494,11 +2497,15 @@ CREATE VIEW views AS CAST('NONE' AS character_data) AS check_option, CAST( - CASE WHEN pg_view_is_updatable(c.oid) THEN 'YES' ELSE 'NO' END + -- (1 << CMD_UPDATE) + (1 << CMD_DELETE) + CASE WHEN pg_relation_is_updatable(c.oid, false) & 20 = 20 + THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable, CAST( - CASE WHEN pg_view_is_insertable(c.oid) THEN 'YES' ELSE 'NO' END + -- 1 << CMD_INSERT + CASE WHEN pg_relation_is_updatable(c.oid, false) & 8 = 8 + THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into, CAST( diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 9b0cd8c2070..3b664d09265 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1015,6 +1015,12 @@ CheckValidResultRel(Relation resultRel, CmdType operation) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot insert into foreign table \"%s\"", RelationGetRelationName(resultRel)))); + if (fdwroutine->IsForeignRelUpdatable != NULL && + (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("foreign table \"%s\" does not allow inserts", + RelationGetRelationName(resultRel)))); break; case CMD_UPDATE: if (fdwroutine->ExecForeignUpdate == NULL) @@ -1022,6 +1028,12 @@ CheckValidResultRel(Relation resultRel, CmdType operation) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot update foreign table \"%s\"", RelationGetRelationName(resultRel)))); + if (fdwroutine->IsForeignRelUpdatable != NULL && + (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_UPDATE)) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("foreign table \"%s\" does not allow updates", + RelationGetRelationName(resultRel)))); break; case CMD_DELETE: if (fdwroutine->ExecForeignDelete == NULL) @@ -1029,6 +1041,12 @@ CheckValidResultRel(Relation resultRel, CmdType operation) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot delete from foreign table \"%s\"", RelationGetRelationName(resultRel)))); + if (fdwroutine->IsForeignRelUpdatable != NULL && + (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_DELETE)) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("foreign table \"%s\" does not allow deletes", + RelationGetRelationName(resultRel)))); break; default: elog(ERROR, "unrecognized CmdType: %d", (int) operation); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 01875fcd45f..a467588e50e 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -2014,6 +2014,7 @@ view_is_auto_updatable(Relation view) base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); if (base_rte->rtekind != RTE_RELATION || (base_rte->relkind != RELKIND_RELATION && + base_rte->relkind != RELKIND_FOREIGN_TABLE && base_rte->relkind != RELKIND_VIEW)) return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); @@ -2058,49 +2059,56 @@ view_is_auto_updatable(Relation view) /* - * relation_is_updatable - test if the specified relation is updatable. + * relation_is_updatable - determine which update events the specified + * relation supports. * * This is used for the information_schema views, which have separate concepts * of "updatable" and "trigger updatable". A relation is "updatable" if it * can be updated without the need for triggers (either because it has a * suitable RULE, or because it is simple enough to be automatically updated). - * * A relation is "trigger updatable" if it has a suitable INSTEAD OF trigger. * The SQL standard regards this as not necessarily updatable, presumably * because there is no way of knowing what the trigger will actually do. - * That's currently handled directly in the information_schema views, so - * need not be considered here. - * - * In the case of an automatically updatable view, the base relation must - * also be updatable. + * The information_schema views therefore call this function with + * include_triggers = false. However, other callers might only care whether + * data-modifying SQL will work, so they can pass include_triggers = true + * to have trigger updatability included in the result. * - * reloid is the pg_class OID to examine. req_events is a bitmask of - * rule event numbers; the relation is considered rule-updatable if it has - * all the specified rules. (We do it this way so that we can test for - * UPDATE plus DELETE rules in a single call.) + * The return value is a bitmask of rule event numbers indicating which of + * the INSERT, UPDATE and DELETE operations are supported. (We do it this way + * so that we can test for UPDATE plus DELETE support in a single call.) */ -bool -relation_is_updatable(Oid reloid, int req_events) +int +relation_is_updatable(Oid reloid, bool include_triggers) { + int events = 0; Relation rel; RuleLock *rulelocks; +#define ALL_EVENTS ((1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE)) + rel = try_relation_open(reloid, AccessShareLock); /* - * If the relation doesn't exist, say "false" rather than throwing an + * If the relation doesn't exist, return zero rather than throwing an * error. This is helpful since scanning an information_schema view under * MVCC rules can result in referencing rels that were just deleted * according to a SnapshotNow probe. */ if (rel == NULL) - return false; + return 0; + + /* If the relation is a table, it is always updatable */ + if (rel->rd_rel->relkind == RELKIND_RELATION) + { + relation_close(rel, AccessShareLock); + return ALL_EVENTS; + } /* Look for unconditional DO INSTEAD rules, and note supported events */ rulelocks = rel->rd_rules; if (rulelocks != NULL) { - int events = 0; int i; for (i = 0; i < rulelocks->numLocks; i++) @@ -2108,16 +2116,61 @@ relation_is_updatable(Oid reloid, int req_events) if (rulelocks->rules[i]->isInstead && rulelocks->rules[i]->qual == NULL) { - events |= 1 << rulelocks->rules[i]->event; + events |= ((1 << rulelocks->rules[i]->event) & ALL_EVENTS); } } - /* If we have all rules needed, say "yes" */ - if ((events & req_events) == req_events) + /* If we have rules for all events, we're done */ + if (events == ALL_EVENTS) { relation_close(rel, AccessShareLock); - return true; + return events; + } + } + + /* Similarly look for INSTEAD OF triggers, if they are to be included */ + if (include_triggers) + { + TriggerDesc *trigDesc = rel->trigdesc; + + if (trigDesc) + { + if (trigDesc->trig_insert_instead_row) + events |= (1 << CMD_INSERT); + if (trigDesc->trig_update_instead_row) + events |= (1 << CMD_UPDATE); + if (trigDesc->trig_delete_instead_row) + events |= (1 << CMD_DELETE); + + /* If we have triggers for all events, we're done */ + if (events == ALL_EVENTS) + { + relation_close(rel, AccessShareLock); + return events; + } + } + } + + /* If this is a foreign table, check which update events it supports */ + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false); + + if (fdwroutine->IsForeignRelUpdatable != NULL) + events |= fdwroutine->IsForeignRelUpdatable(rel); + else + { + /* Assume presence of executor functions is sufficient */ + if (fdwroutine->ExecForeignInsert != NULL) + events |= (1 << CMD_INSERT); + if (fdwroutine->ExecForeignUpdate != NULL) + events |= (1 << CMD_UPDATE); + if (fdwroutine->ExecForeignDelete != NULL) + events |= (1 << CMD_DELETE); } + + relation_close(rel, AccessShareLock); + return events; } /* Check if this is an automatically updatable view */ @@ -2133,25 +2186,26 @@ relation_is_updatable(Oid reloid, int req_events) viewquery = get_view_query(rel); rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + Assert(base_rte->rtekind == RTE_RELATION); if (base_rte->relkind == RELKIND_RELATION) { /* Tables are always updatable */ relation_close(rel, AccessShareLock); - return true; + return ALL_EVENTS; } else { /* Do a recursive check for any other kind of base relation */ baseoid = base_rte->relid; relation_close(rel, AccessShareLock); - return relation_is_updatable(baseoid, req_events); + return relation_is_updatable(baseoid, include_triggers); } } - /* If we reach here, the relation is not updatable */ + /* If we reach here, the relation may support some update commands */ relation_close(rel, AccessShareLock); - return false; + return events; } diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index 829ce59888c..bf06ec048ff 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -528,30 +528,49 @@ pg_collation_for(PG_FUNCTION_ARGS) /* - * information_schema support functions + * pg_relation_is_updatable - determine which update events the specified + * relation supports. * - * Test whether a view (identified by pg_class OID) is insertable-into or - * updatable. The latter requires delete capability too. This is an - * artifact of the way the SQL standard defines the information_schema views: - * if we defined separate functions for update and delete, we'd double the - * work required to compute the view columns. - * - * These rely on relation_is_updatable(), which is in rewriteHandler.c. + * This relies on relation_is_updatable() in rewriteHandler.c, which see + * for additional information. */ Datum -pg_view_is_insertable(PG_FUNCTION_ARGS) +pg_relation_is_updatable(PG_FUNCTION_ARGS) { - Oid viewoid = PG_GETARG_OID(0); - int req_events = (1 << CMD_INSERT); + Oid reloid = PG_GETARG_OID(0); + bool include_triggers = PG_GETARG_BOOL(1); - PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events)); + PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers)); } +/* + * pg_column_is_updatable - determine whether a column is updatable + * + * Currently we just check whether the column's relation is updatable. + * Eventually we might allow views to have some updatable and some + * non-updatable columns. + * + * Also, this function encapsulates the decision about just what + * information_schema.columns.is_updatable actually means. It's not clear + * whether deletability of the column's relation should be required, so + * we want that decision in C code where we could change it without initdb. + */ Datum -pg_view_is_updatable(PG_FUNCTION_ARGS) +pg_column_is_updatable(PG_FUNCTION_ARGS) { - Oid viewoid = PG_GETARG_OID(0); - int req_events = (1 << CMD_UPDATE) | (1 << CMD_DELETE); + Oid reloid = PG_GETARG_OID(0); + AttrNumber attnum = PG_GETARG_INT16(1); + bool include_triggers = PG_GETARG_BOOL(2); + int events; + + /* System columns are never updatable */ + if (attnum <= 0) + PG_RETURN_BOOL(false); + + events = relation_is_updatable(reloid, include_triggers); + + /* We require both updatability and deletability of the relation */ +#define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE)) - PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events)); + PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS); } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 392649c37e6..d46fe9ede37 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201305061 +#define CATALOG_VERSION_NO 201306121 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 4102deca694..b5be075ee16 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1976,10 +1976,10 @@ DESCR("type of the argument"); DATA(insert OID = 3162 ( pg_collation_for PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "2276" _null_ _null_ _null_ _null_ pg_collation_for _null_ _null_ _null_ )); DESCR("collation of the argument; implementation of the COLLATION FOR expression"); -DATA(insert OID = 3842 ( pg_view_is_insertable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_insertable _null_ _null_ _null_ )); -DESCR("is a view insertable-into"); -DATA(insert OID = 3843 ( pg_view_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_updatable _null_ _null_ _null_ )); -DESCR("is a view updatable"); +DATA(insert OID = 3842 ( pg_relation_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 23 "2205 16" _null_ _null_ _null_ _null_ pg_relation_is_updatable _null_ _null_ _null_ )); +DESCR("is a relation insertable/updatable/deletable"); +DATA(insert OID = 3843 ( pg_column_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 3 0 16 "2205 21 16" _null_ _null_ _null_ _null_ pg_column_is_updatable _null_ _null_ _null_ )); +DESCR("is a column updatable"); /* Deferrable unique constraint trigger */ DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ )); diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 485eee320f8..e8326652693 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -80,6 +80,8 @@ typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate, typedef void (*EndForeignModify_function) (EState *estate, ResultRelInfo *rinfo); +typedef int (*IsForeignRelUpdatable_function) (Relation rel); + typedef void (*ExplainForeignScan_function) (ForeignScanState *node, struct ExplainState *es); @@ -134,6 +136,7 @@ typedef struct FdwRoutine ExecForeignUpdate_function ExecForeignUpdate; ExecForeignDelete_function ExecForeignDelete; EndForeignModify_function EndForeignModify; + IsForeignRelUpdatable_function IsForeignRelUpdatable; /* Support functions for EXPLAIN */ ExplainForeignScan_function ExplainForeignScan; diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index 59833158031..1831de46406 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -21,6 +21,6 @@ extern List *QueryRewrite(Query *parsetree); extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown); extern Node *build_column_default(Relation rel, int attrno); -extern bool relation_is_updatable(Oid reloid, int req_events); +extern int relation_is_updatable(Oid reloid, bool include_triggers); #endif /* REWRITEHANDLER_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 8acdcaaf987..667c58b5d0c 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -485,8 +485,8 @@ extern Datum pg_sleep(PG_FUNCTION_ARGS); extern Datum pg_get_keywords(PG_FUNCTION_ARGS); extern Datum pg_typeof(PG_FUNCTION_ARGS); extern Datum pg_collation_for(PG_FUNCTION_ARGS); -extern Datum pg_view_is_insertable(PG_FUNCTION_ARGS); -extern Datum pg_view_is_updatable(PG_FUNCTION_ARGS); +extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS); +extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS); /* oid.c */ extern Datum oidin(PG_FUNCTION_ARGS); -- GitLab