aboutsummaryrefslogtreecommitdiff
path: root/src/backend/rewrite/rewriteHandler.c
diff options
context:
space:
mode:
authorDean Rasheed <dean.a.rasheed@gmail.com>2019-02-20 08:30:21 +0000
committerDean Rasheed <dean.a.rasheed@gmail.com>2019-02-20 08:30:21 +0000
commit41531e42d34f4aca117d343b5e40f3f757dec5fe (patch)
treeb605e6aaccf9a0d404b7ea8b823425f95ef8f6cd /src/backend/rewrite/rewriteHandler.c
parent56fadbedbd2f697400b89e7b767cfa4ec67932d6 (diff)
downloadpostgresql-41531e42d34f4aca117d343b5e40f3f757dec5fe.tar.gz
postgresql-41531e42d34f4aca117d343b5e40f3f757dec5fe.zip
Fix DEFAULT-handling in multi-row VALUES lists for updatable views.
INSERT ... VALUES for a single VALUES row is implemented differently from a multi-row VALUES list, which causes inconsistent behaviour in the way that DEFAULT items are handled. In particular, when inserting into an auto-updatable view on top of a table with a column default, a DEFAULT item in a single VALUES row gets correctly replaced with the table column's default, but for a multi-row VALUES list it is replaced with NULL. Fix this by allowing rewriteValuesRTE() to leave DEFAULT items in the VALUES list untouched if the target relation is an auto-updatable view and has no column default, deferring DEFAULT-expansion until the query against the base relation is rewritten. For all other types of target relation, including tables and trigger- and rule-updatable views, we must continue to replace DEFAULT items with NULL in the absence of a column default. This is somewhat complicated by the fact that if an auto-updatable view has DO ALSO rules attached, the VALUES lists for the product queries need to be handled differently from the original query, since the product queries need to act like rule-updatable views whereas the original query has auto-updatable view semantics. Back-patch to all supported versions. Reported by Roger Curley (bug #15623). Patch by Amit Langote and me. Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r--src/backend/rewrite/rewriteHandler.c139
1 files changed, 128 insertions, 11 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 0338e4e1ad0..7eb41ff0265 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -73,8 +73,8 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle,
TargetEntry *prior_tle,
const char *attrName);
static Node *get_assignment_input(Node *node);
-static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
- List *attrnos);
+static bool rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte,
+ Relation target_relation, List *attrnos, bool force_nulls);
static void markQueryForLocking(Query *qry, Node *jtnode,
LockClauseStrength strength, LockWaitPolicy waitPolicy,
bool pushedDown);
@@ -1219,29 +1219,102 @@ searchForDefault(RangeTblEntry *rte)
* the appropriate default expressions. The other aspects of targetlist
* rewriting need be applied only to the query's targetlist proper.
*
+ * For an auto-updatable view, each DEFAULT item in the VALUES list is
+ * replaced with the default from the view, if it has one. Otherwise it is
+ * left untouched so that the underlying base relation's default can be
+ * applied instead (when we later recurse to here after rewriting the query
+ * to refer to the base relation instead of the view).
+ *
+ * For other types of relation, including rule- and trigger-updatable views,
+ * 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 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".
+ *
+ * Returns true if all DEFAULT items were replaced, and false if some were
+ * left untouched.
*/
-static void
-rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
+static bool
+rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte,
+ Relation target_relation, List *attrnos, bool force_nulls)
{
List *newValues;
ListCell *lc;
+ bool isAutoUpdatableView;
+ bool allReplaced;
/*
* 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 (!searchForDefault(rte))
- return; /* nothing to do */
+ 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)));
+ /*
+ * 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.
+ */
+ isAutoUpdatableView = false;
+ if (!force_nulls &&
+ target_relation->rd_rel->relkind == RELKIND_VIEW &&
+ !view_has_instead_trigger(target_relation, CMD_INSERT))
+ {
+ List *locks;
+ bool hasUpdate;
+ bool found;
+ ListCell *l;
+
+ /* Look for an unconditional DO INSTEAD rule */
+ locks = matchLocks(CMD_INSERT, target_relation->rd_rules,
+ parsetree->resultRelation, parsetree, &hasUpdate);
+
+ found = false;
+ foreach(l, locks)
+ {
+ RewriteRule *rule_lock = (RewriteRule *) lfirst(l);
+
+ if (rule_lock->isInstead &&
+ rule_lock->qual == NULL)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find an unconditional DO INSTEAD rule, assume that the
+ * view is auto-updatable. If it isn't, rewriteTargetView() will
+ * throw an error.
+ */
+ if (!found)
+ isAutoUpdatableView = true;
+ }
+
newValues = NIL;
+ allReplaced = true;
foreach(lc, rte->values_lists)
{
List *sublist = (List *) lfirst(lc);
@@ -1261,17 +1334,26 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
- if (!att_tup->attisdropped)
+ if (!force_nulls && !att_tup->attisdropped)
new_expr = build_column_default(target_relation, attrno);
else
new_expr = NULL; /* force a NULL if dropped */
/*
* If there is no default (ie, default is effectively NULL),
- * we've got to explicitly set the column to NULL.
+ * we've got to explicitly set the column to NULL, unless the
+ * target relation is an auto-updatable view.
*/
if (!new_expr)
{
+ if (isAutoUpdatableView)
+ {
+ /* Leave the value untouched */
+ newList = lappend(newList, col);
+ allReplaced = false;
+ continue;
+ }
+
new_expr = (Node *) makeConst(att_tup->atttypid,
-1,
att_tup->attcollation,
@@ -1296,6 +1378,8 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
newValues = lappend(newValues, newList);
}
rte->values_lists = newValues;
+
+ return allReplaced;
}
@@ -3383,6 +3467,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
List *locks;
List *product_queries;
bool hasUpdate = false;
+ List *attrnos = NIL;
+ int values_rte_index = 0;
+ bool defaults_remaining = false;
result_relation = parsetree->resultRelation;
Assert(result_relation != 0);
@@ -3416,14 +3503,15 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
parsetree->rtable);
if (rte->rtekind == RTE_VALUES)
+ {
values_rte = rte;
+ values_rte_index = rtr->rtindex;
+ }
}
}
if (values_rte)
{
- List *attrnos;
-
/* Process the main targetlist ... */
parsetree->targetList = rewriteTargetListIU(parsetree->targetList,
parsetree->commandType,
@@ -3432,7 +3520,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
parsetree->resultRelation,
&attrnos);
/* ... and the VALUES expression lists */
- rewriteValuesRTE(values_rte, rt_entry_relation, attrnos);
+ if (!rewriteValuesRTE(parsetree, values_rte,
+ rt_entry_relation, attrnos, false))
+ defaults_remaining = true;
}
else
{
@@ -3488,6 +3578,33 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
&qual_product);
/*
+ * If we have a VALUES RTE with any remaining untouched DEFAULT items,
+ * and we got any product queries, finalize the VALUES RTE for each
+ * product query (replacing the remaining DEFAULT items with NULLs).
+ * We don't do this for the original query, because we know that it
+ * must be an auto-insert on a view, and so should use the base
+ * relation's defaults for any remaining DEFAULT items.
+ */
+ if (defaults_remaining && product_queries != NIL)
+ {
+ ListCell *n;
+
+ /*
+ * Each product query has its own copy of the VALUES RTE at the
+ * same index in the rangetable, so we must finalize each one.
+ */
+ foreach(n, product_queries)
+ {
+ Query *pt = (Query *) lfirst(n);
+ RangeTblEntry *values_rte = rt_fetch(values_rte_index,
+ pt->rtable);
+
+ rewriteValuesRTE(pt, values_rte, rt_entry_relation, attrnos,
+ true); /* Force remaining defaults to NULL */
+ }
+ }
+
+ /*
* If there were no INSTEAD rules, and the target relation is a view
* without any INSTEAD OF triggers, see if the view can be
* automatically updated. If so, we perform the necessary query