Updatable view columns

Started by Dean Rasheedover 12 years ago8 messages
#1Dean Rasheed
dean.a.rasheed@gmail.com
1 attachment(s)

Hi,

Attached is a work-in-progress patch to extend auto-updatable views to
support views containing a mix of updatable and non-updatable columns.
This is basically the "columns" part of SQL Feature T111, "Updatable
joins, unions, and columns".

So specifically, views of the following form will now be auto-updatable:

SELECT <column, expression, sub-query or non-SRF>, ...
FROM <single base table or view>
WHERE <arbitrary quals>
[ORDER BY ...]

with the restriction that no window functions, aggregates or
set-returning functions may appear in the view's targetlist (because
otherwise the rewriting process may move them up into quals of the
top-level query where they are not allowed).

Hopefully this will make auto-updatable views much more useful, since
it covers a much wider class of real-world views.

INSERT and UPDATE are supported provided that they do not attempt to
assign to a non-updatable column (which currently means a column that
is not a simple reference to an updatable column of the base
relation). This also means that the view must have at least 1
updatable column for these operations, which is per-spec.

DELETE on the other hand doesn't actually require any updatable
columns, provided the view satisfies all the other updatability
requirements (single base relation, no distinction, grouping, etc...).
This is actually an extension of the spec, which says that DELETE
should only be supported on updatable views with at least 1 updatable
column, but having played around with the code, it seems it would be
an annoying amount of additional code to enforce this restriction, so
I don't see any reason to not just allow it.

Code-wise, aside from the obvious changes needed to the
xxx_is_updatable() functions, the only other code change needed to
support this is in rewriteTargetListIU(). This had code for UPDATE
which would add new dummy targetlist entries for unassigned-to
attributes in the view, similar to expand_targetlist() in preptlist.c.
It now only does this for trigger-updatable views. For auto-updatable
views, it is definitely the wrong thing to do, since it would assign
targetlist entries for all the non-updatable columns. For
rule-updatable views it was not wrong, but I think it was unnecessary,
since the rule will rewrite the query with a different target
relation, which would then get recursively processed. Basically,
adding these dummy targetlist entries is only necessary if the view
relation remains the target after rewriting. That's consistent with
the fact that this code-block was only added in 9.1 to support
triggers on views.

It still needs more testing and doc updates, but otherwise I hope it
will be in reasonable shape for the next commitfest.

Regards,
Dean

Attachments:

updatable-view-cols.v1.patchapplication/octet-stream; name=updatable-view-cols.v1.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index cb87d90..efa27eb
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATExecSetRelOptions(Relation rel, List *
*** 8800,8806 ****
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_query_is_auto_updatable(view_query, security_barrier);
  
  			if (view_updatable_error)
  				ereport(ERROR,
--- 8800,8806 ----
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_is_auto_updatable(view_query, security_barrier, true);
  
  			if (view_updatable_error)
  				ereport(ERROR,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
new file mode 100644
index 832de43..9ece0fa
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
*************** DefineView(ViewStmt *stmt, const char *q
*** 469,475 ****
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_query_is_auto_updatable(viewParse, security_barrier);
  
  		if (view_updatable_error)
  			ereport(ERROR,
--- 469,475 ----
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_is_auto_updatable(viewParse, security_barrier, true);
  
  		if (view_updatable_error)
  			ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 5c6763e..ed96fdc
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** static List *matchLocks(CmdType event, R
*** 61,66 ****
--- 61,68 ----
  		   int varno, Query *parsetree);
  static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
  			 bool forUpdatePushedDown);
+ static bool view_has_instead_trigger(Relation view, CmdType event);
+ static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
  
  
  /*
*************** adjustJoinTreeList(Query *parsetree, boo
*** 616,627 ****
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a view, add tlist entries for any unassigned-to
!  * attributes, assigning them their old values.  These will later get
!  * expanded to the output values of the view.  (This is equivalent to what
!  * the planner's expand_targetlist() will do for UPDATE on a regular table,
!  * but it's more convenient to do it here while we still have easy access
!  * to the view's original RT index.)
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
--- 618,634 ----
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a trigger-updatable view, add tlist entries for any
!  * unassigned-to attributes, assigning them their old values.  These will
!  * later get expanded to the output values of the view.  (This is equivalent
!  * to what the planner's expand_targetlist() will do for UPDATE on a regular
!  * table, but it's more convenient to do it here while we still have easy
!  * access to the view's original RT index.)  This is only necessary for
!  * trigger-updatable views, for which the view remains the result relation of
!  * the query.  For auto-updatable we must not do this, since it might add
!  * assignments to non-updatable view columns.  For rule-updatable views it is
!  * unnecessary extra work, since the query will be rewritten with a different
!  * result relation which will be processed when we recurse via RewriteQuery.
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
*************** rewriteTargetListIU(Query *parsetree, Re
*** 783,793 ****
  		}
  
  		/*
! 		 * For an UPDATE on a view, provide a dummy entry whenever there is no
! 		 * explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW)
  		{
  			Node	   *new_expr;
  
--- 790,801 ----
  		}
  
  		/*
! 		 * For an UPDATE on a trigger-updatable view, provide a dummy entry
! 		 * whenever there is no explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW &&
! 			view_has_instead_trigger(target_relation, CMD_UPDATE))
  		{
  			Node	   *new_expr;
  
*************** view_has_instead_trigger(Relation view,
*** 1925,1979 ****
  
  
  /*
!  * view_is_auto_updatable -
!  *    Retrive the view definition and options and then determine if the view
!  *    can be auto-updated by calling view_query_is_auto_updatable().  Returns
!  *    NULL or a message string giving the reason the view is not auto
!  *    updateable.  See view_query_is_auto_updatable() for details.
!  *
!  *    The only view option which affects if a view can be auto-updated, today,
!  *    is the security_barrier option.  If other options are added later, they
!  *    will also need to be handled here.
!  *
!  * Caller must have verified that the relation is a view!
!  *
!  * Note that the checks performed here are local to this view.	We do not
!  * check whether the view's underlying base relation is updatable; that
!  * will be dealt with in later, recursive processing.
   *
!  * Also note that we don't check for INSTEAD triggers or rules here; those
!  * also prevent auto-update, but they must be checked for by the caller.
   */
! const char *
! view_is_auto_updatable(Relation view)
  {
! 	Query	   *viewquery = get_view_query(view);
! 	bool		security_barrier = RelationIsSecurityView(view);
  
! 	return view_query_is_auto_updatable(viewquery, security_barrier);
  }
  
  
  /*
!  * view_query_is_auto_updatable -
!  *	  Test if the specified view definition can be automatically updated, given
!  *    the view's options (currently only security_barrier affects a view's
!  *    auto-updatable status).
   *
!  *    This will either return NULL (if the view can be updated) or a message
!  *    string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are only based on the view
!  * definition. We do not check whether any base relations referred to by
!  * the view are updatable.
   */
  const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
- 	Bitmapset  *bms;
- 	ListCell   *cell;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
--- 1933,1997 ----
  
  
  /*
!  * view_col_is_auto_updatable - test whether the specified column of a view
!  * is auto-updatable. Returns NULL (if the column can be updated) or a message
!  * string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are local to this view. We do not check
!  * whether the referenced column of the underlying base relation is updatable.
   */
! static const char *
! view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
  {
! 	Var		   *var = (Var *) tle->expr;
  
! 	/*
! 	 * For now, the only updatable columns we support are those that are Vars
! 	 * referring to user columns of the underlying base relation.
! 	 *
! 	 * The view targetlist may contain resjunk columns (e.g., a view defined
! 	 * like "SELECT * FROM t ORDER BY a+b" is auto-updatable) but such columns
! 	 * are not auto-updatable, and in fact should never appear in the outer
! 	 * query's targetlist.
! 	 */
! 	if (tle->resjunk)
! 		return gettext_noop("Junk view columns are not updatable.");
! 
! 	if (!IsA(var, Var) ||
! 		var->varno != rtr->rtindex ||
! 		var->varlevelsup != 0)
! 		return gettext_noop("View columns that are not columns of their base relation are not updatable.");
! 
! 	if (var->varattno < 0)
! 		return gettext_noop("View columns that refer to system columns are not updatable.");
! 
! 	if (var->varattno == 0)
! 		return gettext_noop("View columns that return whole-row references are not updatable.");
! 
! 	return NULL;				/* the view column is updatable */
  }
  
  
  /*
!  * view_is_auto_updatable - test whether the specified view is auto-updatable.
!  * Returns NULL (if the view can be updated) or a message string giving the
!  * reason that it cannot be.
   *
!  * If check_cols is true, the view is required to have at least one updatable
!  * column (required for INSERT/UPDATE). Otherwise the view's columns are not
!  * checked for updatability, and the caller is expected to do any necessary
!  * checks. See view_cols_are_auto_updatable.
   *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether any base relations referred to by the view are
!  * updatable.
   */
  const char *
! view_is_auto_updatable(Query *viewquery, bool security_barrier,
! 					   bool check_cols)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
*************** view_query_is_auto_updatable(Query *view
*** 1990,2000 ****
--- 2008,2025 ----
  	 * arise in Postgres, since any such sub-query will not see any updates
  	 * executed by the outer query anyway, thanks to MVCC snapshotting.)
  	 *
+ 	 * We also relax the second restriction by supporting part of SQL:1999
+ 	 * feature T111, which allows for a mix of updatable and non-updatable
+ 	 * columns, provided that an INSERT or UPDATE doesn't attempt to assign to
+ 	 * a non-updatable column.
+ 	 *
  	 * In addition we impose these constraints, involving features that are
  	 * not part of SQL-92:
  	 *	- No CTEs (WITH clauses).
  	 *	- No OFFSET or LIMIT clauses (this matches a SQL:2008 restriction).
  	 *	- No system columns (including whole-row references) in the tlist.
+ 	 *	- No window functions in the tlist.
+ 	 *	- No set-returning functions in the tlist.
  	 *
  	 * Note that we do these checks without recursively expanding the view.
  	 * If the base relation is a view, we'll recursively deal with it later.
*************** view_query_is_auto_updatable(Query *view
*** 2019,2024 ****
--- 2044,2067 ----
  		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
  
  	/*
+ 	 * We must not allow window functions or set returning functions in the
+ 	 * targetlist. Otherwise we might end up inserting them into the quals of
+ 	 * the main query. We must also check for aggregates in the targetlist in
+ 	 * case they appear without a GROUP BY.
+ 	 *
+ 	 * These restrictions ensure that each row of the view corresponds to a
+ 	 * unique row in the underlying base relation.
+ 	 */
+ 	if (viewquery->hasAggs)
+ 		return gettext_noop("Views that return aggregate functions are not automatically updatable");
+ 
+ 	if (viewquery->hasWindowFuncs)
+ 		return gettext_noop("Views that return window functions are not automatically updatable");
+ 
+ 	if (expression_returns_set((Node *) viewquery->targetList))
+ 		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+ 
+ 	/*
  	 * For now, we also don't support security-barrier views, because of the
  	 * difficulty of keeping upper-level qual expressions away from
  	 * lower-level data.  This might get relaxed in the future.
*************** view_query_is_auto_updatable(Query *view
*** 2045,2086 ****
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * The view's targetlist entries should all be Vars referring to user
! 	 * columns of the base relation, and no two should refer to the same
! 	 * column.
! 	 *
! 	 * Note however that we should ignore resjunk entries.	This proviso is
! 	 * relevant because ORDER BY is not disallowed, and we shouldn't reject a
! 	 * view defined like "SELECT * FROM t ORDER BY a+b".
  	 */
! 	bms = NULL;
! 	foreach(cell, viewquery->targetList)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		Var		   *var = (Var *) tle->expr;
  
! 		if (tle->resjunk)
! 			continue;
  
! 		if (!IsA(var, Var) ||
! 			var->varno != rtr->rtindex ||
! 			var->varlevelsup != 0)
! 			return gettext_noop("Views that return columns that are not columns of their base relation are not automatically updatable.");
  
! 		if (var->varattno < 0)
! 			return gettext_noop("Views that return system columns are not automatically updatable.");
  
! 		if (var->varattno == 0)
! 			return gettext_noop("Views that return whole-row references are not automatically updatable.");
  
- 		if (bms_is_member(var->varattno, bms))
- 			return gettext_noop("Views that return the same column more than once are not automatically updatable.");
  
! 		bms = bms_add_member(bms, var->varattno);
  	}
- 	bms_free(bms);				/* just for cleanliness */
  
! 	return NULL;				/* the view is simply updatable */
  }
  
  
--- 2088,2191 ----
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * Check that the view has at least one updatable column. This is required
! 	 * for INSERT/UPDATE but not for DELETE.
  	 */
! 	if (check_cols)
  	{
! 		ListCell   *cell;
! 		bool		found;
  
! 		found = false;
! 		foreach(cell, viewquery->targetList)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(cell);
  
! 			if (view_col_is_auto_updatable(rtr, tle) == NULL)
! 			{
! 				found = true;
! 				break;
! 			}
! 		}
  
! 		if (!found)
! 			return gettext_noop("Views that have no updatable columns are not automatically updatable.");
! 	}
  
! 	return NULL;				/* the view is updatable */
! }
  
  
! /*
!  * view_cols_are_auto_updatable - test whether the all of the required columns
!  * of an auto-updatable view are actually updatable. Returns NULL (if all the
!  * required columns can be updated) or a message string giving the reason that
!  * they cannot be.
!  *
!  * This should be used for INSERT/UPDATE to ensure that we don't attempt to
!  * assign to any non-updatable columns.
!  *
!  * Additionally it may be used to retrieve the set of updatable columns in the
!  * view or, if one or more of the required columns is not updatable, the name
!  * of the first offending non-updatable column.
!  *
!  * The caller must have already verified that this is an auto-updatable view
!  * using view_is_auto_updatable.
!  *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether the referenced columns of the base relation are
!  * updatable.
!  */
! static const char *
! view_cols_are_auto_updatable(Query *viewquery,
! 							 Bitmapset *required_cols,
! 							 Bitmapset **updatable_cols,
! 							 char **non_updatable_col)
! {
! 	RangeTblRef *rtr;
! 	AttrNumber	col;
! 	ListCell   *cell;
! 
! 	/*
! 	 * The caller should have verified that this view is auto-updatable and
! 	 * so there should be a single base relation.
! 	 */
! 	Assert(list_length(viewquery->jointree->fromlist) == 1);
! 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
! 	Assert(IsA(rtr, RangeTblRef));
! 
! 	/* Initialize the optional return values */
! 	if (updatable_cols != NULL)
! 		*updatable_cols = NULL;
! 	if (non_updatable_col != NULL)
! 		*non_updatable_col = NULL;
! 
! 	/* Test each view column for updatability */
! 	col = -FirstLowInvalidHeapAttributeNumber;
! 	foreach(cell, viewquery->targetList)
! 	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		const char *col_update_detail;
! 
! 		col++;
! 		col_update_detail = view_col_is_auto_updatable(rtr, tle);
! 
! 		if (col_update_detail == NULL)
! 		{
! 			/* The column is updatable */
! 			if (updatable_cols != NULL)
! 				*updatable_cols = bms_add_member(*updatable_cols, col);
! 		}
! 		else if (bms_is_member(col, required_cols))
! 		{
! 			/* Attempt to assign to a non-updatable column */
! 			if (non_updatable_col != NULL)
! 				*non_updatable_col = tle->resname;
! 			return col_update_detail;
! 		}
  	}
  
! 	return NULL;			/* all the required view columns are updatable */
  }
  
  
