Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 23e2a06

Browse files
committed
Yet further fixes for multi-row VALUES lists for updatable views.
DEFAULT markers appearing in an INSERT on an updatable view could be mis-processed if they were in a multi-row VALUES clause. This would lead to strange errors such as "cache lookup failed for type NNNN", or in older branches even to crashes. The cause is that commit 41531e4 tried to re-use rewriteValuesRTE() to remove any SetToDefault nodes (that hadn't previously been replaced by the view's own default values) appearing in "product" queries, that is DO ALSO queries. That's fundamentally wrong because the DO ALSO queries might not even be INSERTs; and even if they are, their targetlists don't necessarily match the view's column list, so that almost all the logic in rewriteValuesRTE() is inapplicable. What we want is a narrow focus on replacing any such nodes with NULL constants. (That is, in this context we are interpreting the defaults as being strictly those of the view itself; and we already replaced any that aren't NULL.) We could add still more !force_nulls tests to further lobotomize rewriteValuesRTE(); but it seems cleaner to split out this case to a new function, restoring rewriteValuesRTE() to the charter it had before. Per bug #17633 from jiye_sw. Patch by me, but thanks to Richard Guo and Japin Li for initial investigation. Back-patch to all supported branches, as the previous fix was. Discussion: https://postgr.es/m/[email protected]
1 parent 8bf4705 commit 23e2a06

File tree

3 files changed

+97
-26
lines changed

3 files changed

+97
-26
lines changed

src/backend/rewrite/rewriteHandler.c

+58-26
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle,
7272
const char *attrName);
7373
static Node *get_assignment_input(Node *node);
7474
static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
75-
Relation target_relation, bool force_nulls);
75+
Relation target_relation);
76+
static void rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte);
7677
static void markQueryForLocking(Query *qry, Node *jtnode,
7778
LockClauseStrength strength, LockWaitPolicy waitPolicy,
7879
bool pushedDown);
@@ -1233,17 +1234,6 @@ searchForDefault(RangeTblEntry *rte)
12331234
* all DEFAULT items are replaced, and if the target relation doesn't have a
12341235
* default, the value is explicitly set to NULL.
12351236
*
1236-
* Additionally, if force_nulls is true, the target relation's defaults are
1237-
* ignored and all DEFAULT items in the VALUES list are explicitly set to
1238-
* NULL, regardless of the target relation's type. This is used for the
1239-
* product queries generated by DO ALSO rules attached to an auto-updatable
1240-
* view, for which we will have already called this function with force_nulls
1241-
* false. For these product queries, we must then force any remaining DEFAULT
1242-
* items to NULL to provide concrete values for the rule actions.
1243-
* Essentially, this is a mix of the 2 cases above --- the original query is
1244-
* an insert into an auto-updatable view, and the product queries are inserts
1245-
* into a rule-updatable view.
1246-
*
12471237
* Note that we may have subscripted or field assignment targetlist entries,
12481238
* as well as more complex expressions from already-replaced DEFAULT items if
12491239
* we have recursed to here for an auto-updatable view. However, it ought to
@@ -1256,7 +1246,7 @@ searchForDefault(RangeTblEntry *rte)
12561246
*/
12571247
static bool
12581248
rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
1259-
Relation target_relation, bool force_nulls)
1249+
Relation target_relation)
12601250
{
12611251
List *newValues;
12621252
ListCell *lc;
@@ -1265,15 +1255,16 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
12651255
int numattrs;
12661256
int *attrnos;
12671257

1258+
/* Steps below are not sensible for non-INSERT queries */
1259+
Assert(parsetree->commandType == CMD_INSERT);
1260+
Assert(rte->rtekind == RTE_VALUES);
1261+
12681262
/*
12691263
* Rebuilding all the lists is a pretty expensive proposition in a big
12701264
* VALUES list, and it's a waste of time if there aren't any DEFAULT
12711265
* placeholders. So first scan to see if there are any.
1272-
*
1273-
* We skip this check if force_nulls is true, because we know that there
1274-
* are DEFAULT items present in that case.
12751266
*/
1276-
if (!force_nulls && !searchForDefault(rte))
1267+
if (!searchForDefault(rte))
12771268
return true; /* nothing to do */
12781269

12791270
/*
@@ -1307,12 +1298,10 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
13071298
/*
13081299
* Check if the target relation is an auto-updatable view, in which case
13091300
* unresolved defaults will be left untouched rather than being set to
1310-
* NULL. If force_nulls is true, we always set DEFAULT items to NULL, so
1311-
* skip this check in that case --- it isn't an auto-updatable view.
1301+
* NULL.
13121302
*/
13131303
isAutoUpdatableView = false;
1314-
if (!force_nulls &&
1315-
target_relation->rd_rel->relkind == RELKIND_VIEW &&
1304+
if (target_relation->rd_rel->relkind == RELKIND_VIEW &&
13161305
!view_has_instead_trigger(target_relation, CMD_INSERT))
13171306
{
13181307
List *locks;
@@ -1370,9 +1359,10 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
13701359

13711360
if (attrno == 0)
13721361
elog(ERROR, "cannot set value in column %d to DEFAULT", i);
1362+
Assert(attrno > 0 && attrno <= target_relation->rd_att->natts);
13731363
att_tup = target_relation->rd_att->attrs[attrno - 1];
13741364

1375-
if (!force_nulls && !att_tup->attisdropped)
1365+
if (!att_tup->attisdropped)
13761366
new_expr = build_column_default(target_relation, attrno);
13771367
else
13781368
new_expr = NULL; /* force a NULL if dropped */
@@ -1422,6 +1412,50 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
14221412
return allReplaced;
14231413
}
14241414

1415+
/*
1416+
* Mop up any remaining DEFAULT items in the given VALUES RTE by
1417+
* replacing them with NULL constants.
1418+
*
1419+
* This is used for the product queries generated by DO ALSO rules attached to
1420+
* an auto-updatable view. The action can't depend on the "target relation"
1421+
* since the product query might not have one (it needn't be an INSERT).
1422+
* Essentially, such queries are treated as being attached to a rule-updatable
1423+
* view.
1424+
*/
1425+
static void
1426+
rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
1427+
{
1428+
List *newValues;
1429+
ListCell *lc;
1430+
1431+
Assert(rte->rtekind == RTE_VALUES);
1432+
newValues = NIL;
1433+
foreach(lc, rte->values_lists)
1434+
{
1435+
List *sublist = (List *) lfirst(lc);
1436+
List *newList = NIL;
1437+
ListCell *lc2;
1438+
1439+
foreach(lc2, sublist)
1440+
{
1441+
Node *col = (Node *) lfirst(lc2);
1442+
1443+
if (IsA(col, SetToDefault))
1444+
{
1445+
SetToDefault *def = (SetToDefault *) col;
1446+
1447+
newList = lappend(newList, makeNullConst(def->typeId,
1448+
def->typeMod,
1449+
def->collation));
1450+
}
1451+
else
1452+
newList = lappend(newList, col);
1453+
}
1454+
newValues = lappend(newValues, newList);
1455+
}
1456+
rte->values_lists = newValues;
1457+
}
1458+
14251459

14261460
/*
14271461
* rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed
@@ -3578,7 +3612,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
35783612
parsetree->resultRelation);
35793613
/* ... and the VALUES expression lists */
35803614
if (!rewriteValuesRTE(parsetree, values_rte, values_rte_index,
3581-
rt_entry_relation, false))
3615+
rt_entry_relation))
35823616
defaults_remaining = true;
35833617
}
35843618
else
@@ -3655,9 +3689,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
36553689
RangeTblEntry *values_rte = rt_fetch(values_rte_index,
36563690
pt->rtable);
36573691

