Skip to content

Commit f0423be

Browse files
committed
Fix multi-row DEFAULT handling for INSERT ... SELECT rules.
Given an updatable view with a DO ALSO INSERT ... SELECT rule, a multi-row INSERT ... VALUES query on the view fails if the VALUES list contains any DEFAULTs that are not replaced by view defaults. This manifests as an "unrecognized node type" error, or an Assert failure, in an assert-enabled build. The reason is that when RewriteQuery() attempts to replace the remaining DEFAULT items with NULLs in any product queries, using rewriteValuesRTEToNulls(), it assumes that the VALUES RTE is located at the same rangetable index in each product query. However, if the product query is an INSERT ... SELECT, then the VALUES RTE is actually in the SELECT part of that query (at the same index), rather than the top-level product query itself. Fix, by descending to the SELECT in such cases. Note that we can't simply use getInsertSelectQuery() for this, since that expects to be given a raw rule action with OLD and NEW placeholder entries, so we duplicate its logic instead. While at it, beef up the checks in getInsertSelectQuery() by checking that the jointree->fromlist node is indeed a RangeTblRef, and that the RTE it points to has rtekind == RTE_SUBQUERY. Per bug #17803, from Alexander Lakhin. Back-patch to all supported branches. Dean Rasheed, reviewed by Tom Lane. Discussion: https://postgr.es/m/17803-53c63ed4ecb4eac6%40postgresql.org
1 parent 8b9cbd4 commit f0423be

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed

src/backend/rewrite/rewriteHandler.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,7 +1597,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
15971597
List *newValues;
15981598
ListCell *lc;
15991599

1600-
Assert(rte->rtekind == RTE_VALUES);
16011600
newValues = NIL;
16021601
foreach(lc, rte->values_lists)
16031602
{
@@ -3821,12 +3820,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
38213820
/*
38223821
* Each product query has its own copy of the VALUES RTE at the
38233822
* same index in the rangetable, so we must finalize each one.
3823+
*
3824+
* Note that if the product query is an INSERT ... SELECT, then
3825+
* the VALUES RTE will be at the same index in the SELECT part of
3826+
* the product query rather than the top-level product query
3827+
* itself.
38243828
*/
38253829
foreach(n, product_queries)
38263830
{
38273831
Query *pt = (Query *) lfirst(n);
3828-
RangeTblEntry *values_rte = rt_fetch(values_rte_index,
3829-
pt->rtable);
3832+
RangeTblEntry *values_rte;
3833+
3834+
if (pt->commandType == CMD_INSERT &&
3835+
pt->jointree && IsA(pt->jointree, FromExpr) &&
3836+
list_length(pt->jointree->fromlist) == 1)
3837+
{
3838+
Node *jtnode = (Node *) linitial(pt->jointree->fromlist);
3839+
3840+
if (IsA(jtnode, RangeTblRef))
3841+
{
3842+
int rtindex = ((RangeTblRef *) jtnode)->rtindex;
3843+
RangeTblEntry *src_rte = rt_fetch(rtindex, pt->rtable);
3844+
3845+
if (src_rte->rtekind == RTE_SUBQUERY &&
3846+
src_rte->subquery &&
3847+
IsA(src_rte->subquery, Query) &&
3848+
src_rte->subquery->commandType == CMD_SELECT)
3849+
pt = src_rte->subquery;
3850+
}
3851+
}
3852+
3853+
values_rte = rt_fetch(values_rte_index, pt->rtable);
3854+
if (values_rte->rtekind != RTE_VALUES)
3855+
elog(ERROR, "failed to find VALUES RTE in product query");
38303856

38313857
rewriteValuesRTEToNulls(pt, values_rte);
38323858
}

src/backend/rewrite/rewriteManip.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -951,12 +951,15 @@ getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
951951
if (list_length(parsetree->jointree->fromlist) != 1)
952952
elog(ERROR, "expected to find SELECT subquery");
953953
rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist);
954-
Assert(IsA(rtr, RangeTblRef));
954+
if (!IsA(rtr, RangeTblRef))
955+
elog(ERROR, "expected to find SELECT subquery");
955956
selectrte = rt_fetch(rtr->rtindex, parsetree->rtable);
956-
selectquery = selectrte->subquery;
957-
if (!(selectquery && IsA(selectquery, Query) &&
958-
selectquery->commandType == CMD_SELECT))
957+
if (!(selectrte->rtekind == RTE_SUBQUERY &&
958+
selectrte->subquery &&
959+
IsA(selectrte->subquery, Query) &&
960+
selectrte->subquery->commandType == CMD_SELECT))
959961
elog(ERROR, "expected to find SELECT subquery");
962+
selectquery = selectrte->subquery;
960963
if (list_length(selectquery->rtable) >= 2 &&
961964
strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->aliasname,
962965
"old") == 0 &&

src/test/regress/expected/updatable_views.out

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,6 +3059,25 @@ select * from base_tab_def order by a, c NULLS LAST;
30593059
| View default | | View default |
30603060
(22 rows)
30613061

3062+
-- Test a DO ALSO INSERT ... SELECT rule
3063+
drop rule base_tab_def_view_ins_rule on base_tab_def_view;
3064+
create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
3065+
do also insert into base_tab_def (a, b, e) select new.a, new.b, 'xxx';
3066+
truncate base_tab_def;
3067+
insert into base_tab_def_view values (1, default, default, default, default);
3068+
insert into base_tab_def_view values (2, default, default, default, default),
3069+
(3, default, default, default, default);
3070+
select * from base_tab_def order by a, e nulls first;
3071+
a | b | c | d | e
3072+
---+--------------+---------------+--------------+-----
3073+
1 | View default | Table default | View default |
3074+
1 | View default | Table default | | xxx
3075+
2 | View default | Table default | View default |
3076+
2 | View default | Table default | | xxx
3077+
3 | View default | Table default | View default |
3078+
3 | View default | Table default | | xxx
3079+
(6 rows)
3080+
30623081
drop view base_tab_def_view;
30633082
drop table base_tab_def;
30643083
-- Test defaults with array assignments

src/test/regress/sql/updatable_views.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,16 @@ insert into base_tab_def_view values (15, default, default, default, default),
15371537
insert into base_tab_def_view values (17), (default);
15381538
select * from base_tab_def order by a, c NULLS LAST;
15391539

1540+
-- Test a DO ALSO INSERT ... SELECT rule
1541+
drop rule base_tab_def_view_ins_rule on base_tab_def_view;
1542+
create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
1543+
do also insert into base_tab_def (a, b, e) select new.a, new.b, 'xxx';
1544+
truncate base_tab_def;
1545+
insert into base_tab_def_view values (1, default, default, default, default);
1546+
insert into base_tab_def_view values (2, default, default, default, default),
1547+
(3, default, default, default, default);
1548+
select * from base_tab_def order by a, e nulls first;
1549+
15401550
drop view base_tab_def_view;
15411551
drop table base_tab_def;
15421552

0 commit comments

Comments
 (0)