*************** view_query_is_auto_updatable(Query *view
*** 2088,2093 ****
--- 2193,2204 ----
   * relation_is_updatable - determine which update events the specified
   * relation supports.
   *
+  * Note that views may contain a mix of updatable and non-updatable columns.
+  * For a view to support INSERT/UPDATE it must have at least one updatable
+  * column, but there is no such restriction for DELETE. If include_cols is
+  * non-NULL, then only the specified columns are considered when testing for
+  * updatability.
+  *
   * 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
*************** view_query_is_auto_updatable(Query *view
*** 2105,2111 ****
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid, bool include_triggers)
  {
  	int			events = 0;
  	Relation	rel;
--- 2216,2224 ----
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid,
! 					  bool include_triggers,
! 					  Bitmapset *include_cols)
  {
  	int			events = 0;
  	Relation	rel;
*************** relation_is_updatable(Oid reloid, bool i
*** 2200,2231 ****
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW &&
! 		view_is_auto_updatable(rel) == NULL)
  	{
! 		Query	   *viewquery;
! 		RangeTblRef *rtr;
! 		RangeTblEntry *base_rte;
! 		Oid			baseoid;
! 
! 		/* The base relation must also be updatable */
! 		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 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);
  		}
  	}
  
--- 2313,2368 ----
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW)
  	{
! 		Query	   *viewquery = get_view_query(rel);
! 		bool		security_barrier = RelationIsSecurityView(rel);
  
! 		if (view_is_auto_updatable(viewquery, security_barrier, false) == NULL)
  		{
! 			Bitmapset  *updatable_cols;
! 			int			auto_events;
! 			RangeTblRef *rtr;
! 			RangeTblEntry *base_rte;
! 			Oid			baseoid;
! 
! 			/*
! 			 * Determine which of the view's columns are updatable. If there
! 			 * are none within the set of of columns we are looking at, then
! 			 * the view doesn't support INSERT/UPDATE, but it may still
! 			 * support DELETE.
! 			 */
! 			view_cols_are_auto_updatable(viewquery, NULL,
! 										 &updatable_cols, NULL);
! 
! 			if (include_cols != NULL)
! 				updatable_cols = bms_int_members(updatable_cols, include_cols);
! 
! 			if (bms_is_empty(updatable_cols))
! 				auto_events = (1 << CMD_DELETE); /* May support DELETE */
! 			else
! 				auto_events = ALL_EVENTS; /* May support all events */
! 
! 			/*
! 			 * The base relation must also support these update commands.
! 			 * Tables are always updatable, but for any other kind of base
! 			 * relation we must do a recursive check limited to the columns
! 			 * referenced by the locally updatable columns in this view.
! 			 */
! 			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)
! 			{
! 				baseoid = base_rte->relid;
! 				include_cols = adjust_view_column_set(updatable_cols,
! 													  viewquery->targetList);
! 				auto_events &= relation_is_updatable(baseoid,
! 													 include_triggers,
! 													 include_cols);
! 			}
! 			events |= auto_events;
  		}
  	}
  