3658-
rewriteValuesRTE(pt, values_rte, values_rte_index,
3659-
rt_entry_relation,
3660-
true); /* Force remaining defaults to NULL */
3692+
rewriteValuesRTEToNulls(pt, values_rte);
36613693
}
36623694
}
36633695

src/test/regress/expected/updatable_views.out

+22
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,30 @@ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
431431
Index Cond: ((a > 0) AND (a = 5))
432432
(3 rows)
433433

434+
-- it's still updatable if we add a DO ALSO rule
435+
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
436+
CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO
437+
INSERT INTO base_tbl_hist(a,b) VALUES(new.a, new.b);
438+
SELECT table_name, is_updatable, is_insertable_into
439+
FROM information_schema.views
440+
WHERE table_name = 'rw_view1';
441+
table_name | is_updatable | is_insertable_into
442+
------------+--------------+--------------------
443+
rw_view1 | YES | YES
444+
(1 row)
445+
446+
-- Check behavior with DEFAULTs (bug #17633)
447+
INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT);
448+
SELECT a, b FROM base_tbl_hist;
449+
a | b
450+
----+---
451+
9 |
452+
10 |
453+
(2 rows)
454+
434455
DROP TABLE base_tbl CASCADE;
435456
NOTICE: drop cascades to view rw_view1
457+
DROP TABLE base_tbl_hist;
436458
-- view on top of view
437459
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
438460
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);

src/test/regress/sql/updatable_views.sql

+17
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,24 @@ SELECT * FROM base_tbl;
145145
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
146146
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
147147

148+
-- it's still updatable if we add a DO ALSO rule
149+
150+
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
151+
152+
CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO
153+
INSERT INTO base_tbl_hist(a,b) VALUES(new.a, new.b);
154+
155+
SELECT table_name, is_updatable, is_insertable_into
156+
FROM information_schema.views
157+
WHERE table_name = 'rw_view1';
158+
159+
-- Check behavior with DEFAULTs (bug #17633)
160+
161+
INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT);
162+
SELECT a, b FROM base_tbl_hist;
163+
148164
DROP TABLE base_tbl CASCADE;
165+
DROP TABLE base_tbl_hist;
149166

150167
-- view on top of view
151168

0 commit comments

Comments
 (0)