diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index bb2669b5df8..ac8942d9f1d 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -75,7 +75,8 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle, const char *attrName); static Node *get_assignment_input(Node *node); static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, - Relation target_relation, bool force_nulls); + Relation target_relation); +static void rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte); static void markQueryForLocking(Query *qry, Node *jtnode, LockClauseStrength strength, LockWaitPolicy waitPolicy, bool pushedDown); @@ -1262,17 +1263,6 @@ searchForDefault(RangeTblEntry *rte) * all DEFAULT items are replaced, and if the target relation doesn't have a * default, the value is explicitly set to NULL. * - * Additionally, if force_nulls is true, the target relation's defaults are - * ignored and all DEFAULT items in the VALUES list are explicitly set to - * NULL, regardless of the target relation's type. This is used for the - * product queries generated by DO ALSO rules attached to an auto-updatable - * view, for which we will have already called this function with force_nulls - * false. For these product queries, we must then force any remaining DEFAULT - * items to NULL to provide concrete values for the rule actions. - * Essentially, this is a mix of the 2 cases above --- the original query is - * an insert into an auto-updatable view, and the product queries are inserts - * into a rule-updatable view. - * * 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 @@ -1285,7 +1275,7 @@ searchForDefault(RangeTblEntry *rte) */ static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, - Relation target_relation, bool force_nulls) + Relation target_relation) { List *newValues; ListCell *lc; @@ -1294,15 +1284,16 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, int numattrs; int *attrnos; + /* Steps below are not sensible for non-INSERT queries */ + Assert(parsetree->commandType == CMD_INSERT); + Assert(rte->rtekind == RTE_VALUES); + /* * Rebuilding all the lists is a pretty expensive proposition in a big * VALUES list, and it's a waste of time if there aren't any DEFAULT * placeholders. So first scan to see if there are any. - * - * We skip this check if force_nulls is true, because we know that there - * are DEFAULT items present in that case. */ - if (!force_nulls && !searchForDefault(rte)) + if (!searchForDefault(rte)) return true; /* nothing to do */ /* @@ -1336,12 +1327,10 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, /* * Check if the target relation is an auto-updatable view, in which case * unresolved defaults will be left untouched rather than being set to - * NULL. If force_nulls is true, we always set DEFAULT items to NULL, so - * skip this check in that case --- it isn't an auto-updatable view. + * NULL. */ isAutoUpdatableView = false; - if (!force_nulls && - target_relation->rd_rel->relkind == RELKIND_VIEW && + if (target_relation->rd_rel->relkind == RELKIND_VIEW && !view_has_instead_trigger(target_relation, CMD_INSERT)) { List *locks; @@ -1399,9 +1388,10 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, if (attrno == 0) elog(ERROR, "cannot set value in column %d to DEFAULT", i); + Assert(attrno > 0 && attrno <= target_relation->rd_att->natts); att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1); - if (!force_nulls && !att_tup->attisdropped) + if (!att_tup->attisdropped) new_expr = build_column_default(target_relation, attrno); else new_expr = NULL; /* force a NULL if dropped */ @@ -1451,6 +1441,50 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, return allReplaced; } +/* + * Mop up any remaining DEFAULT items in the given VALUES RTE by + * replacing them with NULL constants. + * + * This is used for the product queries generated by DO ALSO rules attached to + * an auto-updatable view. The action can't depend on the "target relation" + * since the product query might not have one (it needn't be an INSERT). + * Essentially, such queries are treated as being attached to a rule-updatable + * view. + */ +static void +rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte) +{ + List *newValues; + ListCell *lc; + + Assert(rte->rtekind == RTE_VALUES); + newValues = NIL; + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + List *newList = NIL; + ListCell *lc2; + + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + if (IsA(col, SetToDefault)) + { + SetToDefault *def = (SetToDefault *) col; + + newList = lappend(newList, makeNullConst(def->typeId, + def->typeMod, + def->collation)); + } + else + newList = lappend(newList, col); + } + newValues = lappend(newValues, newList); + } + rte->values_lists = newValues; +} + /* * rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed @@ -3663,7 +3697,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) parsetree->resultRelation); /* ... and the VALUES expression lists */ if (!rewriteValuesRTE(parsetree, values_rte, values_rte_index, - rt_entry_relation, false)) + rt_entry_relation)) defaults_remaining = true; } else @@ -3743,9 +3777,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) RangeTblEntry *values_rte = rt_fetch(values_rte_index, pt->rtable); - rewriteValuesRTE(pt, values_rte, values_rte_index, - rt_entry_relation, - true); /* Force remaining defaults to NULL */ + rewriteValuesRTEToNulls(pt, values_rte); } } diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index c3a68ee13bc..06f1e64d421 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -433,8 +433,30 @@ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5; Index Cond: ((a > 0) AND (a = 5)) (3 rows) +-- it's still updatable if we add a DO ALSO rule +CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text); +CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO + INSERT INTO base_tbl_hist(a,b) VALUES(new.a, new.b); +SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name = 'rw_view1'; + table_name | is_updatable | is_insertable_into +------------+--------------+-------------------- + rw_view1 | YES | YES +(1 row) + +-- Check behavior with DEFAULTs (bug #17633) +INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT); +SELECT a, b FROM base_tbl_hist; + a | b +----+--- + 9 | + 10 | +(2 rows) + DROP TABLE base_tbl CASCADE; NOTICE: drop cascades to view rw_view1 +DROP TABLE base_tbl_hist; -- view on top of view 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); diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index 09328e582b2..cc441b9c927 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -148,7 +148,24 @@ SELECT * FROM base_tbl; EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5; EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5; +-- it's still updatable if we add a DO ALSO rule + +CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text); + +CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO + INSERT INTO base_tbl_hist(a,b) VALUES(new.a, new.b); + +SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name = 'rw_view1'; + +-- Check behavior with DEFAULTs (bug #17633) + +INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT); +SELECT a, b FROM base_tbl_hist; + DROP TABLE base_tbl CASCADE; +DROP TABLE base_tbl_hist; -- view on top of view