*************** adjust_view_column_set(Bitmapset *cols,
*** 2319,2326 ****
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
- 	const char *auto_update_detail;
  	Query	   *viewquery;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
--- 2456,2464 ----
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
  	Query	   *viewquery;
+ 	bool		security_barrier;
+ 	const char *auto_update_detail;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
*************** rewriteTargetView(Query *parsetree, Rela
*** 2331,2338 ****
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be simply updatable, else fail */
! 	auto_update_detail = view_is_auto_updatable(view);
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
--- 2469,2481 ----
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be updatable, else fail */
! 	viewquery = get_view_query(view);
! 	security_barrier = RelationIsSecurityView(view);
! 
! 	auto_update_detail =
! 			view_is_auto_updatable(viewquery, security_barrier, false);
! 
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
*************** rewriteTargetView(Query *parsetree, Rela
*** 2369,2374 ****
--- 2512,2576 ----
  		}
  	}
  
+ 	/*
+ 	 * For INSERT/UPDATE the modified columns must all be updatable.  Note we
+ 	 * get the modified columns from the query's targetlist, not from the
+ 	 * result RTE's modifiedCols set, since rewriteTargetListIU may have added
+ 	 * additional targetlist entries for view defaults, and these must also be
+ 	 * updatable.
+ 	 */
+ 	if (parsetree->commandType != CMD_DELETE)
+ 	{
+ 		Bitmapset  *modified_cols = NULL;
+ 		char	   *non_updatable_col;
+ 
+ 		foreach(lc, parsetree->targetList)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 				modified_cols = bms_add_member(modified_cols,
+ 							tle->resno - FirstLowInvalidHeapAttributeNumber);
+ 		}
+ 
+ 		auto_update_detail = view_cols_are_auto_updatable(viewquery,
+ 														  modified_cols,
+ 														  NULL,
+ 														  &non_updatable_col);
+ 		if (auto_update_detail)
+ 		{
+ 			switch (parsetree->commandType)
+ 			{
+ 				case CMD_INSERT:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 non_updatable_col == NULL ?
+ 							 errmsg("cannot insert into view \"%s\"",
+ 									RelationGetRelationName(view)) :
+ 							 errmsg("cannot insert into column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				case CMD_UPDATE:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 non_updatable_col == NULL ?
+ 							 errmsg("cannot update view \"%s\"",
+ 									RelationGetRelationName(view)) :
+ 							 errmsg("cannot update column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized CmdType: %d",
+ 						 (int) parsetree->commandType);
+ 					break;
+ 			}
+ 		}
+ 	}
+ 
  	/* Locate RTE describing the view in the outer query */
  	view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
  
*************** rewriteTargetView(Query *parsetree, Rela
*** 2376,2383 ****
  	 * If we get here, view_is_auto_updatable() has verified that the view
  	 * contains a single base relation.
  	 */
- 	viewquery = get_view_query(view);
- 
  	Assert(list_length(viewquery->jointree->fromlist) == 1);
  	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
  	Assert(IsA(rtr, RangeTblRef));
--- 2578,2583 ----
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
new file mode 100644
index aecdcd0..8aba906
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 20,25 ****
--- 20,26 ----
  #include <math.h>
  #include <unistd.h>
  
+ #include "access/sysattr.h"
  #include "catalog/catalog.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_type.h"
*************** pg_relation_is_updatable(PG_FUNCTION_ARG
*** 547,563 ****
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	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.
--- 548,560 ----
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers, NULL));
  }
  
  /*
   * pg_column_is_updatable - determine whether a column is updatable
   *
!  * 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.
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 567,572 ****
--- 564,570 ----
  {
  	Oid			reloid = PG_GETARG_OID(0);
  	AttrNumber	attnum = PG_GETARG_INT16(1);
+ 	AttrNumber	col = attnum - FirstLowInvalidHeapAttributeNumber;
  	bool		include_triggers = PG_GETARG_BOOL(2);
  	int			events;
  
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 574,580 ****
  	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))
--- 572,579 ----
  	if (attnum <= 0)
  		PG_RETURN_BOOL(false);
  
! 	events = relation_is_updatable(reloid, include_triggers,
! 								   bms_make_singleton(col));
  
  	/* We require both updatability and deletability of the relation */
  #define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index f0604b0..6f1085b
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
*************** extern void AcquireRewriteLocks(Query *p
*** 22,30 ****
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
! extern const char *view_is_auto_updatable(Relation view);
! extern const char *view_query_is_auto_updatable(Query *viewquery,
! 										 bool security_barrier);
! extern int	relation_is_updatable(Oid reloid, bool include_triggers);
  
  #endif   /* REWRITEHANDLER_H */
--- 22,32 ----
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
! extern const char *view_is_auto_updatable(Query *viewquery,
! 								   bool security_barrier,
! 								   bool check_cols);
! extern int	relation_is_updatable(Oid reloid,
! 						  bool include_triggers,
! 						  Bitmapset *include_cols);
  
  #endif   /* REWRITEHANDLER_H */
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 87097a4..3e48d0b
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
***************
*** 1,7 ****
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views are rejected with useful error messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
--- 1,8 ----
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 17,34 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
--- 18,36 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
*************** SELECT table_name, is_insertable_into
*** 37,50 ****
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
-  ro_view14  | NO
-  ro_view15  | NO
-  ro_view16  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
--- 39,50 ----
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
+  ro_view21  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
*************** SELECT table_name, is_insertable_into
*** 52,62 ****
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
! (20 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
--- 52,65 ----
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
!  rw_view14  | YES
!  rw_view15  | YES
!  rw_view16  | YES
! (21 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
*************** SELECT table_name, is_updatable, is_inse
*** 65,78 ****
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
-  ro_view14  | NO           | NO
-  ro_view15  | NO           | NO
-  ro_view16  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
--- 68,79 ----
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
+  ro_view21  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
*************** SELECT table_name, is_updatable, is_inse
*** 80,90 ****
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
! (20 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
--- 81,94 ----
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
!  rw_view14  | YES          | YES
!  rw_view15  | YES          | YES
!  rw_view16  | YES          | YES
! (21 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
*************** SELECT table_name, column_name, is_updat
*** 96,107 ****
   ro_view12  | a             | NO
   ro_view13  | a             | NO
   ro_view13  | b             | NO
-  ro_view14  | ctid          | NO
-  ro_view15  | a             | NO
-  ro_view15  | upper         | NO
-  ro_view16  | a             | NO
-  ro_view16  | b             | NO
-  ro_view16  | aa            | NO
   ro_view17  | a             | NO
   ro_view17  | b             | NO
   ro_view18  | a             | NO
--- 100,105 ----
*************** SELECT table_name, column_name, is_updat
*** 119,124 ****
--- 117,125 ----
   ro_view20  | log_cnt       | NO
   ro_view20  | is_cycled     | NO
   ro_view20  | is_called     | NO
+  ro_view21  | a             | NO
+  ro_view21  | b             | NO
+  ro_view21  | g             | NO
   ro_view3   | ?column?      | NO
   ro_view4   | count         | NO
   ro_view5   | a             | NO
*************** SELECT table_name, column_name, is_updat
*** 131,138 ****
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
! (43 rows)
  
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 132,148 ----
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
!  rw_view14  | ctid          | NO
!  rw_view14  | a             | YES
!  rw_view14  | b             | YES
!  rw_view15  | a             | YES
!  rw_view15  | upper         | NO
!  rw_view16  | a             | YES
!  rw_view16  | b             | YES
!  rw_view16  | aa            | YES
! (48 rows)
  
+ -- Read-only views
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** DETAIL:  Views containing HAVING are not
*** 147,157 ****
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
--- 157,167 ----
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return aggregate functions are not automatically updatable
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return window functions are not automatically updatable
  HINT:  To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
*************** INSERT INTO ro_view13 VALUES (3, 'Row 3'
*** 185,202 ****
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
! INSERT INTO ro_view14 VALUES (null);
! ERROR:  cannot insert into view "ro_view14"
! DETAIL:  Views that return system columns are not automatically updatable.
! HINT:  To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! ERROR:  cannot insert into view "ro_view15"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
! HINT:  To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
! ERROR:  cannot insert into view "ro_view16"
! DETAIL:  Views that return the same column more than once are not automatically updatable.
! HINT:  To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 195,269 ----
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! ERROR:  cannot insert into column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! ERROR:  cannot update column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   3 | ROW 3
! (6 rows)
! 
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |      b      
! ----+-------------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   4 | Unspecified
! (6 rows)
! 
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! ERROR:  multiple assignments to same column "a"
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! ERROR:  multiple assignments to same column "a"
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!  -3 | Row 3
! (6 rows)
! 
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** UPDATE ro_view20 SET max_value=1000;
*** 213,220 ****
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 16 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
--- 280,291 ----
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+ UPDATE ro_view21 SET b=upper(b);
+ ERROR:  cannot update view "ro_view21"
+ DETAIL:  Views that return set-returning functions are not automatically updatable.
+ HINT:  To make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 17 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
*************** drop cascades to view ro_view8
*** 226,236 ****
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view ro_view15
! drop cascades to view ro_view16
  drop cascades to view ro_view18
  drop cascades to view ro_view4
! drop cascades to view ro_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
--- 297,308 ----
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view rw_view15
! drop cascades to view rw_view16
  drop cascades to view ro_view18
+ drop cascades to view ro_view21
  drop cascades to view ro_view4
! drop cascades to view rw_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
*************** SELECT * FROM rw_view1;
*** 1063,1068 ****
--- 1135,1282 ----
  
  DROP TABLE base_tbl CASCADE;
  NOTICE:  drop cascades to view rw_view1
+ -- views with updatable and non-updatable columns
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "ctid" of view "rw_view1"
+ DETAIL:  View columns that refer to system columns are not updatable.
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+   a  |         s         |         c         
+ -----+-------------------+-------------------
+  1.1 | 0.891207360061435 | 0.453596121425577
+ (1 row)
+ 
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+          s         
+ -------------------
+  0.867423225594017
+ (1 row)
+ 
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+         t         
+ ------------------
+  1.96475965724865
+ (1 row)
+ 
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+  base_a |         s         |         c         |        t         
+ --------+-------------------+-------------------+------------------
+    1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317
+ (1 row)
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view3"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view3 SET s = s; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+   a  
+ -----
+  0.2
+  0.3
+  0.4
+  0.5
+  0.6
+  0.7
+  0.8
+  0.9
+    1
+ (9 rows)
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+  table_name | is_insertable_into 
+ ------------+--------------------
+  rw_view1   | YES
+  rw_view2   | YES
+  rw_view3   | NO
+ (3 rows)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  rw_view1   | YES          | YES
+  rw_view2   | YES          | YES
+  rw_view3   | NO           | NO
+ (3 rows)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name, ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  rw_view1   | ctid        | NO
+  rw_view1   | s           | NO
+  rw_view1   | a           | YES
+  rw_view1   | c           | NO
+  rw_view2   | s           | NO
+  rw_view2   | c           | NO
+  rw_view2   | t           | NO
+  rw_view2   | base_a      | YES
+  rw_view2   | ctid        | NO
+  rw_view3   | s           | NO
+  rw_view3   | c           | NO
+  rw_view3   | t           | NO
+  rw_view3   | ctid        | NO
+ (13 rows)
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+  upd | ins | del 
+ -----+-----+-----
+  f   | f   | t
+ (1 row)
+ 
+ DROP TABLE base_tbl CASCADE;
+ NOTICE:  drop cascades to 3 other objects
+ DETAIL:  drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ drop cascades to view rw_view3
  -- inheritance tests
  CREATE TABLE base_tbl_parent (a int);
  CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 0481800..b7d9ba7
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
***************
*** 2,8 ****
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views are rejected with useful error messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
--- 2,9 ----
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 20,50 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
  
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
--- 21,53 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name, ordinal_position;
  
+ -- Read-only views
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
*************** UPDATE ro_view10 SET a=a+1;
*** 58,70 ****
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! INSERT INTO ro_view14 VALUES (null);
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
--- 61,96 ----
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
+ UPDATE ro_view21 SET b=upper(b);
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
*************** SELECT * FROM rw_view1;
*** 510,515 ****
--- 536,603 ----
  
  DROP TABLE base_tbl CASCADE;
  
+ -- views with updatable and non-updatable columns
+ 
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ 
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ 
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ 
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ UPDATE rw_view3 SET s = s; -- should fail
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name, ordinal_position;
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+ 
+ DROP TABLE base_tbl CASCADE;
+ 
  -- inheritance tests
  
  CREATE TABLE base_tbl_parent (a int);
#2Peter Eisentraut
peter_e@gmx.net
In reply to: Dean Rasheed (#1)
Re: Updatable view columns

On Mon, 2013-08-12 at 15:27 +0100, Dean Rasheed wrote:

Attached is a work-in-progress patch to extend auto-updatable views to
support views containing a mix of updatable and non-updatable columns.
This is basically the "columns" part of SQL Feature T111, "Updatable
joins, unions, and columns".

This patch needs to be rebased.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Peter Eisentraut (#2)
1 attachment(s)
Re: Updatable view columns

On 22 August 2013 01:49, Peter Eisentraut <peter_e@gmx.net> wrote:

On Mon, 2013-08-12 at 15:27 +0100, Dean Rasheed wrote:

Attached is a work-in-progress patch to extend auto-updatable views to
support views containing a mix of updatable and non-updatable columns.
This is basically the "columns" part of SQL Feature T111, "Updatable
joins, unions, and columns".

This patch needs to be rebased.

Here's a rebased version, now with updated docs.

Regards,
Dean

Attachments:

updatable-view-cols.v2.patchapplication/octet-stream; name=updatable-view-cols.v2.patchDownload
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index 8102ec2..4505290
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 319,334 ****
  
       <listitem>
        <para>
!        All columns in the view's select list must be simple references to
!        columns of the underlying relation.  They cannot be expressions,
!        literals or functions.  System columns cannot be referenced, either.
!       </para>
!      </listitem>
! 
!      <listitem>
!       <para>
!        No column of the underlying relation can appear more than once in
!        the view's select list.
        </para>
       </listitem>
  
--- 319,326 ----
  
       <listitem>
        <para>
!        The view's select list must not contain any aggregates, window functions
!        or set-returning functions.
        </para>
       </listitem>
  
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 341,346 ****
--- 333,346 ----
     </para>
  
     <para>
+     An automatically updatable view may contain a mix of updatable and
+     non-updatable columns.  A column is updatable if it is a simple reference
+     to an updatable column of the underlying base relation; otherwise the
+     column is read-only, and an error will be raised if an <command>INSERT</>
+     or <command>UPDATE</> statement attempts to assign a value to it.
+    </para>
+ 
+    <para>
      If the view is automatically updatable the system will convert any
      <command>INSERT</>, <command>UPDATE</> or <command>DELETE</> statement
      on the view into the corresponding statement on the underlying base
*************** CREATE VIEW pg_comedies AS
*** 435,440 ****
--- 435,459 ----
    </para>
  
    <para>
+    Create a view with a mix of updatable and non-updatable columns:
+ 
+ <programlisting>
+ CREATE VIEW comedies AS
+     SELECT f.*,
+            country_code_to_name(f.country_code) AS country
+            (SELECT avg(r.rating)
+             FROM user_ratings r
+             WHERE r.film_id = f.id) AS avg_rating
+     FROM films f
+     WHERE f.kind = 'Comedy';
+ </programlisting>
+    This view will support <command>INSERT</>, <command>UPDATE</> and
+    <command>DELETE</>.  All the columns from the <literal>films</> table will
+    be updatable, whereas the computed columns <literal>country</> and
+    <literal>avg_rating</> will be read-only.
+   </para>
+ 
+   <para>
     Create a recursive view consisting of the numbers from 1 to 100:
  <programlisting>
  CREATE RECURSIVE VIEW nums_1_100 (n) AS
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index adc74dd..fc257f6
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATExecSetRelOptions(Relation rel, List *
*** 8800,8806 ****
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_query_is_auto_updatable(view_query, security_barrier);
  
  			if (view_updatable_error)
  				ereport(ERROR,
--- 8800,8807 ----
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_query_is_auto_updatable(view_query,
! 											 security_barrier, true);
  
  			if (view_updatable_error)
  				ereport(ERROR,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
new file mode 100644
index 832de43..aca40e7
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
*************** DefineView(ViewStmt *stmt, const char *q
*** 469,475 ****
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_query_is_auto_updatable(viewParse, security_barrier);
  
  		if (view_updatable_error)
  			ereport(ERROR,
--- 469,475 ----
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_query_is_auto_updatable(viewParse, security_barrier, true);
  
  		if (view_updatable_error)
  			ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index c55fb14..23f4fe1
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** static List *matchLocks(CmdType event, R
*** 61,66 ****
--- 61,68 ----
  		   int varno, Query *parsetree);
  static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
  			 bool forUpdatePushedDown);
+ static bool view_has_instead_trigger(Relation view, CmdType event);
+ static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
  
  
  /*
*************** adjustJoinTreeList(Query *parsetree, boo
*** 616,627 ****
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a view, add tlist entries for any unassigned-to
!  * attributes, assigning them their old values.  These will later get
!  * expanded to the output values of the view.  (This is equivalent to what
!  * the planner's expand_targetlist() will do for UPDATE on a regular table,
!  * but it's more convenient to do it here while we still have easy access
!  * to the view's original RT index.)
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
--- 618,634 ----
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a trigger-updatable view, add tlist entries for any
!  * unassigned-to attributes, assigning them their old values.  These will
!  * later get expanded to the output values of the view.  (This is equivalent
!  * to what the planner's expand_targetlist() will do for UPDATE on a regular
!  * table, but it's more convenient to do it here while we still have easy
!  * access to the view's original RT index.)  This is only necessary for
!  * trigger-updatable views, for which the view remains the result relation of
!  * the query.  For auto-updatable we must not do this, since it might add
!  * assignments to non-updatable view columns.  For rule-updatable views it is
!  * unnecessary extra work, since the query will be rewritten with a different
!  * result relation which will be processed when we recurse via RewriteQuery.
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
*************** rewriteTargetListIU(Query *parsetree, Re
*** 783,793 ****
  		}
  
  		/*
! 		 * For an UPDATE on a view, provide a dummy entry whenever there is no
! 		 * explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW)
  		{
  			Node	   *new_expr;
  
--- 790,801 ----
  		}
  
  		/*
! 		 * For an UPDATE on a trigger-updatable view, provide a dummy entry
! 		 * whenever there is no explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW &&
! 			view_has_instead_trigger(target_relation, CMD_UPDATE))
  		{
  			Node	   *new_expr;
  
*************** get_view_query(Relation view)
*** 1895,1901 ****
   * view_has_instead_trigger - does view have an INSTEAD OF trigger for event?
   *
   * If it does, we don't want to treat it as auto-updatable.  This test can't
!  * be folded into view_is_auto_updatable because it's not an error condition.
   */
  static bool
  view_has_instead_trigger(Relation view, CmdType event)
--- 1903,1910 ----
   * view_has_instead_trigger - does view have an INSTEAD OF trigger for event?
   *
   * If it does, we don't want to treat it as auto-updatable.  This test can't
!  * be folded into view_query_is_auto_updatable because it's not an error
!  * condition.
   */
  static bool
  view_has_instead_trigger(Relation view, CmdType event)
*************** view_has_instead_trigger(Relation view,
*** 1925,1979 ****
  
  
  /*
!  * view_is_auto_updatable -
!  *    Retrive the view definition and options and then determine if the view
!  *    can be auto-updated by calling view_query_is_auto_updatable().  Returns
!  *    NULL or a message string giving the reason the view is not auto
!  *    updateable.  See view_query_is_auto_updatable() for details.
!  *
!  *    The only view option which affects if a view can be auto-updated, today,
!  *    is the security_barrier option.  If other options are added later, they
!  *    will also need to be handled here.
!  *
!  * Caller must have verified that the relation is a view!
!  *
!  * Note that the checks performed here are local to this view.	We do not
!  * check whether the view's underlying base relation is updatable; that
!  * will be dealt with in later, recursive processing.
   *
!  * Also note that we don't check for INSTEAD triggers or rules here; those
!  * also prevent auto-update, but they must be checked for by the caller.
   */
! const char *
! view_is_auto_updatable(Relation view)
  {
! 	Query	   *viewquery = get_view_query(view);
! 	bool		security_barrier = RelationIsSecurityView(view);
  
! 	return view_query_is_auto_updatable(viewquery, security_barrier);
  }
  
  
  /*
!  * view_query_is_auto_updatable -
!  *	  Test if the specified view definition can be automatically updated, given
!  *    the view's options (currently only security_barrier affects a view's
!  *    auto-updatable status).
   *
!  *    This will either return NULL (if the view can be updated) or a message
!  *    string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are only based on the view
!  * definition. We do not check whether any base relations referred to by
!  * the view are updatable.
   */
  const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
- 	Bitmapset  *bms;
- 	ListCell   *cell;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
--- 1934,1997 ----
  
  
  /*
!  * view_col_is_auto_updatable - test whether the specified column of a view
!  * is auto-updatable. Returns NULL (if the column can be updated) or a message
!  * string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are local to this view. We do not check
!  * whether the referenced column of the underlying base relation is updatable.
   */
! static const char *
! view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
  {
! 	Var		   *var = (Var *) tle->expr;
  
! 	/*
! 	 * For now, the only updatable columns we support are those that are Vars
! 	 * referring to user columns of the underlying base relation.
! 	 *
! 	 * The view targetlist may contain resjunk columns (e.g., a view defined
! 	 * like "SELECT * FROM t ORDER BY a+b" is auto-updatable) but such columns
! 	 * are not auto-updatable, and in fact should never appear in the outer
! 	 * query's targetlist.
! 	 */
! 	if (tle->resjunk)
! 		return gettext_noop("Junk view columns are not updatable.");
! 
! 	if (!IsA(var, Var) ||
! 		var->varno != rtr->rtindex ||
! 		var->varlevelsup != 0)
! 		return gettext_noop("View columns that are not columns of their base relation are not updatable.");
! 
! 	if (var->varattno < 0)
! 		return gettext_noop("View columns that refer to system columns are not updatable.");
! 
! 	if (var->varattno == 0)
! 		return gettext_noop("View columns that return whole-row references are not updatable.");
! 
! 	return NULL;				/* the view column is updatable */
  }
  
  
  /*
!  * view_query_is_auto_updatable - test whether the specified view definition
!  * represents an auto-updatable view. Returns NULL (if the view can be updated)
!  * or a message string giving the reason that it cannot be.
   *
!  * If check_cols is true, the view is required to have at least one updatable
!  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
!  * checked for updatability. See also view_cols_are_auto_updatable.
   *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether any base relations referred to by the view are
!  * updatable.
   */
  const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier,
! 							 bool check_cols)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
*************** view_query_is_auto_updatable(Query *view
*** 1990,2000 ****
--- 2008,2025 ----
  	 * arise in Postgres, since any such sub-query will not see any updates
  	 * executed by the outer query anyway, thanks to MVCC snapshotting.)
  	 *
+ 	 * We also relax the second restriction by supporting part of SQL:1999
+ 	 * feature T111, which allows for a mix of updatable and non-updatable
+ 	 * columns, provided that an INSERT or UPDATE doesn't attempt to assign to
+ 	 * a non-updatable column.
+ 	 *
  	 * In addition we impose these constraints, involving features that are
  	 * not part of SQL-92:
  	 *	- No CTEs (WITH clauses).
  	 *	- No OFFSET or LIMIT clauses (this matches a SQL:2008 restriction).
  	 *	- No system columns (including whole-row references) in the tlist.
+ 	 *	- No window functions in the tlist.
+ 	 *	- No set-returning functions in the tlist.
  	 *
  	 * Note that we do these checks without recursively expanding the view.
  	 * If the base relation is a view, we'll recursively deal with it later.
*************** view_query_is_auto_updatable(Query *view
*** 2019,2024 ****
--- 2044,2067 ----
  		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
  
  	/*
+ 	 * We must not allow window functions or set returning functions in the
+ 	 * targetlist. Otherwise we might end up inserting them into the quals of
+ 	 * the main query. We must also check for aggregates in the targetlist in
+ 	 * case they appear without a GROUP BY.
+ 	 *
+ 	 * These restrictions ensure that each row of the view corresponds to a
+ 	 * unique row in the underlying base relation.
+ 	 */
+ 	if (viewquery->hasAggs)
+ 		return gettext_noop("Views that return aggregate functions are not automatically updatable");
+ 
+ 	if (viewquery->hasWindowFuncs)
+ 		return gettext_noop("Views that return window functions are not automatically updatable");
+ 
+ 	if (expression_returns_set((Node *) viewquery->targetList))
+ 		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+ 
+ 	/*
  	 * For now, we also don't support security-barrier views, because of the
  	 * difficulty of keeping upper-level qual expressions away from
  	 * lower-level data.  This might get relaxed in the future.
*************** view_query_is_auto_updatable(Query *view
*** 2045,2086 ****
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * The view's targetlist entries should all be Vars referring to user
! 	 * columns of the base relation, and no two should refer to the same
! 	 * column.
! 	 *
! 	 * Note however that we should ignore resjunk entries.	This proviso is
! 	 * relevant because ORDER BY is not disallowed, and we shouldn't reject a
! 	 * view defined like "SELECT * FROM t ORDER BY a+b".
  	 */
! 	bms = NULL;
! 	foreach(cell, viewquery->targetList)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		Var		   *var = (Var *) tle->expr;
  
! 		if (tle->resjunk)
! 			continue;
  
! 		if (!IsA(var, Var) ||
! 			var->varno != rtr->rtindex ||
! 			var->varlevelsup != 0)
! 			return gettext_noop("Views that return columns that are not columns of their base relation are not automatically updatable.");
  
! 		if (var->varattno < 0)
! 			return gettext_noop("Views that return system columns are not automatically updatable.");
  
! 		if (var->varattno == 0)
! 			return gettext_noop("Views that return whole-row references are not automatically updatable.");
  
- 		if (bms_is_member(var->varattno, bms))
- 			return gettext_noop("Views that return the same column more than once are not automatically updatable.");
  
! 		bms = bms_add_member(bms, var->varattno);
  	}
- 	bms_free(bms);				/* just for cleanliness */
  
! 	return NULL;				/* the view is simply updatable */
  }
  
  
--- 2088,2191 ----
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * Check that the view has at least one updatable column. This is required
! 	 * for INSERT/UPDATE but not for DELETE.
  	 */
! 	if (check_cols)
  	{
! 		ListCell   *cell;
! 		bool		found;
  
! 		found = false;
! 		foreach(cell, viewquery->targetList)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(cell);
  
! 			if (view_col_is_auto_updatable(rtr, tle) == NULL)
! 			{
! 				found = true;
! 				break;
! 			}
! 		}
  
! 		if (!found)
! 			return gettext_noop("Views that have no updatable columns are not automatically updatable.");
! 	}
  
! 	return NULL;				/* the view is updatable */
! }
  
  
! /*
!  * view_cols_are_auto_updatable - test whether the all of the required columns
!  * of an auto-updatable view are actually updatable. Returns NULL (if all the
!  * required columns can be updated) or a message string giving the reason that
!  * they cannot be.
!  *
!  * This should be used for INSERT/UPDATE to ensure that we don't attempt to
!  * assign to any non-updatable columns.
!  *
!  * Additionally it may be used to retrieve the set of updatable columns in the
!  * view or, if one or more of the required columns is not updatable, the name
!  * of the first offending non-updatable column.
!  *
!  * The caller must have already verified that this is an auto-updatable view
!  * using view_query_is_auto_updatable.
!  *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether the referenced columns of the base relation are
!  * updatable.
!  */
! static const char *
! view_cols_are_auto_updatable(Query *viewquery,
! 							 Bitmapset *required_cols,
! 							 Bitmapset **updatable_cols,
! 							 char **non_updatable_col)
! {
! 	RangeTblRef *rtr;
! 	AttrNumber	col;
! 	ListCell   *cell;
! 
! 	/*
! 	 * The caller should have verified that this view is auto-updatable and
! 	 * so there should be a single base relation.
! 	 */
! 	Assert(list_length(viewquery->jointree->fromlist) == 1);
! 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
! 	Assert(IsA(rtr, RangeTblRef));
! 
! 	/* Initialize the optional return values */
! 	if (updatable_cols != NULL)
! 		*updatable_cols = NULL;
! 	if (non_updatable_col != NULL)
! 		*non_updatable_col = NULL;
! 
! 	/* Test each view column for updatability */
! 	col = -FirstLowInvalidHeapAttributeNumber;
! 	foreach(cell, viewquery->targetList)
! 	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		const char *col_update_detail;
! 
! 		col++;
! 		col_update_detail = view_col_is_auto_updatable(rtr, tle);
! 
! 		if (col_update_detail == NULL)
! 		{
! 			/* The column is updatable */
! 			if (updatable_cols != NULL)
! 				*updatable_cols = bms_add_member(*updatable_cols, col);
! 		}
! 		else if (bms_is_member(col, required_cols))
! 		{
! 			/* The required column is not updatable */
! 			if (non_updatable_col != NULL)
! 				*non_updatable_col = tle->resname;
! 			return col_update_detail;
! 		}
  	}
  
! 	return NULL;			/* all the required view columns are updatable */
  }
  
  
*************** view_query_is_auto_updatable(Query *view
*** 2088,2093 ****
--- 2193,2204 ----
   * relation_is_updatable - determine which update events the specified
   * relation supports.
   *
+  * Note that views may contain a mix of updatable and non-updatable columns.
+  * For a view to support INSERT/UPDATE it must have at least one updatable
+  * column, but there is no such restriction for DELETE. If include_cols is
+  * non-NULL, then only the specified columns are considered when testing for
+  * updatability.
+  *
   * 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
*************** view_query_is_auto_updatable(Query *view
*** 2105,2111 ****
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid, bool include_triggers)
  {
  	int			events = 0;
  	Relation	rel;
--- 2216,2224 ----
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid,
! 					  bool include_triggers,
! 					  Bitmapset *include_cols)
  {
  	int			events = 0;
  	Relation	rel;
*************** relation_is_updatable(Oid reloid, bool i
*** 2200,2231 ****
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW &&
! 		view_is_auto_updatable(rel) == NULL)
  	{
! 		Query	   *viewquery;
! 		RangeTblRef *rtr;
! 		RangeTblEntry *base_rte;
! 		Oid			baseoid;
! 
! 		/* The base relation must also be updatable */
! 		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 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);
  		}
  	}
  
--- 2313,2369 ----
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW)
  	{
! 		Query	   *viewquery = get_view_query(rel);
  
! 		if (view_query_is_auto_updatable(viewquery,
! 										 RelationIsSecurityView(rel),
! 										 false) == NULL)
  		{
! 			Bitmapset  *updatable_cols;
! 			int			auto_events;
! 			RangeTblRef *rtr;
! 			RangeTblEntry *base_rte;
! 			Oid			baseoid;
! 
! 			/*
! 			 * Determine which of the view's columns are updatable. If there
! 			 * are none within the set of of columns we are looking at, then
! 			 * the view doesn't support INSERT/UPDATE, but it may still
! 			 * support DELETE.
! 			 */
! 			view_cols_are_auto_updatable(viewquery, NULL,
! 										 &updatable_cols, NULL);
! 
! 			if (include_cols != NULL)
! 				updatable_cols = bms_int_members(updatable_cols, include_cols);
! 
! 			if (bms_is_empty(updatable_cols))
! 				auto_events = (1 << CMD_DELETE); /* May support DELETE */
! 			else
! 				auto_events = ALL_EVENTS; /* May support all events */
! 
! 			/*
! 			 * The base relation must also support these update commands.
! 			 * Tables are always updatable, but for any other kind of base
! 			 * relation we must do a recursive check limited to the columns
! 			 * referenced by the locally updatable columns in this view.
! 			 */
! 			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)
! 			{
! 				baseoid = base_rte->relid;
! 				include_cols = adjust_view_column_set(updatable_cols,
! 													  viewquery->targetList);
! 				auto_events &= relation_is_updatable(baseoid,
! 													 include_triggers,
! 													 include_cols);
! 			}
! 			events |= auto_events;
  		}
  	}
  
*************** relation_is_updatable(Oid reloid, bool i
*** 2241,2247 ****
   * This is used with simply-updatable views to map column-permissions sets for
   * the view columns onto the matching columns in the underlying base relation.
   * The targetlist is expected to be a list of plain Vars of the underlying
!  * relation (as per the checks above in view_is_auto_updatable).
   */
  static Bitmapset *
  adjust_view_column_set(Bitmapset *cols, List *targetlist)
--- 2379,2385 ----
   * This is used with simply-updatable views to map column-permissions sets for
   * the view columns onto the matching columns in the underlying base relation.
   * The targetlist is expected to be a list of plain Vars of the underlying
!  * relation (as per the checks above in view_query_is_auto_updatable).
   */
  static Bitmapset *
  adjust_view_column_set(Bitmapset *cols, List *targetlist)
*************** adjust_view_column_set(Bitmapset *cols,
*** 2319,2326 ****
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
- 	const char *auto_update_detail;
  	Query	   *viewquery;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
--- 2457,2464 ----
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
  	Query	   *viewquery;
+ 	const char *auto_update_detail;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
*************** rewriteTargetView(Query *parsetree, Rela
*** 2331,2338 ****
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be simply updatable, else fail */
! 	auto_update_detail = view_is_auto_updatable(view);
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
--- 2469,2482 ----
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be updatable, else fail */
! 	viewquery = get_view_query(view);
! 
! 	auto_update_detail =
! 		view_query_is_auto_updatable(viewquery,
! 									 RelationIsSecurityView(view),
! 									 parsetree->commandType != CMD_DELETE);
! 
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
*************** rewriteTargetView(Query *parsetree, Rela
*** 2369,2383 ****
  		}
  	}
  
  	/* Locate RTE describing the view in the outer query */
  	view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
  
  	/*
! 	 * If we get here, view_is_auto_updatable() has verified that the view
! 	 * contains a single base relation.
  	 */
- 	viewquery = get_view_query(view);
- 
  	Assert(list_length(viewquery->jointree->fromlist) == 1);
  	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
  	Assert(IsA(rtr, RangeTblRef));
--- 2513,2582 ----
  		}
  	}
  
+ 	/*
+ 	 * For INSERT/UPDATE the modified columns must all be updatable. Note that
+ 	 * we get the modified columns from the query's targetlist, not from the
+ 	 * result RTE's modifiedCols set, since rewriteTargetListIU may have added
+ 	 * additional targetlist entries for view defaults, and these must also be
+ 	 * updatable.
+ 	 */
+ 	if (parsetree->commandType != CMD_DELETE)
+ 	{
+ 		Bitmapset  *modified_cols = NULL;
+ 		char	   *non_updatable_col;
+ 
+ 		foreach(lc, parsetree->targetList)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 				modified_cols = bms_add_member(modified_cols,
+ 							tle->resno - FirstLowInvalidHeapAttributeNumber);
+ 		}
+ 
+ 		auto_update_detail = view_cols_are_auto_updatable(viewquery,
+ 														  modified_cols,
+ 														  NULL,
+ 														  &non_updatable_col);
+ 		if (auto_update_detail)
+ 		{
+ 			/*
+ 			 * This is a different error, caused by an attempt to update a
+ 			 * non-updatable column in an otherwise updatable view.
+ 			 */
+ 			switch (parsetree->commandType)
+ 			{
+ 				case CMD_INSERT:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot insert into column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				case CMD_UPDATE:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot update column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized CmdType: %d",
+ 						 (int) parsetree->commandType);
+ 					break;
+ 			}
+ 		}
+ 	}
+ 
  	/* Locate RTE describing the view in the outer query */
  	view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
  
  	/*
! 	 * If we get here, view_query_is_auto_updatable() has verified that the
! 	 * view contains a single base relation.
  	 */
  	Assert(list_length(viewquery->jointree->fromlist) == 1);
  	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
  	Assert(IsA(rtr, RangeTblRef));
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
new file mode 100644
index aecdcd0..8aba906
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 20,25 ****
--- 20,26 ----
  #include <math.h>
  #include <unistd.h>
  
+ #include "access/sysattr.h"
  #include "catalog/catalog.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_type.h"
*************** pg_relation_is_updatable(PG_FUNCTION_ARG
*** 547,563 ****
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	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.
--- 548,560 ----
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers, NULL));
  }
  
  /*
   * pg_column_is_updatable - determine whether a column is updatable
   *
!  * 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.
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 567,572 ****
--- 564,570 ----
  {
  	Oid			reloid = PG_GETARG_OID(0);
  	AttrNumber	attnum = PG_GETARG_INT16(1);
+ 	AttrNumber	col = attnum - FirstLowInvalidHeapAttributeNumber;
  	bool		include_triggers = PG_GETARG_BOOL(2);
  	int			events;
  
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 574,580 ****
  	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))
--- 572,579 ----
  	if (attnum <= 0)
  		PG_RETURN_BOOL(false);
  
! 	events = relation_is_updatable(reloid, include_triggers,
! 								   bms_make_singleton(col));
  
  	/* We require both updatability and deletability of the relation */
  #define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index f0604b0..c959590
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
*************** extern void AcquireRewriteLocks(Query *p
*** 22,30 ****
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
- extern const char *view_is_auto_updatable(Relation view);
  extern const char *view_query_is_auto_updatable(Query *viewquery,
! 										 bool security_barrier);
! extern int	relation_is_updatable(Oid reloid, bool include_triggers);
  
  #endif   /* REWRITEHANDLER_H */
--- 22,32 ----
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
  extern const char *view_query_is_auto_updatable(Query *viewquery,
! 										 bool security_barrier,
! 										 bool check_cols);
! extern int	relation_is_updatable(Oid reloid,
! 						  bool include_triggers,
! 						  Bitmapset *include_cols);
  
  #endif   /* REWRITEHANDLER_H */
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 4af9354..e463b43
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
***************
*** 1,7 ****
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views are rejected with useful error messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
--- 1,8 ----
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 17,34 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
--- 18,36 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
*************** SELECT table_name, is_insertable_into
*** 37,50 ****
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
-  ro_view14  | NO
-  ro_view15  | NO
-  ro_view16  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
--- 39,50 ----
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
+  ro_view21  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
*************** SELECT table_name, is_insertable_into
*** 52,62 ****
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
! (20 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
--- 52,65 ----
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
!  rw_view14  | YES
!  rw_view15  | YES
!  rw_view16  | YES
! (21 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
*************** SELECT table_name, is_updatable, is_inse
*** 65,78 ****
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
-  ro_view14  | NO           | NO
-  ro_view15  | NO           | NO
-  ro_view16  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
--- 68,79 ----
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
+  ro_view21  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
*************** SELECT table_name, is_updatable, is_inse
*** 80,90 ****
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
! (20 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
--- 81,94 ----
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
!  rw_view14  | YES          | YES
!  rw_view15  | YES          | YES
!  rw_view16  | YES          | YES
! (21 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
*************** SELECT table_name, column_name, is_updat
*** 96,107 ****
   ro_view12  | a             | NO
   ro_view13  | a             | NO
   ro_view13  | b             | NO
-  ro_view14  | ctid          | NO
-  ro_view15  | a             | NO
-  ro_view15  | upper         | NO
-  ro_view16  | a             | NO
-  ro_view16  | b             | NO
-  ro_view16  | aa            | NO
   ro_view17  | a             | NO
   ro_view17  | b             | NO
   ro_view18  | a             | NO
--- 100,105 ----
*************** SELECT table_name, column_name, is_updat
*** 119,124 ****
--- 117,125 ----
   ro_view20  | log_cnt       | NO
   ro_view20  | is_cycled     | NO
   ro_view20  | is_called     | NO
+  ro_view21  | a             | NO
+  ro_view21  | b             | NO
+  ro_view21  | g             | NO
   ro_view3   | ?column?      | NO
   ro_view4   | count         | NO
   ro_view5   | a             | NO
*************** SELECT table_name, column_name, is_updat
*** 131,138 ****
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
! (43 rows)
  
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 132,148 ----
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
!  rw_view14  | ctid          | NO
!  rw_view14  | a             | YES
!  rw_view14  | b             | YES
!  rw_view15  | a             | YES
!  rw_view15  | upper         | NO
!  rw_view16  | a             | YES
!  rw_view16  | b             | YES
!  rw_view16  | aa            | YES
! (48 rows)
  
+ -- Read-only views
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** DETAIL:  Views containing HAVING are not
*** 147,157 ****
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
--- 157,167 ----
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return aggregate functions are not automatically updatable
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return window functions are not automatically updatable
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
*************** INSERT INTO ro_view13 VALUES (3, 'Row 3'
*** 185,202 ****
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view14 VALUES (null);
! ERROR:  cannot insert into view "ro_view14"
! DETAIL:  Views that return system columns are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! ERROR:  cannot insert into view "ro_view15"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
! ERROR:  cannot insert into view "ro_view16"
! DETAIL:  Views that return the same column more than once are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 195,269 ----
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! ERROR:  cannot insert into column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! ERROR:  cannot update column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   3 | ROW 3
! (6 rows)
! 
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |      b      
! ----+-------------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   4 | Unspecified
! (6 rows)
! 
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! ERROR:  multiple assignments to same column "a"
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! ERROR:  multiple assignments to same column "a"
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!  -3 | Row 3
! (6 rows)
! 
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** UPDATE ro_view20 SET max_value=1000;
*** 213,220 ****
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 16 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
--- 280,291 ----
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+ UPDATE ro_view21 SET b=upper(b);
+ ERROR:  cannot update view "ro_view21"
+ DETAIL:  Views that return set-returning functions are not automatically updatable.
+ HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 17 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
*************** drop cascades to view ro_view8
*** 226,236 ****
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view ro_view15
! drop cascades to view ro_view16
  drop cascades to view ro_view18
  drop cascades to view ro_view4
! drop cascades to view ro_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
--- 297,308 ----
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view rw_view15
! drop cascades to view rw_view16
  drop cascades to view ro_view18
+ drop cascades to view ro_view21
  drop cascades to view ro_view4
! drop cascades to view rw_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
*************** SELECT * FROM rw_view1;
*** 1063,1068 ****
--- 1135,1282 ----
  
  DROP TABLE base_tbl CASCADE;
  NOTICE:  drop cascades to view rw_view1
+ -- views with updatable and non-updatable columns
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "ctid" of view "rw_view1"
+ DETAIL:  View columns that refer to system columns are not updatable.
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+   a  |         s         |         c         
+ -----+-------------------+-------------------
+  1.1 | 0.891207360061435 | 0.453596121425577
+ (1 row)
+ 
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+          s         
+ -------------------
+  0.867423225594017
+ (1 row)
+ 
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+         t         
+ ------------------
+  1.96475965724865
+ (1 row)
+ 
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+  base_a |         s         |         c         |        t         
+ --------+-------------------+-------------------+------------------
+    1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317
+ (1 row)
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view3"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view3 SET s = s; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+   a  
+ -----
+  0.2
+  0.3
+  0.4
+  0.5
+  0.6
+  0.7
+  0.8
+  0.9
+    1
+ (9 rows)
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+  table_name | is_insertable_into 
+ ------------+--------------------
+  rw_view1   | YES
+  rw_view2   | YES
+  rw_view3   | NO
+ (3 rows)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  rw_view1   | YES          | YES
+  rw_view2   | YES          | YES
+  rw_view3   | NO           | NO
+ (3 rows)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name, ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  rw_view1   | ctid        | NO
+  rw_view1   | s           | NO
+  rw_view1   | a           | YES
+  rw_view1   | c           | NO
+  rw_view2   | s           | NO
+  rw_view2   | c           | NO
+  rw_view2   | t           | NO
+  rw_view2   | base_a      | YES
+  rw_view2   | ctid        | NO
+  rw_view3   | s           | NO
+  rw_view3   | c           | NO
+  rw_view3   | t           | NO
+  rw_view3   | ctid        | NO
+ (13 rows)
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+  upd | ins | del 
+ -----+-----+-----
+  f   | f   | t
+ (1 row)
+ 
+ DROP TABLE base_tbl CASCADE;
+ NOTICE:  drop cascades to 3 other objects
+ DETAIL:  drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ drop cascades to view rw_view3
  -- inheritance tests
  CREATE TABLE base_tbl_parent (a int);
  CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 0481800..b7d9ba7
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
***************
*** 2,8 ****
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views are rejected with useful error messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
--- 2,9 ----
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 20,50 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
  
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
--- 21,53 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\_view%'
   ORDER BY table_name, ordinal_position;
  
+ -- Read-only views
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
*************** UPDATE ro_view10 SET a=a+1;
*** 58,70 ****
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! INSERT INTO ro_view14 VALUES (null);
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
--- 61,96 ----
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
+ UPDATE ro_view21 SET b=upper(b);
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
*************** SELECT * FROM rw_view1;
*** 510,515 ****
--- 536,603 ----
  
  DROP TABLE base_tbl CASCADE;
  
+ -- views with updatable and non-updatable columns
+ 
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ 
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ 
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ 
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ UPDATE rw_view3 SET s = s; -- should fail
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\_view%'
+  ORDER BY table_name, ordinal_position;
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+ 
+ DROP TABLE base_tbl CASCADE;
+ 
  -- inheritance tests
  
  CREATE TABLE base_tbl_parent (a int);
#4Marko Tiikkaja
marko@joh.to
In reply to: Dean Rasheed (#3)
1 attachment(s)
Re: Updatable view columns

Hi Dean,

First of all, thanks for working on this!

The patch compiles and applies fine (though with offsets). The feature
is in the SQL standard, and further improves an existing feature.

Some stuff I've spotted and fixed in the attached patch (hope you don't
mind, thought it'd be easier to just fix these rather than describing
them in email):
- Some typos I've spotted in some of the comments
- Fixed escaping of _ in regression tests

Other than these, the patch looks good to me. Will wait for your
thoughts and an updated patch before marking ready for committer.

Regards,
Marko Tiikkaja

Attachments:

updatable_views_typos.patchtext/plain; charset=windows-1252; name=updatable_views_typos.patchDownload
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index 4505290..e0fbe1e 100644
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -440,7 +440,7 @@ CREATE VIEW pg_comedies AS
 <programlisting>
 CREATE VIEW comedies AS
     SELECT f.*,
-           country_code_to_name(f.country_code) AS country
+           country_code_to_name(f.country_code) AS country,
            (SELECT avg(r.rating)
             FROM user_ratings r
             WHERE r.film_id = f.id) AS avg_rating
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 4c8c4f1..a35e63e 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -625,7 +625,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
  * table, but it's more convenient to do it here while we still have easy
  * access to the view's original RT index.)  This is only necessary for
  * trigger-updatable views, for which the view remains the result relation of
- * the query.  For auto-updatable we must not do this, since it might add
+ * the query.  For auto-updatable views we must not do this, since it might add
  * assignments to non-updatable view columns.  For rule-updatable views it is
  * unnecessary extra work, since the query will be rewritten with a different
  * result relation which will be processed when we recurse via RewriteQuery.
@@ -2102,7 +2102,7 @@ view_query_is_auto_updatable(Query *viewquery, bool security_barrier,
 
 
 /*
- * view_cols_are_auto_updatable - test whether the all of the required columns
+ * view_cols_are_auto_updatable - test whether all of the required columns
  * of an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
  * they cannot be.
@@ -2111,7 +2111,7 @@ view_query_is_auto_updatable(Query *viewquery, bool security_barrier,
  * assign to any non-updatable columns.
  *
  * Additionally it may be used to retrieve the set of updatable columns in the
- * view or, if one or more of the required columns is not updatable, the name
+ * view, or if one or more of the required columns is not updatable, the name
  * of the first offending non-updatable column.
  *
  * The caller must have already verified that this is an auto-updatable view
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index e463b43..c725bba 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -30,7 +30,7 @@ CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
 CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
 SELECT table_name, is_insertable_into
   FROM information_schema.tables
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
  table_name | is_insertable_into 
 ------------+--------------------
@@ -59,7 +59,7 @@ SELECT table_name, is_insertable_into
 
 SELECT table_name, is_updatable, is_insertable_into
   FROM information_schema.views
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
  table_name | is_updatable | is_insertable_into 
 ------------+--------------+--------------------
@@ -88,7 +88,7 @@ SELECT table_name, is_updatable, is_insertable_into
 
 SELECT table_name, column_name, is_updatable
   FROM information_schema.columns
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name, ordinal_position;
  table_name |  column_name  | is_updatable 
 ------------+---------------+--------------
@@ -1222,7 +1222,7 @@ SELECT * FROM base_tbl ORDER BY a;
 
 SELECT table_name, is_insertable_into
   FROM information_schema.tables
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
  table_name | is_insertable_into 
 ------------+--------------------
@@ -1233,7 +1233,7 @@ SELECT table_name, is_insertable_into
 
 SELECT table_name, is_updatable, is_insertable_into
   FROM information_schema.views
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
  table_name | is_updatable | is_insertable_into 
 ------------+--------------+--------------------
@@ -1244,7 +1244,7 @@ SELECT table_name, is_updatable, is_insertable_into
 
 SELECT table_name, column_name, is_updatable
   FROM information_schema.columns
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name, ordinal_position;
  table_name | column_name | is_updatable 
 ------------+-------------+--------------
@@ -1553,7 +1553,7 @@ CREATE TABLE base_tbl (a int);
 CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
-SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
+SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
  table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
 ---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
  regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index b7d9ba7..a77cf19 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -34,17 +34,17 @@ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; --
 
 SELECT table_name, is_insertable_into
   FROM information_schema.tables
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
 
 SELECT table_name, is_updatable, is_insertable_into
   FROM information_schema.views
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
 
 SELECT table_name, column_name, is_updatable
   FROM information_schema.columns
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name, ordinal_position;
 
 -- Read-only views
@@ -578,17 +578,17 @@ SELECT * FROM base_tbl ORDER BY a;
 
 SELECT table_name, is_insertable_into
   FROM information_schema.tables
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
 
 SELECT table_name, is_updatable, is_insertable_into
   FROM information_schema.views
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name;
 
 SELECT table_name, column_name, is_updatable
   FROM information_schema.columns
- WHERE table_name LIKE E'r_\_view%'
+ WHERE table_name LIKE E'r_\\_view%'
  ORDER BY table_name, ordinal_position;
 
 SELECT events & 4 != 0 AS upd,
@@ -699,7 +699,7 @@ CREATE TABLE base_tbl (a int);
 CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
-SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
+SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
 INSERT INTO rw_view1 VALUES (1); -- ok
#5Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Marko Tiikkaja (#4)
1 attachment(s)
Re: Updatable view columns

On 16 September 2013 21:14, Marko Tiikkaja <marko@joh.to> wrote:

Hi Dean,

First of all, thanks for working on this!

The patch compiles and applies fine (though with offsets). The feature is
in the SQL standard, and further improves an existing feature.

Some stuff I've spotted and fixed in the attached patch (hope you don't
mind, thought it'd be easier to just fix these rather than describing them
in email):
- Some typos I've spotted in some of the comments
- Fixed escaping of _ in regression tests

Other than these, the patch looks good to me. Will wait for your thoughts
and an updated patch before marking ready for committer.

Thanks for the review. Those changes all look sensible to me.

Here's an updated patch incorporating all your fixes, and rebased to
apply without offsets.

Regards,
Dean

Attachments:

updatable-view-cols.v3.patchapplication/octet-stream; name=updatable-view-cols.v3.patchDownload
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index 8102ec2..e0fbe1e
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 319,334 ****
  
       <listitem>
        <para>
!        All columns in the view's select list must be simple references to
!        columns of the underlying relation.  They cannot be expressions,
!        literals or functions.  System columns cannot be referenced, either.
!       </para>
!      </listitem>
! 
!      <listitem>
!       <para>
!        No column of the underlying relation can appear more than once in
!        the view's select list.
        </para>
       </listitem>
  
--- 319,326 ----
  
       <listitem>
        <para>
!        The view's select list must not contain any aggregates, window functions
!        or set-returning functions.
        </para>
       </listitem>
  
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 341,346 ****
--- 333,346 ----
     </para>
  
     <para>
+     An automatically updatable view may contain a mix of updatable and
+     non-updatable columns.  A column is updatable if it is a simple reference
+     to an updatable column of the underlying base relation; otherwise the
+     column is read-only, and an error will be raised if an <command>INSERT</>
+     or <command>UPDATE</> statement attempts to assign a value to it.
+    </para>
+ 
+    <para>
      If the view is automatically updatable the system will convert any
      <command>INSERT</>, <command>UPDATE</> or <command>DELETE</> statement
      on the view into the corresponding statement on the underlying base
*************** CREATE VIEW pg_comedies AS
*** 435,440 ****
--- 435,459 ----
    </para>
  
    <para>
+    Create a view with a mix of updatable and non-updatable columns:
+ 
+ <programlisting>
+ CREATE VIEW comedies AS
+     SELECT f.*,
+            country_code_to_name(f.country_code) AS country,
+            (SELECT avg(r.rating)
+             FROM user_ratings r
+             WHERE r.film_id = f.id) AS avg_rating
+     FROM films f
+     WHERE f.kind = 'Comedy';
+ </programlisting>
+    This view will support <command>INSERT</>, <command>UPDATE</> and
+    <command>DELETE</>.  All the columns from the <literal>films</> table will
+    be updatable, whereas the computed columns <literal>country</> and
+    <literal>avg_rating</> will be read-only.
+   </para>
+ 
+   <para>
     Create a recursive view consisting of the numbers from 1 to 100:
  <programlisting>
  CREATE RECURSIVE VIEW nums_1_100 (n) AS
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index adc74dd..fc257f6
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATExecSetRelOptions(Relation rel, List *
*** 8800,8806 ****
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_query_is_auto_updatable(view_query, security_barrier);
  
  			if (view_updatable_error)
  				ereport(ERROR,
--- 8800,8807 ----
  		if (check_option)
  		{   
  			const char *view_updatable_error =
! 				view_query_is_auto_updatable(view_query,
! 											 security_barrier, true);
  
  			if (view_updatable_error)
  				ereport(ERROR,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
new file mode 100644
index 832de43..aca40e7
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
*************** DefineView(ViewStmt *stmt, const char *q
*** 469,475 ****
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_query_is_auto_updatable(viewParse, security_barrier);
  
  		if (view_updatable_error)
  			ereport(ERROR,
--- 469,475 ----
  	if (check_option)
  	{
  		const char *view_updatable_error =
! 			view_query_is_auto_updatable(viewParse, security_barrier, true);
  
  		if (view_updatable_error)
  			ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 8a9a703..c52a374
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** static List *matchLocks(CmdType event, R
*** 61,66 ****
--- 61,68 ----
  		   int varno, Query *parsetree);
  static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
  			 bool forUpdatePushedDown);
+ static bool view_has_instead_trigger(Relation view, CmdType event);
+ static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
  
  
  /*
*************** adjustJoinTreeList(Query *parsetree, boo
*** 616,627 ****
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a view, add tlist entries for any unassigned-to
!  * attributes, assigning them their old values.  These will later get
!  * expanded to the output values of the view.  (This is equivalent to what
!  * the planner's expand_targetlist() will do for UPDATE on a regular table,
!  * but it's more convenient to do it here while we still have easy access
!  * to the view's original RT index.)
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
--- 618,635 ----
   * and UPDATE, replace explicit DEFAULT specifications with column default
   * expressions.
   *
!  * 2. For an UPDATE on a trigger-updatable view, add tlist entries for any
!  * unassigned-to attributes, assigning them their old values.  These will
!  * later get expanded to the output values of the view.  (This is equivalent
!  * to what the planner's expand_targetlist() will do for UPDATE on a regular
!  * table, but it's more convenient to do it here while we still have easy
!  * access to the view's original RT index.)  This is only necessary for
!  * trigger-updatable views, for which the view remains the result relation of
!  * the query.  For auto-updatable views we must not do this, since it might
!  * add assignments to non-updatable view columns.  For rule-updatable views it
!  * is unnecessary extra work, since the query will be rewritten with a
!  * different result relation which will be processed when we recurse via
!  * RewriteQuery.
   *
   * 3. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
*************** rewriteTargetListIU(Query *parsetree, Re
*** 783,793 ****
  		}
  
  		/*
! 		 * For an UPDATE on a view, provide a dummy entry whenever there is no
! 		 * explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW)
  		{
  			Node	   *new_expr;
  
--- 791,802 ----
  		}
  
  		/*
! 		 * For an UPDATE on a trigger-updatable view, provide a dummy entry
! 		 * whenever there is no explicit assignment.
  		 */
  		if (new_tle == NULL && commandType == CMD_UPDATE &&
! 			target_relation->rd_rel->relkind == RELKIND_VIEW &&
! 			view_has_instead_trigger(target_relation, CMD_UPDATE))
  		{
  			Node	   *new_expr;
  
*************** get_view_query(Relation view)
*** 1880,1886 ****
   * view_has_instead_trigger - does view have an INSTEAD OF trigger for event?
   *
   * If it does, we don't want to treat it as auto-updatable.  This test can't
!  * be folded into view_is_auto_updatable because it's not an error condition.
   */
  static bool
  view_has_instead_trigger(Relation view, CmdType event)
--- 1889,1896 ----
   * view_has_instead_trigger - does view have an INSTEAD OF trigger for event?
   *
   * If it does, we don't want to treat it as auto-updatable.  This test can't
!  * be folded into view_query_is_auto_updatable because it's not an error
!  * condition.
   */
  static bool
  view_has_instead_trigger(Relation view, CmdType event)
*************** view_has_instead_trigger(Relation view,
*** 1910,1964 ****
  
  
  /*
!  * view_is_auto_updatable -
!  *    Retrive the view definition and options and then determine if the view
!  *    can be auto-updated by calling view_query_is_auto_updatable().  Returns
!  *    NULL or a message string giving the reason the view is not auto
!  *    updateable.  See view_query_is_auto_updatable() for details.
!  *
!  *    The only view option which affects if a view can be auto-updated, today,
!  *    is the security_barrier option.  If other options are added later, they
!  *    will also need to be handled here.
!  *
!  * Caller must have verified that the relation is a view!
!  *
!  * Note that the checks performed here are local to this view.	We do not
!  * check whether the view's underlying base relation is updatable; that
!  * will be dealt with in later, recursive processing.
   *
!  * Also note that we don't check for INSTEAD triggers or rules here; those
!  * also prevent auto-update, but they must be checked for by the caller.
   */
! const char *
! view_is_auto_updatable(Relation view)
  {
! 	Query	   *viewquery = get_view_query(view);
! 	bool		security_barrier = RelationIsSecurityView(view);
  
! 	return view_query_is_auto_updatable(viewquery, security_barrier);
  }
  
  
  /*
!  * view_query_is_auto_updatable -
!  *	  Test if the specified view definition can be automatically updated, given
!  *    the view's options (currently only security_barrier affects a view's
!  *    auto-updatable status).
   *
!  *    This will either return NULL (if the view can be updated) or a message
!  *    string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are only based on the view
!  * definition. We do not check whether any base relations referred to by
!  * the view are updatable.
   */
  const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
- 	Bitmapset  *bms;
- 	ListCell   *cell;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
--- 1920,1983 ----
  
  
  /*
!  * view_col_is_auto_updatable - test whether the specified column of a view
!  * is auto-updatable. Returns NULL (if the column can be updated) or a message
!  * string giving the reason that it cannot be.
   *
!  * Note that the checks performed here are local to this view. We do not check
!  * whether the referenced column of the underlying base relation is updatable.
   */
! static const char *
! view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
  {
! 	Var		   *var = (Var *) tle->expr;
  
! 	/*
! 	 * For now, the only updatable columns we support are those that are Vars
! 	 * referring to user columns of the underlying base relation.
! 	 *
! 	 * The view targetlist may contain resjunk columns (e.g., a view defined
! 	 * like "SELECT * FROM t ORDER BY a+b" is auto-updatable) but such columns
! 	 * are not auto-updatable, and in fact should never appear in the outer
! 	 * query's targetlist.
! 	 */
! 	if (tle->resjunk)
! 		return gettext_noop("Junk view columns are not updatable.");
! 
! 	if (!IsA(var, Var) ||
! 		var->varno != rtr->rtindex ||
! 		var->varlevelsup != 0)
! 		return gettext_noop("View columns that are not columns of their base relation are not updatable.");
! 
! 	if (var->varattno < 0)
! 		return gettext_noop("View columns that refer to system columns are not updatable.");
! 
! 	if (var->varattno == 0)
! 		return gettext_noop("View columns that return whole-row references are not updatable.");
! 
! 	return NULL;				/* the view column is updatable */
  }
  
  
  /*
!  * view_query_is_auto_updatable - test whether the specified view definition
!  * represents an auto-updatable view. Returns NULL (if the view can be updated)
!  * or a message string giving the reason that it cannot be.
   *
!  * If check_cols is true, the view is required to have at least one updatable
!  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
!  * checked for updatability. See also view_cols_are_auto_updatable.
   *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether any base relations referred to by the view are
!  * updatable.
   */
  const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier,
! 							 bool check_cols)
  {
  	RangeTblRef *rtr;
  	RangeTblEntry *base_rte;
  
  	/*----------
  	 * Check if the view is simply updatable.  According to SQL-92 this means:
*************** view_query_is_auto_updatable(Query *view
*** 1975,1985 ****
--- 1994,2011 ----
  	 * arise in Postgres, since any such sub-query will not see any updates
  	 * executed by the outer query anyway, thanks to MVCC snapshotting.)
  	 *
+ 	 * We also relax the second restriction by supporting part of SQL:1999
+ 	 * feature T111, which allows for a mix of updatable and non-updatable
+ 	 * columns, provided that an INSERT or UPDATE doesn't attempt to assign to
+ 	 * a non-updatable column.
+ 	 *
  	 * In addition we impose these constraints, involving features that are
  	 * not part of SQL-92:
  	 *	- No CTEs (WITH clauses).
  	 *	- No OFFSET or LIMIT clauses (this matches a SQL:2008 restriction).
  	 *	- No system columns (including whole-row references) in the tlist.
+ 	 *	- No window functions in the tlist.
+ 	 *	- No set-returning functions in the tlist.
  	 *
  	 * Note that we do these checks without recursively expanding the view.
  	 * If the base relation is a view, we'll recursively deal with it later.
*************** view_query_is_auto_updatable(Query *view
*** 2004,2009 ****
--- 2030,2053 ----
  		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
  
  	/*
+ 	 * We must not allow window functions or set returning functions in the
+ 	 * targetlist. Otherwise we might end up inserting them into the quals of
+ 	 * the main query. We must also check for aggregates in the targetlist in
+ 	 * case they appear without a GROUP BY.
+ 	 *
+ 	 * These restrictions ensure that each row of the view corresponds to a
+ 	 * unique row in the underlying base relation.
+ 	 */
+ 	if (viewquery->hasAggs)
+ 		return gettext_noop("Views that return aggregate functions are not automatically updatable");
+ 
+ 	if (viewquery->hasWindowFuncs)
+ 		return gettext_noop("Views that return window functions are not automatically updatable");
+ 
+ 	if (expression_returns_set((Node *) viewquery->targetList))
+ 		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+ 
+ 	/*
  	 * For now, we also don't support security-barrier views, because of the
  	 * difficulty of keeping upper-level qual expressions away from
  	 * lower-level data.  This might get relaxed in the future.
*************** view_query_is_auto_updatable(Query *view
*** 2030,2071 ****
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * The view's targetlist entries should all be Vars referring to user
! 	 * columns of the base relation, and no two should refer to the same
! 	 * column.
! 	 *
! 	 * Note however that we should ignore resjunk entries.	This proviso is
! 	 * relevant because ORDER BY is not disallowed, and we shouldn't reject a
! 	 * view defined like "SELECT * FROM t ORDER BY a+b".
  	 */
! 	bms = NULL;
! 	foreach(cell, viewquery->targetList)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		Var		   *var = (Var *) tle->expr;
  
! 		if (tle->resjunk)
! 			continue;
  
! 		if (!IsA(var, Var) ||
! 			var->varno != rtr->rtindex ||
! 			var->varlevelsup != 0)
! 			return gettext_noop("Views that return columns that are not columns of their base relation are not automatically updatable.");
  
! 		if (var->varattno < 0)
! 			return gettext_noop("Views that return system columns are not automatically updatable.");
  
! 		if (var->varattno == 0)
! 			return gettext_noop("Views that return whole-row references are not automatically updatable.");
  
- 		if (bms_is_member(var->varattno, bms))
- 			return gettext_noop("Views that return the same column more than once are not automatically updatable.");
  
! 		bms = bms_add_member(bms, var->varattno);
  	}
- 	bms_free(bms);				/* just for cleanliness */
  
! 	return NULL;				/* the view is simply updatable */
  }
  
  
--- 2074,2177 ----
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
  	/*
! 	 * Check that the view has at least one updatable column. This is required
! 	 * for INSERT/UPDATE but not for DELETE.
  	 */
! 	if (check_cols)
  	{
! 		ListCell   *cell;
! 		bool		found;
  
! 		found = false;
! 		foreach(cell, viewquery->targetList)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(cell);
  
! 			if (view_col_is_auto_updatable(rtr, tle) == NULL)
! 			{
! 				found = true;
! 				break;
! 			}
! 		}
  
! 		if (!found)
! 			return gettext_noop("Views that have no updatable columns are not automatically updatable.");
! 	}
  
! 	return NULL;				/* the view is updatable */
! }
  
  
! /*
!  * view_cols_are_auto_updatable - test whether all of the required columns of
!  * an auto-updatable view are actually updatable. Returns NULL (if all the
!  * required columns can be updated) or a message string giving the reason that
!  * they cannot be.
!  *
!  * This should be used for INSERT/UPDATE to ensure that we don't attempt to
!  * assign to any non-updatable columns.
!  *
!  * Additionally it may be used to retrieve the set of updatable columns in the
!  * view, or if one or more of the required columns is not updatable, the name
!  * of the first offending non-updatable column.
!  *
!  * The caller must have already verified that this is an auto-updatable view
!  * using view_query_is_auto_updatable.
!  *
!  * Note that the checks performed here are only based on the view definition.
!  * We do not check whether the referenced columns of the base relation are
!  * updatable.
!  */
! static const char *
! view_cols_are_auto_updatable(Query *viewquery,
! 							 Bitmapset *required_cols,
! 							 Bitmapset **updatable_cols,
! 							 char **non_updatable_col)
! {
! 	RangeTblRef *rtr;
! 	AttrNumber	col;
! 	ListCell   *cell;
! 
! 	/*
! 	 * The caller should have verified that this view is auto-updatable and
! 	 * so there should be a single base relation.
! 	 */
! 	Assert(list_length(viewquery->jointree->fromlist) == 1);
! 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
! 	Assert(IsA(rtr, RangeTblRef));
! 
! 	/* Initialize the optional return values */
! 	if (updatable_cols != NULL)
! 		*updatable_cols = NULL;
! 	if (non_updatable_col != NULL)
! 		*non_updatable_col = NULL;
! 
! 	/* Test each view column for updatability */
! 	col = -FirstLowInvalidHeapAttributeNumber;
! 	foreach(cell, viewquery->targetList)
! 	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
! 		const char *col_update_detail;
! 
! 		col++;
! 		col_update_detail = view_col_is_auto_updatable(rtr, tle);
! 
! 		if (col_update_detail == NULL)
! 		{
! 			/* The column is updatable */
! 			if (updatable_cols != NULL)
! 				*updatable_cols = bms_add_member(*updatable_cols, col);
! 		}
! 		else if (bms_is_member(col, required_cols))
! 		{
! 			/* The required column is not updatable */
! 			if (non_updatable_col != NULL)
! 				*non_updatable_col = tle->resname;
! 			return col_update_detail;
! 		}
  	}
  
! 	return NULL;			/* all the required view columns are updatable */
  }
  
  
*************** view_query_is_auto_updatable(Query *view
*** 2073,2078 ****
--- 2179,2190 ----
   * relation_is_updatable - determine which update events the specified
   * relation supports.
   *
+  * Note that views may contain a mix of updatable and non-updatable columns.
+  * For a view to support INSERT/UPDATE it must have at least one updatable
+  * column, but there is no such restriction for DELETE. If include_cols is
+  * non-NULL, then only the specified columns are considered when testing for
+  * updatability.
+  *
   * 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
*************** view_query_is_auto_updatable(Query *view
*** 2090,2096 ****
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid, bool include_triggers)
  {
  	int			events = 0;
  	Relation	rel;
--- 2202,2210 ----
   * so that we can test for UPDATE plus DELETE support in a single call.)
   */
  int
! relation_is_updatable(Oid reloid,
! 					  bool include_triggers,
! 					  Bitmapset *include_cols)
  {
  	int			events = 0;
  	Relation	rel;
*************** relation_is_updatable(Oid reloid, bool i
*** 2185,2216 ****
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW &&
! 		view_is_auto_updatable(rel) == NULL)
  	{
! 		Query	   *viewquery;
! 		RangeTblRef *rtr;
! 		RangeTblEntry *base_rte;
! 		Oid			baseoid;
! 
! 		/* The base relation must also be updatable */
! 		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 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);
  		}
  	}
  
--- 2299,2355 ----
  	}
  
  	/* Check if this is an automatically updatable view */
! 	if (rel->rd_rel->relkind == RELKIND_VIEW)
  	{
! 		Query	   *viewquery = get_view_query(rel);
  
! 		if (view_query_is_auto_updatable(viewquery,
! 										 RelationIsSecurityView(rel),
! 										 false) == NULL)
  		{
! 			Bitmapset  *updatable_cols;
! 			int			auto_events;
! 			RangeTblRef *rtr;
! 			RangeTblEntry *base_rte;
! 			Oid			baseoid;
! 
! 			/*
! 			 * Determine which of the view's columns are updatable. If there
! 			 * are none within the set of of columns we are looking at, then
! 			 * the view doesn't support INSERT/UPDATE, but it may still
! 			 * support DELETE.
! 			 */
! 			view_cols_are_auto_updatable(viewquery, NULL,
! 										 &updatable_cols, NULL);
! 
! 			if (include_cols != NULL)
! 				updatable_cols = bms_int_members(updatable_cols, include_cols);
! 
! 			if (bms_is_empty(updatable_cols))
! 				auto_events = (1 << CMD_DELETE); /* May support DELETE */
! 			else
! 				auto_events = ALL_EVENTS; /* May support all events */
! 
! 			/*
! 			 * The base relation must also support these update commands.
! 			 * Tables are always updatable, but for any other kind of base
! 			 * relation we must do a recursive check limited to the columns
! 			 * referenced by the locally updatable columns in this view.
! 			 */
! 			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)
! 			{
! 				baseoid = base_rte->relid;
! 				include_cols = adjust_view_column_set(updatable_cols,
! 													  viewquery->targetList);
! 				auto_events &= relation_is_updatable(baseoid,
! 													 include_triggers,
! 													 include_cols);
! 			}
! 			events |= auto_events;
  		}
  	}
  
*************** relation_is_updatable(Oid reloid, bool i
*** 2226,2232 ****
   * This is used with simply-updatable views to map column-permissions sets for
   * the view columns onto the matching columns in the underlying base relation.
   * The targetlist is expected to be a list of plain Vars of the underlying
!  * relation (as per the checks above in view_is_auto_updatable).
   */
  static Bitmapset *
  adjust_view_column_set(Bitmapset *cols, List *targetlist)
--- 2365,2371 ----
   * This is used with simply-updatable views to map column-permissions sets for
   * the view columns onto the matching columns in the underlying base relation.
   * The targetlist is expected to be a list of plain Vars of the underlying
!  * relation (as per the checks above in view_query_is_auto_updatable).
   */
  static Bitmapset *
  adjust_view_column_set(Bitmapset *cols, List *targetlist)
*************** adjust_view_column_set(Bitmapset *cols,
*** 2304,2311 ****
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
- 	const char *auto_update_detail;
  	Query	   *viewquery;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
--- 2443,2450 ----
  static Query *
  rewriteTargetView(Query *parsetree, Relation view)
  {
  	Query	   *viewquery;
+ 	const char *auto_update_detail;
  	RangeTblRef *rtr;
  	int			base_rt_index;
  	int			new_rt_index;
*************** rewriteTargetView(Query *parsetree, Rela
*** 2316,2323 ****
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be simply updatable, else fail */
! 	auto_update_detail = view_is_auto_updatable(view);
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
--- 2455,2468 ----
  	List	   *view_targetlist;
  	ListCell   *lc;
  
! 	/* The view must be updatable, else fail */
! 	viewquery = get_view_query(view);
! 
! 	auto_update_detail =
! 		view_query_is_auto_updatable(viewquery,
! 									 RelationIsSecurityView(view),
! 									 parsetree->commandType != CMD_DELETE);
! 
  	if (auto_update_detail)
  	{
  		/* messages here should match execMain.c's CheckValidResultRel */
*************** rewriteTargetView(Query *parsetree, Rela
*** 2354,2368 ****
  		}
  	}
  
  	/* Locate RTE describing the view in the outer query */
  	view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
  
  	/*
! 	 * If we get here, view_is_auto_updatable() has verified that the view
! 	 * contains a single base relation.
  	 */
- 	viewquery = get_view_query(view);
- 
  	Assert(list_length(viewquery->jointree->fromlist) == 1);
  	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
  	Assert(IsA(rtr, RangeTblRef));
--- 2499,2568 ----
  		}
  	}
  
+ 	/*
+ 	 * For INSERT/UPDATE the modified columns must all be updatable. Note that
+ 	 * we get the modified columns from the query's targetlist, not from the
+ 	 * result RTE's modifiedCols set, since rewriteTargetListIU may have added
+ 	 * additional targetlist entries for view defaults, and these must also be
+ 	 * updatable.
+ 	 */
+ 	if (parsetree->commandType != CMD_DELETE)
+ 	{
+ 		Bitmapset  *modified_cols = NULL;
+ 		char	   *non_updatable_col;
+ 
+ 		foreach(lc, parsetree->targetList)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 				modified_cols = bms_add_member(modified_cols,
+ 							tle->resno - FirstLowInvalidHeapAttributeNumber);
+ 		}
+ 
+ 		auto_update_detail = view_cols_are_auto_updatable(viewquery,
+ 														  modified_cols,
+ 														  NULL,
+ 														  &non_updatable_col);
+ 		if (auto_update_detail)
+ 		{
+ 			/*
+ 			 * This is a different error, caused by an attempt to update a
+ 			 * non-updatable column in an otherwise updatable view.
+ 			 */
+ 			switch (parsetree->commandType)
+ 			{
+ 				case CMD_INSERT:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot insert into column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				case CMD_UPDATE:
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot update column \"%s\" of view \"%s\"",
+ 									non_updatable_col,
+ 									RelationGetRelationName(view)),
+ 							 errdetail_internal("%s", _(auto_update_detail))));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized CmdType: %d",
+ 						 (int) parsetree->commandType);
+ 					break;
+ 			}
+ 		}
+ 	}
+ 
  	/* Locate RTE describing the view in the outer query */
  	view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
  
  	/*
! 	 * If we get here, view_query_is_auto_updatable() has verified that the
! 	 * view contains a single base relation.
  	 */
  	Assert(list_length(viewquery->jointree->fromlist) == 1);
  	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
  	Assert(IsA(rtr, RangeTblRef));
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
new file mode 100644
index aecdcd0..8aba906
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 20,25 ****
--- 20,26 ----
  #include <math.h>
  #include <unistd.h>
  
+ #include "access/sysattr.h"
  #include "catalog/catalog.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_type.h"
*************** pg_relation_is_updatable(PG_FUNCTION_ARG
*** 547,563 ****
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	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.
--- 548,560 ----
  	Oid			reloid = PG_GETARG_OID(0);
  	bool		include_triggers = PG_GETARG_BOOL(1);
  
! 	PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers, NULL));
  }
  
  /*
   * pg_column_is_updatable - determine whether a column is updatable
   *
!  * 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.
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 567,572 ****
--- 564,570 ----
  {
  	Oid			reloid = PG_GETARG_OID(0);
  	AttrNumber	attnum = PG_GETARG_INT16(1);
+ 	AttrNumber	col = attnum - FirstLowInvalidHeapAttributeNumber;
  	bool		include_triggers = PG_GETARG_BOOL(2);
  	int			events;
  
*************** pg_column_is_updatable(PG_FUNCTION_ARGS)
*** 574,580 ****
  	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))
--- 572,579 ----
  	if (attnum <= 0)
  		PG_RETURN_BOOL(false);
  
! 	events = relation_is_updatable(reloid, include_triggers,
! 								   bms_make_singleton(col));
  
  	/* We require both updatability and deletability of the relation */
  #define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index f0604b0..c959590
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
*************** extern void AcquireRewriteLocks(Query *p
*** 22,30 ****
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
- extern const char *view_is_auto_updatable(Relation view);
  extern const char *view_query_is_auto_updatable(Query *viewquery,
! 										 bool security_barrier);
! extern int	relation_is_updatable(Oid reloid, bool include_triggers);
  
  #endif   /* REWRITEHANDLER_H */
--- 22,32 ----
  
  extern Node *build_column_default(Relation rel, int attrno);
  extern Query *get_view_query(Relation view);
  extern const char *view_query_is_auto_updatable(Query *viewquery,
! 										 bool security_barrier,
! 										 bool check_cols);
! extern int	relation_is_updatable(Oid reloid,
! 						  bool include_triggers,
! 						  Bitmapset *include_cols);
  
  #endif   /* REWRITEHANDLER_H */
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 4af9354..c725bba
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
***************
*** 1,7 ****
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views are rejected with useful error messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
--- 1,8 ----
  --
  -- UPDATABLE VIEWS
  --
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
  CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 17,34 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
--- 18,36 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name;
   table_name | is_insertable_into 
  ------------+--------------------
*************** SELECT table_name, is_insertable_into
*** 37,50 ****
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
-  ro_view14  | NO
-  ro_view15  | NO
-  ro_view16  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
--- 39,50 ----
   ro_view11  | NO
   ro_view12  | NO
   ro_view13  | NO
   ro_view17  | NO
   ro_view18  | NO
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
+  ro_view21  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
*************** SELECT table_name, is_insertable_into
*** 52,62 ****
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
! (20 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
--- 52,65 ----
   ro_view7   | NO
   ro_view8   | NO
   ro_view9   | NO
!  rw_view14  | YES
!  rw_view15  | YES
!  rw_view16  | YES
! (21 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name;
   table_name | is_updatable | is_insertable_into 
  ------------+--------------+--------------------
*************** SELECT table_name, is_updatable, is_inse
*** 65,78 ****
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
-  ro_view14  | NO           | NO
-  ro_view15  | NO           | NO
-  ro_view16  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
--- 68,79 ----
   ro_view11  | NO           | NO
   ro_view12  | NO           | NO
   ro_view13  | NO           | NO
   ro_view17  | NO           | NO
   ro_view18  | NO           | NO
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
+  ro_view21  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
*************** SELECT table_name, is_updatable, is_inse
*** 80,90 ****
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
! (20 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
--- 81,94 ----
   ro_view7   | NO           | NO
   ro_view8   | NO           | NO
   ro_view9   | NO           | NO
!  rw_view14  | YES          | YES
!  rw_view15  | YES          | YES
!  rw_view16  | YES          | YES
! (21 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name, ordinal_position;
   table_name |  column_name  | is_updatable 
  ------------+---------------+--------------
*************** SELECT table_name, column_name, is_updat
*** 96,107 ****
   ro_view12  | a             | NO
   ro_view13  | a             | NO
   ro_view13  | b             | NO
-  ro_view14  | ctid          | NO
-  ro_view15  | a             | NO
-  ro_view15  | upper         | NO
-  ro_view16  | a             | NO
-  ro_view16  | b             | NO
-  ro_view16  | aa            | NO
   ro_view17  | a             | NO
   ro_view17  | b             | NO
   ro_view18  | a             | NO
--- 100,105 ----
*************** SELECT table_name, column_name, is_updat
*** 119,124 ****
--- 117,125 ----
   ro_view20  | log_cnt       | NO
   ro_view20  | is_cycled     | NO
   ro_view20  | is_called     | NO
+  ro_view21  | a             | NO
+  ro_view21  | b             | NO
+  ro_view21  | g             | NO
   ro_view3   | ?column?      | NO
   ro_view4   | count         | NO
   ro_view5   | a             | NO
*************** SELECT table_name, column_name, is_updat
*** 131,138 ****
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
! (43 rows)
  
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 132,148 ----
   ro_view8   | b             | NO
   ro_view9   | a             | NO
   ro_view9   | b             | NO
!  rw_view14  | ctid          | NO
!  rw_view14  | a             | YES
!  rw_view14  | b             | YES
!  rw_view15  | a             | YES
!  rw_view15  | upper         | NO
!  rw_view16  | a             | YES
!  rw_view16  | b             | YES
!  rw_view16  | aa            | YES
! (48 rows)
  
+ -- Read-only views
  DELETE FROM ro_view1;
  ERROR:  cannot delete from view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** DETAIL:  Views containing HAVING are not
*** 147,157 ****
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
--- 157,167 ----
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view4;
  ERROR:  cannot delete from view "ro_view4"
! DETAIL:  Views that return aggregate functions are not automatically updatable
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view5;
  ERROR:  cannot delete from view "ro_view5"
! DETAIL:  Views that return window functions are not automatically updatable
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
  DELETE FROM ro_view6;
  ERROR:  cannot delete from view "ro_view6"
*************** INSERT INTO ro_view13 VALUES (3, 'Row 3'
*** 185,202 ****
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view14 VALUES (null);
! ERROR:  cannot insert into view "ro_view14"
! DETAIL:  Views that return system columns are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! ERROR:  cannot insert into view "ro_view15"
! DETAIL:  Views that return columns that are not columns of their base relation are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
! ERROR:  cannot insert into view "ro_view16"
! DETAIL:  Views that return the same column more than once are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
--- 195,269 ----
  ERROR:  cannot insert into view "ro_view13"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! ERROR:  cannot insert into column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! ERROR:  cannot update column "ctid" of view "rw_view14"
! DETAIL:  View columns that refer to system columns are not updatable.
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   3 | ROW 3
! (6 rows)
! 
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! ERROR:  cannot insert into column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! ERROR:  cannot update column "upper" of view "rw_view15"
! DETAIL:  View columns that are not columns of their base relation are not updatable.
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |      b      
! ----+-------------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!   4 | Unspecified
! (6 rows)
! 
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! ERROR:  multiple assignments to same column "a"
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! ERROR:  multiple assignments to same column "a"
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
!  a  |   b    
! ----+--------
!  -2 | Row -2
!  -1 | Row -1
!   0 | Row 0
!   1 | Row 1
!   2 | Row 2
!  -3 | Row 3
! (6 rows)
! 
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
*************** UPDATE ro_view20 SET max_value=1000;
*** 213,220 ****
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 16 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
--- 280,291 ----
  ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+ UPDATE ro_view21 SET b=upper(b);
+ ERROR:  cannot update view "ro_view21"
+ DETAIL:  Views that return set-returning functions are not automatically updatable.
+ HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 17 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
*************** drop cascades to view ro_view8
*** 226,236 ****
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view ro_view15
! drop cascades to view ro_view16
  drop cascades to view ro_view18
  drop cascades to view ro_view4
! drop cascades to view ro_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
--- 297,308 ----
  drop cascades to view ro_view9
  drop cascades to view ro_view11
  drop cascades to view ro_view13
! drop cascades to view rw_view15
! drop cascades to view rw_view16
  drop cascades to view ro_view18
+ drop cascades to view ro_view21
  drop cascades to view ro_view4
! drop cascades to view rw_view14
  DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  NOTICE:  drop cascades to view ro_view20
*************** SELECT * FROM rw_view1;
*** 1063,1068 ****
--- 1135,1282 ----
  
  DROP TABLE base_tbl CASCADE;
  NOTICE:  drop cascades to view rw_view1
+ -- views with updatable and non-updatable columns
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "ctid" of view "rw_view1"
+ DETAIL:  View columns that refer to system columns are not updatable.
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+   a  |         s         |         c         
+ -----+-------------------+-------------------
+  1.1 | 0.891207360061435 | 0.453596121425577
+ (1 row)
+ 
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+          s         
+ -------------------
+  0.867423225594017
+ (1 row)
+ 
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+         t         
+ ------------------
+  1.96475965724865
+ (1 row)
+ 
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ ERROR:  cannot update column "t" of view "rw_view2"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+  base_a |         s         |         c         |        t         
+ --------+-------------------+-------------------+------------------
+    1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317
+ (1 row)
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ ERROR:  cannot insert into column "t" of view "rw_view3"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ ERROR:  cannot insert into column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ UPDATE rw_view3 SET s = s; -- should fail
+ ERROR:  cannot update column "s" of view "rw_view1"
+ DETAIL:  View columns that are not columns of their base relation are not updatable.
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+   a  
+ -----
+  0.2
+  0.3
+  0.4
+  0.5
+  0.6
+  0.7
+  0.8
+  0.9
+    1
+ (9 rows)
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name;
+  table_name | is_insertable_into 
+ ------------+--------------------
+  rw_view1   | YES
+  rw_view2   | YES
+  rw_view3   | NO
+ (3 rows)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name;
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  rw_view1   | YES          | YES
+  rw_view2   | YES          | YES
+  rw_view3   | NO           | NO
+ (3 rows)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name, ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  rw_view1   | ctid        | NO
+  rw_view1   | s           | NO
+  rw_view1   | a           | YES
+  rw_view1   | c           | NO
+  rw_view2   | s           | NO
+  rw_view2   | c           | NO
+  rw_view2   | t           | NO
+  rw_view2   | base_a      | YES
+  rw_view2   | ctid        | NO
+  rw_view3   | s           | NO
+  rw_view3   | c           | NO
+  rw_view3   | t           | NO
+  rw_view3   | ctid        | NO
+ (13 rows)
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+  upd | ins | del 
+ -----+-----+-----
+  f   | f   | t
+ (1 row)
+ 
+ DROP TABLE base_tbl CASCADE;
+ NOTICE:  drop cascades to 3 other objects
+ DETAIL:  drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ drop cascades to view rw_view3
  -- inheritance tests
  CREATE TABLE base_tbl_parent (a int);
  CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
*************** CREATE TABLE base_tbl (a int);
*** 1339,1345 ****
  CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
  CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
  CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
! SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
   table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
  ---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
   regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
--- 1553,1559 ----
  CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
  CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
  CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
! SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
   table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
  ---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
   regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 0481800..a77cf19
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
***************
*** 2,8 ****
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views are rejected with useful error messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
--- 2,9 ----
  -- UPDATABLE VIEWS
  --
  
! -- check that non-updatable views and columns are rejected with useful error
! -- messages
  
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
*************** CREATE VIEW ro_view10 AS SELECT 1 AS a;
*** 20,50 ****
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW ro_view14 AS SELECT ctid FROM base_tbl; -- System columns not supported
! CREATE VIEW ro_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function in targetlist
! CREATE VIEW ro_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE 'ro_view%'
   ORDER BY table_name, ordinal_position;
  
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
--- 21,53 ----
  CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
  CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
  CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
! CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
! CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
! CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
  CREATE VIEW ro_view18 WITH (security_barrier = true)
    AS SELECT * FROM base_tbl; -- Security barrier views not updatable
  CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
  CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
+ CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name;
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name;
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
!  WHERE table_name LIKE E'r_\\_view%'
   ORDER BY table_name, ordinal_position;
  
+ -- Read-only views
  DELETE FROM ro_view1;
  DELETE FROM ro_view2;
  DELETE FROM ro_view3;
*************** UPDATE ro_view10 SET a=a+1;
*** 58,70 ****
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! INSERT INTO ro_view14 VALUES (null);
! INSERT INTO ro_view15 VALUES (3, 'ROW 3');
! INSERT INTO ro_view16 VALUES (3, 'Row 3', 3);
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
--- 61,96 ----
  UPDATE ro_view11 SET a=a+1;
  UPDATE ro_view12 SET a=a+1;
  INSERT INTO ro_view13 VALUES (3, 'Row 3');
! -- Partially updatable view
! INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
! INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
! UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view14 WHERE a=3; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
! INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
! ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
! INSERT INTO rw_view15 (a) VALUES (4); -- should fail
! UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
! UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
! UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view15 WHERE a=4; -- should be OK
! -- Partially updatable view
! INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
! INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
! UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
! UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
! SELECT * FROM base_tbl;
! DELETE FROM rw_view16 WHERE a=-3; -- should be OK
! -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
  INSERT INTO ro_view18 VALUES (3, 'ROW 3');
  DELETE FROM ro_view19;
  UPDATE ro_view20 SET max_value=1000;
+ UPDATE ro_view21 SET b=upper(b);
  
  DROP TABLE base_tbl CASCADE;
  DROP VIEW ro_view10, ro_view12, ro_view19;
*************** SELECT * FROM rw_view1;
*** 510,515 ****
--- 536,603 ----
  
  DROP TABLE base_tbl CASCADE;
  
+ -- views with updatable and non-updatable columns
+ 
+ CREATE TABLE base_tbl(a float);
+ INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+ 
+ CREATE VIEW rw_view1 AS
+   SELECT ctid, sin(a) s, a, cos(a) c
+   FROM base_tbl
+   WHERE a != 0
+   ORDER BY abs(a);
+ 
+ INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+ UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+ DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+ 
+ CREATE VIEW rw_view2 AS
+   SELECT s, c, s/c t, a base_a, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+ UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+ DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+ 
+ CREATE VIEW rw_view3 AS
+   SELECT s, c, s/c t, ctid
+   FROM rw_view1;
+ 
+ INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ UPDATE rw_view3 SET s = s; -- should fail
+ DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+ SELECT * FROM base_tbl ORDER BY a;
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE E'r_\\_view%'
+  ORDER BY table_name, ordinal_position;
+ 
+ SELECT events & 4 != 0 AS upd,
+        events & 8 != 0 AS ins,
+        events & 16 != 0 AS del
+   FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+ 
+ DROP TABLE base_tbl CASCADE;
+ 
  -- inheritance tests
  
  CREATE TABLE base_tbl_parent (a int);
*************** CREATE TABLE base_tbl (a int);
*** 611,617 ****
  CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
  CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
  CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
! SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
  
  INSERT INTO rw_view1 VALUES (-1); -- ok
  INSERT INTO rw_view1 VALUES (1); -- ok
--- 699,705 ----
  CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
  CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
  CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
! SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
  
  INSERT INTO rw_view1 VALUES (-1); -- ok
  INSERT INTO rw_view1 VALUES (1); -- ok
#6Marko Tiikkaja
marko@joh.to
In reply to: Dean Rasheed (#5)
Re: Updatable view columns

On 2013-09-17 12:53, Dean Rasheed wrote:

Thanks for the review. Those changes all look sensible to me.

Here's an updated patch incorporating all your fixes, and rebased to
apply without offsets.

Looks good to me. Marking this one ready for committer.

Regards,
Marko Tiikkaja

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Robert Haas
robertmhaas@gmail.com
In reply to: Marko Tiikkaja (#6)
Re: Updatable view columns

On Tue, Sep 17, 2013 at 6:16 PM, Marko Tiikkaja <marko@joh.to> wrote:

On 2013-09-17 12:53, Dean Rasheed wrote:

Thanks for the review. Those changes all look sensible to me.

Here's an updated patch incorporating all your fixes, and rebased to
apply without offsets.

Looks good to me. Marking this one ready for committer.

Committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Robert Haas (#7)
Re: Updatable view columns

On 18 October 2013 15:43, Robert Haas <robertmhaas@gmail.com> wrote:

Committed.

Excellent. Thank you!
And thank you Marko for your thorough review.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers