On Tue, 12 Feb 2019 at 10:33, Dean Rasheed <dean.a.rash...@gmail.com> wrote:
> Here's an updated patch ...

So I pushed that. However, ...

Playing around with it some more, I realised that whilst this appeared
to fix the reported problem, it exposes another issue which is down to
the interaction between rewriteTargetListIU() and rewriteValuesRTE()
--- for an INSERT with a VALUES RTE, rewriteTargetListIU() computes a
list of target attribute numbers (attrno_list) from the targetlist in
its original order, which rewriteValuesRTE() then relies on being the
same length (and in the same order) as each of the lists in the VALUES
RTE. That's OK for the initial invocation of those functions, but it
breaks down when they're recursively invoked for auto-updatable views.

For example, the following test-case, based on a slight variation of
the new regression tests:

create table foo (a int default 1, b int);
create view foo_v as select * from foo;
alter view foo_v alter column b set default 2;
insert into foo_v values (default), (default);

triggers the following Assert in rewriteValuesRTE():

    /* Check list lengths (we can assume all the VALUES sublists are alike) */
    Assert(list_length(attrnos) == list_length(linitial(rte->values_lists)));

What's happening is that the initial targetlist, which contains just 1
entry for the column a, gains another entry to set the default for
column b from the view. Then, when it recurses into
rewriteTargetListIU()/rewriteValuesRTE() for the base relation, the
size of the targetlist (now 2) no longer matches the sizes of the
VALUES RTE lists (1).

I think that that failed Assert was unreachable prior to 41531e42d3,
because the old version of rewriteValuesRTE() would always replace all
unresolved DEFAULT items with NULLs, so when it recursed into
rewriteValuesRTE() for the base relation, it would always bail out
early because there would be no DEFAULT items left, and so it would
fail to notice the list size mismatch.

My first thought was that this could be fixed by having
rewriteTargetListIU() compute attrno_list using only those targetlist
entries that refer to the VALUES RTE, and thus omit any new entries
added due to view defaults. That doesn't work though, because that's
not the only way that a list size mismatch can be triggered --- it's
also possible that rewriteTargetListIU() will merge together
targetlist entries, for example if they're array element assignments
referring to the same column, in which case the rewritten targetlist
could be shorter than the VALUES RTE lists, and attempting to compute
attrno_list from it correctly would be trickier.

So actually, I think the right way to fix this is to give up trying to
compute attrno_list in rewriteTargetListIU(), and have
rewriteValuesRTE() work out the attribute assignments itself for each
column of the VALUES RTE by examining the rewritten targetlist. That
looks to be quite straightforward, and results in a cleaner separation
of logic between the 2 functions, per the attached patch.

I think that rewriteValuesRTE() should only ever see DEFAULT items for
columns that are simple assignments to columns of the target relation,
so it only needs to work out the target attribute numbers for TLEs
that contain simple Vars referring to the VALUES RTE. Any DEFAULT seen
in a column not matching that would be an error, but actually I think
such a thing ought to be a "can't happen" error because of the prior
checks during parse analysis, so I've used elog() to report if this
does happen.

Incidentally, it looks like the part of rewriteValuesRTE()'s comment
about subscripted and field assignment has been wrong since
a3c7a993d5, so I've attempted to clarify that. That will need to look
different pre-9.6, I think.

Thoughts?

Regards,
Dean
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 7eb41ff..73fac75
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -67,14 +67,13 @@ static List *rewriteTargetListIU(List *t
 					CmdType commandType,
 					OverridingKind override,
 					Relation target_relation,
-					int result_rti,
-					List **attrno_list);
+					int result_rti);
 static TargetEntry *process_matched_tle(TargetEntry *src_tle,
 					TargetEntry *prior_tle,
 					const char *attrName);
 static Node *get_assignment_input(Node *node);
-static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte,
-				 Relation target_relation, List *attrnos, bool force_nulls);
+static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
+				 Relation target_relation, bool force_nulls);
 static void markQueryForLocking(Query *qry, Node *jtnode,
 					LockClauseStrength strength, LockWaitPolicy waitPolicy,
 					bool pushedDown);
@@ -705,11 +704,6 @@ adjustJoinTreeList(Query *parsetree, boo
  * is not needed for rewriting, but will be needed by the planner, and we
  * can do it essentially for free while handling the other items.
  *
- * If attrno_list isn't NULL, we return an additional output besides the
- * rewritten targetlist: an integer list of the assigned-to attnums, in
- * order of the original tlist's non-junk entries.  This is needed for
- * processing VALUES RTEs.
- *
  * Note that for an inheritable UPDATE, this processing is only done once,
  * using the parent relation as reference.  It must not do anything that
  * will not be correct when transposed to the child relation(s).  (Step 4
@@ -722,8 +716,7 @@ rewriteTargetListIU(List *targetList,
 					CmdType commandType,
 					OverridingKind override,
 					Relation target_relation,
-					int result_rti,
-					List **attrno_list)
+					int result_rti)
 {
 	TargetEntry **new_tles;
 	List	   *new_tlist = NIL;
@@ -734,9 +727,6 @@ rewriteTargetListIU(List *targetList,
 				numattrs;
 	ListCell   *temp;
 
-	if (attrno_list)			/* initialize optional result list */
-		*attrno_list = NIL;
-
 	/*
 	 * We process the normal (non-junk) attributes by scanning the input tlist
 	 * once and transferring TLEs into an array, then scanning the array to
@@ -762,10 +752,6 @@ rewriteTargetListIU(List *targetList,
 				elog(ERROR, "bogus resno %d in targetlist", attrno);
 			att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
 
-			/* put attrno into attrno_list even if it's dropped */
-			if (attrno_list)
-				*attrno_list = lappend_int(*attrno_list, attrno);
-
 			/* We can (and must) ignore deleted attributes */
 			if (att_tup->attisdropped)
 				continue;
@@ -1240,22 +1226,26 @@ searchForDefault(RangeTblEntry *rte)
  * an insert into an auto-updatable view, and the product queries are inserts
  * into a rule-updatable view.
  *
- * Note that we currently can't support subscripted or field assignment
- * in the multi-VALUES case.  The targetlist will contain simple Vars
- * referencing the VALUES RTE, and therefore process_matched_tle() will
- * reject any such attempt with "multiple assignments to same column".
+ * Note that we may have subscripted or field assignment targetlist entries,
+ * as well as more complex expressions from already-replaced DEFAULT items if
+ * we have recursed to here for an auto-updatable view. However, it ought to
+ * be impossible for such entries to have DEFAULTs assigned to them --- we
+ * should only have to replace DEFAULT items for targetlist entries that
+ * contain simple Vars referencing the VALUES RTE.
  *
  * Returns true if all DEFAULT items were replaced, and false if some were
  * left untouched.
  */
 static bool
-rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte,
-				 Relation target_relation, List *attrnos, bool force_nulls)
+rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
+				 Relation target_relation, bool force_nulls)
 {
 	List	   *newValues;
 	ListCell   *lc;
 	bool		isAutoUpdatableView;
 	bool		allReplaced;
+	int			numattrs;
+	int		   *attrnos;
 
 	/*
 	 * Rebuilding all the lists is a pretty expensive proposition in a big
@@ -1268,8 +1258,33 @@ rewriteValuesRTE(Query *parsetree, Range
 	if (!force_nulls && !searchForDefault(rte))
 		return true;			/* nothing to do */
 
-	/* Check list lengths (we can assume all the VALUES sublists are alike) */
-	Assert(list_length(attrnos) == list_length(linitial(rte->values_lists)));
+	/*
+	 * Scan the targetlist for entries referring to the VALUES RTE, and note
+	 * the target attributes. As noted above, we should only need to do this
+	 * for targetlist entries containing simple Vars --- nothing else in the
+	 * VALUES RTE should contain DEFAULT items, and we complain if such a
+	 * thing does occur.
+	 */
+	numattrs = list_length(linitial(rte->values_lists));
+	attrnos = (int *) palloc0(numattrs * sizeof(int));
+
+	foreach(lc, parsetree->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+		if (IsA(tle->expr, Var))
+		{
+			Var		   *var = (Var *) tle->expr;
+
+			if (var->varno == rti)
+			{
+				int			attrno = var->varattno;
+
+				Assert(attrno >= 1 && attrno <= numattrs);
+				attrnos[attrno - 1] = tle->resno;
+			}
+		}
+	}
 
 	/*
 	 * Check if the target relation is an auto-updatable view, in which case
@@ -1320,18 +1335,23 @@ rewriteValuesRTE(Query *parsetree, Range
 		List	   *sublist = (List *) lfirst(lc);
 		List	   *newList = NIL;
 		ListCell   *lc2;
-		ListCell   *lc3;
+		int			i;
 
-		forboth(lc2, sublist, lc3, attrnos)
+		Assert(list_length(sublist) == numattrs);
+
+		i = 0;
+		foreach(lc2, sublist)
 		{
 			Node	   *col = (Node *) lfirst(lc2);
-			int			attrno = lfirst_int(lc3);
+			int			attrno = attrnos[i++];
 
 			if (IsA(col, SetToDefault))
 			{
 				Form_pg_attribute att_tup;
 				Node	   *new_expr;
 
+				if (attrno == 0)
+					elog(ERROR, "Cannot set value in column %d to DEFAULT", i);
 				att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
 
 				if (!force_nulls && !att_tup->attisdropped)
@@ -1379,6 +1399,8 @@ rewriteValuesRTE(Query *parsetree, Range
 	}
 	rte->values_lists = newValues;
 
+	pfree(attrnos);
+
 	return allReplaced;
 }
 
@@ -3467,7 +3489,6 @@ RewriteQuery(Query *parsetree, List *rew
 		List	   *locks;
 		List	   *product_queries;
 		bool		hasUpdate = false;
-		List	   *attrnos = NIL;
 		int			values_rte_index = 0;
 		bool		defaults_remaining = false;
 
@@ -3517,11 +3538,10 @@ RewriteQuery(Query *parsetree, List *rew
 															parsetree->commandType,
 															parsetree->override,
 															rt_entry_relation,
-															parsetree->resultRelation,
-															&attrnos);
+															parsetree->resultRelation);
 				/* ... and the VALUES expression lists */
-				if (!rewriteValuesRTE(parsetree, values_rte,
-									  rt_entry_relation, attrnos, false))
+				if (!rewriteValuesRTE(parsetree, values_rte, values_rte_index,
+									  rt_entry_relation, false))
 					defaults_remaining = true;
 			}
 			else
@@ -3532,7 +3552,7 @@ RewriteQuery(Query *parsetree, List *rew
 										parsetree->commandType,
 										parsetree->override,
 										rt_entry_relation,
-										parsetree->resultRelation, NULL);
+										parsetree->resultRelation);
 			}
 
 			if (parsetree->onConflict &&
@@ -3543,8 +3563,7 @@ RewriteQuery(Query *parsetree, List *rew
 										CMD_UPDATE,
 										parsetree->override,
 										rt_entry_relation,
-										parsetree->resultRelation,
-										NULL);
+										parsetree->resultRelation);
 			}
 		}
 		else if (event == CMD_UPDATE)
