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);
* 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
*/
static bool
rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
- Relation target_relation, bool force_nulls)
+ Relation target_relation)
{
List *newValues;
ListCell *lc;
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 */
/*
/*
* 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;
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 = target_relation->rd_att->attrs[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 */
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
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
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);
}
}
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);