diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
new file mode 100644
index 123cb4f..5edbf3e
*** a/contrib/postgres_fdw/option.c
--- b/contrib/postgres_fdw/option.c
*************** postgres_fdw_validator(PG_FUNCTION_ARGS)
*** 106,114 ****
/*
* Validate option value, when we can do so without any context.
*/
! if (strcmp(def->defname, "use_remote_estimate") == 0)
{
! /* use_remote_estimate accepts only boolean values */
(void) defGetBoolean(def);
}
else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
--- 106,115 ----
/*
* Validate option value, when we can do so without any context.
*/
! if (strcmp(def->defname, "use_remote_estimate") == 0 ||
! strcmp(def->defname, "updatable") == 0)
{
! /* these accept only boolean values */
(void) defGetBoolean(def);
}
else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
*************** InitPgFdwOptions(void)
*** 151,156 ****
--- 152,160 ----
/* cost factors */
{"fdw_startup_cost", ForeignServerRelationId, false},
{"fdw_tuple_cost", ForeignServerRelationId, false},
+ /* updatability may be specified on the server and on tables */
+ {"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
new file mode 100644
index 49dfe2c..376f5aa
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** static void postgresBeginForeignScan(For
*** 251,256 ****
--- 251,259 ----
static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
static void postgresReScanForeignScan(ForeignScanState *node);
static void postgresEndForeignScan(ForeignScanState *node);
+ static int postgresIsForeignRelUpdatable(Oid foreigntableid);
+ static bool postgresIsForeignColUpdatable(Oid foreigntableid,
+ AttrNumber attnum);
static void postgresAddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 348,353 ****
--- 351,358 ----
routine->EndForeignScan = postgresEndForeignScan;
/* Functions for updating foreign tables */
+ routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
+ routine->IsForeignColUpdatable = postgresIsForeignColUpdatable;
routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets;
routine->PlanForeignModify = postgresPlanForeignModify;
routine->BeginForeignModify = postgresBeginForeignModify;
*************** postgresEndForeignScan(ForeignScanState
*** 1099,1104 ****
--- 1104,1171 ----
}
/*
+ * postgresIsForeignRelUpdatable
+ * Determine whether a foreign table supports INSERT, UPDATE and/or
+ * DELETE.
+ */
+ static int
+ postgresIsForeignRelUpdatable(Oid foreigntableid)
+ {
+ bool updatable;
+ ForeignTable *table;
+ ForeignServer *server;
+ ListCell *lc;
+
+ /*
+ * By default, all postgresql foreign tables are updatable. This may
+ * overridden by a per-server setting, which may in turn be overridden by
+ * a per-table setting.
+ */
+ updatable = true;
+
+ table = GetForeignTable(foreigntableid);
+ 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 suport for INSERT, UPDATE and DELETE.
+ */
+ return updatable ?
+ (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0;
+ }
+
+ /*
+ * postgresIsForeignColUpdatable
+ * Determine whether a column of a foreign table is updatable.
+ */
+ static bool
+ postgresIsForeignColUpdatable(Oid foreigntableid,
+ AttrNumber attnum)
+ {
+ /*
+ * For now we assume that all foreign table columns are updatable, if the
+ * foreign table supports UPDATE.
+ */
+ int events = postgresIsForeignRelUpdatable(foreigntableid);
+
+ return (events & (1 << CMD_UPDATE)) != 0;
+ }
+
+ /*
* postgresAddForeignUpdateTargets
* Add resjunk column(s) needed for update/delete on a foreign table
*/
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
new file mode 100644
index c94988a..9be2edf
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
*************** EndForeignScan (ForeignScanState *node);
*** 286,291 ****
--- 286,316 ----
+ int
+ IsForeignRelUpdatable (Oid foreigntableid);
+
+
+ Determine 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, based on the
+ CmdType> enumeration
+ ((1 << CMD_UPDATE) = 4> for UPDATE>,
+ (1 << CMD_INSERT) = 8> for INSERT>,
+ (1 << CMD_DELETE) = 16> for DELETE>).
+
+
+
+
+ bool
+ IsForeignColUpdatable (Oid foreigntableid,
+ AttrNumber attnum);
+
+
+ Test if the specified column of the specified foreign table is updatable.
+
+
+
+
void
AddForeignUpdateTargets (Query *parsetree,
RangeTblEntry *target_rte,
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
new file mode 100644
index 4aa798a..be91485
*** a/doc/src/sgml/postgres-fdw.sgml
--- b/doc/src/sgml/postgres-fdw.sgml
***************
*** 254,259 ****
--- 254,284 ----
+
+
+ Updatability Options
+
+
+ By default all foreign tables using postgres_fdw> are assumed
+ to be updatable. This may be overridden using the following option:
+
+
+
+
+
+ updatable
+
+
+ This option can be specified for a foreign table or a foreign server.
+ It controls whether postgres_fdw> allows foreign tables to
+ be modified using INSERT>, UPDATE> and
+ DELETE> commands. The default is true.
+
+
+
+
+
+
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index 2307586..cd4972c
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW columns AS
*** 731,737 ****
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))
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)
--- 731,737 ----
CAST(null AS character_data) AS generation_expression,
CAST(CASE WHEN c.relkind = 'r' OR
! (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)
*************** CREATE VIEW tables AS
*** 1895,1901 ****
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))
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,
--- 1895,1902 ----
CAST(t.typname AS sql_identifier) AS user_defined_type_name,
CAST(CASE WHEN c.relkind = 'r' OR
! (c.relkind IN ('v', 'f') AND
! 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,
*************** CREATE VIEW views AS
*** 2494,2504 ****
CAST('NONE' AS character_data) AS check_option,
CAST(
! CASE WHEN pg_view_is_updatable(c.oid) 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
AS yes_or_no) AS is_insertable_into,
CAST(
--- 2495,2505 ----
CAST('NONE' AS character_data) AS check_option,
CAST(
! 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_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
new file mode 100644
index e1b280a..036dd2f
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** CheckValidResultRel(Relation resultRel,
*** 1009,1033 ****
switch (operation)
{
case CMD_INSERT:
! if (fdwroutine->ExecForeignInsert == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot insert into foreign table \"%s\"",
RelationGetRelationName(resultRel))));
break;
case CMD_UPDATE:
! if (fdwroutine->ExecForeignUpdate == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot update foreign table \"%s\"",
RelationGetRelationName(resultRel))));
break;
case CMD_DELETE:
! if (fdwroutine->ExecForeignDelete == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot delete from foreign table \"%s\"",
RelationGetRelationName(resultRel))));
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
--- 1009,1054 ----
switch (operation)
{
case CMD_INSERT:
! if (fdwroutine->ExecForeignInsert == NULL ||
! fdwroutine->IsForeignRelUpdatable == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot insert into foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if ((fdwroutine->IsForeignRelUpdatable(resultRel->rd_id)
+ & (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 ||
! fdwroutine->IsForeignRelUpdatable == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot update foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if ((fdwroutine->IsForeignRelUpdatable(resultRel->rd_id)
+ & (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 ||
! fdwroutine->IsForeignRelUpdatable == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot delete from foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if ((fdwroutine->IsForeignRelUpdatable(resultRel->rd_id)
+ & (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
new file mode 100644
index 83f26e3..7587f76
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** view_is_auto_updatable(Relation view)
*** 2014,2019 ****
--- 2014,2020 ----
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.");
*************** view_is_auto_updatable(Relation view)
*** 2058,2064 ****
/*
! * relation_is_updatable - test if the specified relation is updatable.
*
* This is used for the information_schema views, which have separate concepts
* of "updatable" and "trigger updatable". A relation is "updatable" if it
--- 2059,2066 ----
/*
! * 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
*************** view_is_auto_updatable(Relation view)
*** 2074,2089 ****
* In the case of an automatically updatable view, the base relation must
* also be updatable.
*
! * 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.)
*/
! bool
! relation_is_updatable(Oid reloid, int req_events)
{
Relation rel;
RuleLock *rulelocks;
rel = try_relation_open(reloid, AccessShareLock);
--- 2076,2094 ----
* In the case of an automatically updatable view, the base relation must
* also be updatable.
*
! * reloid is the pg_class OID to examine. include_triggers determines whether
! * to treat trigger-updatable as updatable, which is useful for clients that
! * only care if data-modifying SQL will work. 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.)
*/
! int
! relation_is_updatable(Oid reloid, bool include_triggers)
{
Relation rel;
RuleLock *rulelocks;
+ int events = 0;
rel = try_relation_open(reloid, AccessShareLock);
*************** relation_is_updatable(Oid reloid, int re
*** 2094,2106 ****
* deleted according to a SnapshotNow probe.
*/
if (rel == NULL)
! return false;
/* 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++)
--- 2099,2119 ----
* deleted according to a SnapshotNow probe.
*/
if (rel == NULL)
! return 0;
!
! /* If the relation is a table, it is always updatable */
! #define ALL_EVENTS ((1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE))
!
! 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 i;
for (i = 0; i < rulelocks->numLocks; i++)
*************** relation_is_updatable(Oid reloid, int re
*** 2108,2125 ****
if (rulelocks->rules[i]->isInstead &&
rulelocks->rules[i]->qual == NULL)
{
! events |= 1 << rulelocks->rules[i]->event;
}
}
! /* If we have all rules needed, say "yes" */
! if ((events & req_events) == req_events)
{
relation_close(rel, AccessShareLock);
! return true;
}
}
/* Check if this is an automatically updatable view */
if (rel->rd_rel->relkind == RELKIND_VIEW &&
view_is_auto_updatable(rel) == NULL)
--- 2121,2173 ----
if (rulelocks->rules[i]->isInstead &&
rulelocks->rules[i]->qual == NULL)
{
! events |= ((1 << rulelocks->rules[i]->event) & ALL_EVENTS);
}
}
! /* If we have all rules needed, return now */
! if (events == ALL_EVENTS)
{
relation_close(rel, AccessShareLock);
! return events;
! }
! }
!
! /* Similarly look for INSTEAD OF triggers, if they are to be included */
! if (include_triggers)
! {
! TriggerDesc *trigDesc = rel->trigdesc;
!
! if (trigDesc && trigDesc->trig_insert_instead_row)
! events |= (1 << CMD_INSERT);
! if (trigDesc && trigDesc->trig_update_instead_row)
! events |= (1 << CMD_UPDATE);
! if (trigDesc && trigDesc->trig_delete_instead_row)
! events |= (1 << CMD_DELETE);
!
! /* Return if we now support all update events */
! 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(reloid)
+ & ALL_EVENTS);
+ }
+
+ relation_close(rel, AccessShareLock);
+ return events;
+ }
+
/* Check if this is an automatically updatable view */
if (rel->rd_rel->relkind == RELKIND_VIEW &&
view_is_auto_updatable(rel) == NULL)
*************** relation_is_updatable(Oid reloid, int re
*** 2138,2157 ****
{
/* Tables are always updatable */
relation_close(rel, AccessShareLock);
! return true;
}
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);
}
}
! /* If we reach here, the relation is not updatable */
relation_close(rel, AccessShareLock);
! return false;
}
--- 2186,2205 ----
{
/* Tables are always updatable */
relation_close(rel, AccessShareLock);
! 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, include_triggers);
}
}
! /* If we reach here, the relation may support some update commands */
relation_close(rel, AccessShareLock);
! return events;
}
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
new file mode 100644
index 4e38d7c..995f17c
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 25,30 ****
--- 25,31 ----
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "common/relpath.h"
+ #include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "parser/keywords.h"
***************
*** 37,42 ****
--- 38,44 ----
#include "utils/lsyscache.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+ #include "utils/rel.h"
#include "utils/timestamp.h"
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
*************** pg_collation_for(PG_FUNCTION_ARGS)
*** 527,557 ****
}
/*
! * information_schema support functions
*
! * 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.
*/
Datum
! pg_view_is_insertable(PG_FUNCTION_ARGS)
{
! Oid viewoid = PG_GETARG_OID(0);
! int req_events = (1 << CMD_INSERT);
! PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
}
Datum
! pg_view_is_updatable(PG_FUNCTION_ARGS)
{
! Oid viewoid = PG_GETARG_OID(0);
! int req_events = (1 << CMD_UPDATE) | (1 << CMD_DELETE);
! PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
}
--- 529,620 ----
}
+ /* ----------------------------------------------------------------------
+ * Information_schema support functions.
+ *
+ * These rely on relation_is_updatable(), which is in rewriteHandler.c.
+ * ----------------------------------------------------------------------
+ */
+
/*
! * Determine which update events are supported by a relation (identified by
! * pg_class OID).
*
! * This may optionally include or exclude INSTEAD OF triggers from
! * consideration --- the information_schema views need to exclude such
! * triggers but other client applications may want to include them to
! * determine whether a given relation actually supports INSERT, UPDATE and
! * DELETE operations.
*
! * The return value is a bitmask indicating which of the INSERT, UPDATE and
! * DELETE operations are supported. This works for all relation kinds,
! * although it is typically only called for views and foreign tables, since
! * the result is trivial for other relkinds.
*/
Datum
! pg_relation_is_updatable(PG_FUNCTION_ARGS)
{
! Oid reloid = PG_GETARG_OID(0);
! bool include_triggers = PG_GETARG_BOOL(1);
! PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers));
}
+ /*
+ * Test whether a column (identified by pg_class OID and attnum) is updatable.
+ *
+ * This is used in information_schema.columns, and it supports all kinds of
+ * relations although we only actually use it for views and foreign tables
+ * since the other relkinds are trivial.
+ */
Datum
! pg_column_is_updatable(PG_FUNCTION_ARGS)
{
! Oid reloid = PG_GETARG_OID(0);
! AttrNumber attnum = PG_GETARG_INT16(1);
! bool include_triggers = PG_GETARG_BOOL(2);
! Relation rel;
! bool updatable;
! /* System columns are never updatable */
! if (attnum <= 0)
! PG_RETURN_BOOL(false);
!
! rel = try_relation_open(reloid, AccessShareLock);
! if (rel == NULL)
! PG_RETURN_BOOL(false);
!
! if (attnum > rel->rd_att->natts ||
! rel->rd_att->attrs[attnum - 1]->attisdropped)
! {
! /*
! * The column number is out of range, or the column has been dropped,
! * so it is not updatable.
! */
! updatable = false;
! }
! else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! /*
! * For foreign tables, ask the FDW if the column is updatable.
! */
! FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false);
!
! updatable = fdwroutine->IsForeignColUpdatable != NULL &&
! fdwroutine->IsForeignColUpdatable(reloid, attnum);
! }
! else
! {
! /*
! * Otherwise, for non-foreign tables, the column is updatable if and
! * only if the relation supports UPDATE.
! */
! int events = relation_is_updatable(rel->rd_id, include_triggers);
!
! updatable = events & (1 << CMD_UPDATE);
! }
!
! relation_close(rel, AccessShareLock);
!
! PG_RETURN_BOOL(updatable);
}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 685b9c7..55c4070
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("type of the argument");
*** 1976,1985 ****
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");
/* 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_ ));
--- 1976,1985 ----
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_relation_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 23 "26 16" _null_ _null_ _null_ _null_ pg_relation_is_updatable _null_ _null_ _null_ ));
! DESCR("is a relation updatable");
! 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 "26 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
new file mode 100644
index 485eee3..bca1d59
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** typedef void (*ReScanForeignScan_functio
*** 47,52 ****
--- 47,57 ----
typedef void (*EndForeignScan_function) (ForeignScanState *node);
+ typedef int (*IsForeignRelUpdatable_function) (Oid foreigntableid);
+
+ typedef bool (*IsForeignColUpdatable_function) (Oid foreigntableid,
+ AttrNumber attnum);
+
typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
*************** typedef struct FdwRoutine
*** 127,132 ****
--- 132,139 ----
*/
/* Functions for updating foreign tables */
+ IsForeignRelUpdatable_function IsForeignRelUpdatable;
+ IsForeignColUpdatable_function IsForeignColUpdatable;
AddForeignUpdateTargets_function AddForeignUpdateTargets;
PlanForeignModify_function PlanForeignModify;
BeginForeignModify_function BeginForeignModify;
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index 5983315..44e682c
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
*************** extern List *QueryRewrite(Query *parsetr
*** 21,26 ****
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);
#endif /* REWRITEHANDLER_H */
--- 21,26 ----
extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno);
! 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
new file mode 100644
index 15b60ab..218e645
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_sleep(PG_FUNCTION_ARGS);
*** 485,492 ****
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);
/* oid.c */
extern Datum oidin(PG_FUNCTION_ARGS);
--- 485,492 ----
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_relation_is_updatable(PG_FUNCTION_ARGS);
! extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS);
/* oid.c */
extern Datum oidin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index ecb61e0..3adba33
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** SELECT table_name, column_name, is_updat
*** 468,477 ****
ORDER BY table_name, ordinal_position;
table_name | column_name | is_updatable
------------+-------------+--------------
! rw_view1 | a | NO
! rw_view1 | b | NO
! rw_view2 | a | NO
! rw_view2 | b | NO
(4 rows)
CREATE RULE rw_view1_del_rule AS ON DELETE TO rw_view1
--- 468,477 ----
ORDER BY table_name, ordinal_position;
table_name | column_name | is_updatable
------------+-------------+--------------
! rw_view1 | a | YES
! rw_view1 | b | YES
! rw_view2 | a | YES
! rw_view2 | b | YES
(4 rows)
CREATE RULE rw_view1_del_rule AS ON DELETE TO rw_view1