@@ -3554,7 +3573,7 @@ RewriteQuery(Query *parsetree, List *rew
 									parsetree->commandType,
 									parsetree->override,
 									rt_entry_relation,
-									parsetree->resultRelation, NULL);
+									parsetree->resultRelation);
 		}
 		else if (event == CMD_DELETE)
 		{
@@ -3599,7 +3618,8 @@ RewriteQuery(Query *parsetree, List *rew
 				RangeTblEntry *values_rte = rt_fetch(values_rte_index,
 													 pt->rtable);
 
-				rewriteValuesRTE(pt, values_rte, rt_entry_relation, attrnos,
+				rewriteValuesRTE(pt, values_rte, values_rte_index,
+								 rt_entry_relation,
 								 true); /* Force remaining defaults to NULL */
 			}
 		}
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 7ee6a54..6ce058f
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2791,6 +2791,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
  a  |       b       |       c       |      d       | e 
 ----+---------------+---------------+--------------+---
@@ -2806,7 +2807,9 @@ select * from base_tab_def order by a;
  14 | View default  | Table default | View default | 
  15 | View default  | Table default | View default | 
  16 | View default  | Table default | View default | 
-(12 rows)
+ 17 | View default  | Table default | View default | 
+    | View default  | Table default | View default | 
+(14 rows)
 
 -- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of
 -- table defaults, where there are no view defaults.
@@ -2832,6 +2835,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
  a  |       b       |       c       |      d       | e 
 ----+---------------+---------------+--------------+---
@@ -2847,7 +2851,9 @@ select * from base_tab_def order by a;
  14 | View default  |               | View default | 
  15 | View default  |               | View default | 
  16 | View default  |               | View default | 
-(12 rows)
+ 17 | View default  |               | View default | 
+    | View default  |               | View default | 
+(14 rows)
 
 -- Using an unconditional DO INSTEAD rule should also cause NULLs to be
 -- inserted where there are no view defaults.
@@ -2866,6 +2872,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
  a  |       b       |       c       |      d       | e 
 ----+---------------+---------------+--------------+---
@@ -2881,7 +2888,9 @@ select * from base_tab_def order by a;
  14 | View default  |               | View default | 
  15 | View default  |               | View default | 
  16 | View default  |               | View default | 
-(12 rows)
+ 17 | View default  |               | View default | 
+    | View default  |               | View default | 
+(14 rows)
 
 -- A DO ALSO rule should cause each row to be inserted twice. The first
 -- insert should behave the same as an auto-updatable view (using table
@@ -2902,6 +2911,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a, c NULLS LAST;
  a  |       b       |       c       |      d       | e 
 ----+---------------+---------------+--------------+---
@@ -2923,7 +2933,26 @@ select * from base_tab_def order by a, c
  15 | View default  |               | View default | 
  16 | View default  | Table default | View default | 
  16 | View default  |               | View default | 
-(18 rows)
+ 17 | View default  | Table default | View default | 
+ 17 | View default  |               | View default | 
+    | View default  | Table default | View default | 
+    | View default  |               | View default | 
+(22 rows)
 
 drop view base_tab_def_view;
 drop table base_tab_def;
+-- Test defaults with array assignments
+create table base_tab (a serial, b int[], c text, d text default 'Table default');
+create view base_tab_view as select c, a, b from base_tab;
+alter view base_tab_view alter column c set default 'View default';
+insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3])
+values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12);
+select * from base_tab order by a;
+  a  |        b         |      c       |       d       
+-----+------------------+--------------+---------------
+   1 | {1,2,3,4,5}      | View default | Table default
+ 100 | {10,11,12,13,14} | C value      | Table default
+(2 rows)
+
+drop view base_tab_view;
+drop table base_tab;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 71c4c74..f5f88a1
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1400,6 +1400,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
 
 -- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of
@@ -1426,6 +1427,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
 
 -- Using an unconditional DO INSTEAD rule should also cause NULLs to be
@@ -1445,6 +1447,7 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a;
 
 -- A DO ALSO rule should cause each row to be inserted twice. The first
@@ -1466,7 +1469,18 @@ insert into base_tab_def_view values (12
 insert into base_tab_def_view values (14, default, default, default, default);
 insert into base_tab_def_view values (15, default, default, default, default),
                                      (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
 select * from base_tab_def order by a, c NULLS LAST;
 
 drop view base_tab_def_view;
 drop table base_tab_def;
+
+-- Test defaults with array assignments
+create table base_tab (a serial, b int[], c text, d text default 'Table default');
+create view base_tab_view as select c, a, b from base_tab;
+alter view base_tab_view alter column c set default 'View default';
+insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3])
+values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12);
+select * from base_tab order by a;
+drop view base_tab_view;
+drop table base_tab;

Reply via email to