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