aboutsummaryrefslogtreecommitdiff
path: root/src/backend/rewrite/rewriteHandler.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2018-08-04 19:38:58 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2018-08-04 19:38:58 -0400
commitb8a1247a34e234be6becf7f70b9f1e8e9369db64 (patch)
treeb6ebdbb4aad6f80c38f69ba869a56cf1d4fd19ff /src/backend/rewrite/rewriteHandler.c
parent5a23c74b63ec9f63c648f79b13a900c37332ee55 (diff)
downloadpostgresql-b8a1247a34e234be6becf7f70b9f1e8e9369db64.tar.gz
postgresql-b8a1247a34e234be6becf7f70b9f1e8e9369db64.zip
Fix INSERT ON CONFLICT UPDATE through a view that isn't just SELECT *.
When expanding an updatable view that is an INSERT's target, the rewriter failed to rewrite Vars in the ON CONFLICT UPDATE clause. This accidentally worked if the view was just "SELECT * FROM ...", as the transformation would be a no-op in that case. With more complicated view targetlists, this omission would often lead to "attribute ... has the wrong type" errors or even crashes, as reported by Mario De Frutos Dieguez. Fix by adding code to rewriteTargetView to fix up the data structure correctly. The easiest way to update the exclRelTlist list is to rebuild it from scratch looking at the new target relation, so factor the code for that out of transformOnConflictClause to make it sharable. In passing, avoid duplicate permissions checks against the EXCLUDED pseudo-relation, and prevent useless view expansion of that relation's dummy RTE. The latter is only known to happen (after this patch) in cases where the query would fail later due to not having any INSTEAD OF triggers for the view. But by exactly that token, it would create an unintended and very poorly tested state of the query data structure, so it seems like a good idea to prevent it from happening at all. This has been broken since ON CONFLICT was introduced, so back-patch to 9.5. Dean Rasheed, based on an earlier patch by Amit Langote; comment-kibitzing and back-patching by me Discussion: https://postgr.es/m/CAFYwGJ0xfzy8jaK80hVN2eUWr6huce0RU8AgU04MGD00igqkTg@mail.gmail.com
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r--src/backend/rewrite/rewriteHandler.c103
1 files changed, 101 insertions, 2 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 5b87c554f50..3123ee274db 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -29,6 +29,7 @@
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
@@ -1771,6 +1772,17 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
continue;
/*
+ * In INSERT ... ON CONFLICT, ignore the EXCLUDED pseudo-relation;
+ * even if it points to a view, we needn't expand it, and should not
+ * because we want the RTE to remain of RTE_RELATION type. Otherwise,
+ * it would get changed to RTE_SUBQUERY type, which is an
+ * untested/unsupported situation.
+ */
+ if (parsetree->onConflict &&
+ rt_index == parsetree->onConflict->exclRelIndex)
+ continue;
+
+ /*
* If the table is not referenced in the query, then we ignore it.
* This prevents infinite expansion loop due to new rtable entries
* inserted by expansion of a rule. A table is referenced if it is
@@ -2875,8 +2887,6 @@ rewriteTargetView(Query *parsetree, Relation view)
*/
base_rte->relkind = base_rel->rd_rel->relkind;
- heap_close(base_rel, NoLock);
-
/*
* If the view query contains any sublink subqueries then we need to also
* acquire locks on any relations they refer to. We know that there won't
@@ -3035,6 +3045,93 @@ rewriteTargetView(Query *parsetree, Relation view)
}
/*
+ * For INSERT .. ON CONFLICT .. DO UPDATE, we must also update assorted
+ * stuff in the onConflict data structure.
+ */
+ if (parsetree->onConflict &&
+ parsetree->onConflict->action == ONCONFLICT_UPDATE)
+ {
+ Index old_exclRelIndex,
+ new_exclRelIndex;
+ RangeTblEntry *new_exclRte;
+ List *tmp_tlist;
+
+ /*
+ * Like the INSERT/UPDATE code above, update the resnos in the
+ * auxiliary UPDATE targetlist to refer to columns of the base
+ * relation.
+ */
+ foreach(lc, parsetree->onConflict->onConflictSet)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ TargetEntry *view_tle;
+
+ if (tle->resjunk)
+ continue;
+
+ view_tle = get_tle_by_resno(view_targetlist, tle->resno);
+ if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var))
+ tle->resno = ((Var *) view_tle->expr)->varattno;
+ else
+ elog(ERROR, "attribute number %d not found in view targetlist",
+ tle->resno);
+ }
+
+ /*
+ * Also, create a new RTE for the EXCLUDED pseudo-relation, using the
+ * query's new base rel (which may well have a different column list
+ * from the view, hence we need a new column alias list). This should
+ * match transformOnConflictClause. In particular, note that the
+ * relkind is set to composite to signal that we're not dealing with
+ * an actual relation, and no permissions checks are wanted.
+ */
+ old_exclRelIndex = parsetree->onConflict->exclRelIndex;
+
+ new_exclRte = addRangeTableEntryForRelation(make_parsestate(NULL),
+ base_rel,
+ makeAlias("excluded",
+ NIL),
+ false, false);
+ new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
+ new_exclRte->requiredPerms = 0;
+ /* other permissions fields in new_exclRte are already empty */
+
+ parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
+ new_exclRelIndex = parsetree->onConflict->exclRelIndex =
+ list_length(parsetree->rtable);
+
+ /*
+ * Replace the targetlist for the EXCLUDED pseudo-relation with a new
+ * one, representing the columns from the new base relation.
+ */
+ parsetree->onConflict->exclRelTlist =
+ BuildOnConflictExcludedTargetlist(base_rel, new_exclRelIndex);
+
+ /*
+ * Update all Vars in the ON CONFLICT clause that refer to the old
+ * EXCLUDED pseudo-relation. We want to use the column mappings
+ * defined in the view targetlist, but we need the outputs to refer to
+ * the new EXCLUDED pseudo-relation rather than the new target RTE.
+ * Also notice that "EXCLUDED.*" will be expanded using the view's
+ * rowtype, which seems correct.
+ */
+ tmp_tlist = copyObject(view_targetlist);
+
+ ChangeVarNodes((Node *) tmp_tlist, new_rt_index,
+ new_exclRelIndex, 0);
+
+ parsetree->onConflict = (OnConflictExpr *)
+ ReplaceVarsFromTargetList((Node *) parsetree->onConflict,
+ old_exclRelIndex,
+ 0,
+ view_rte,
+ tmp_tlist,
+ REPLACEVARS_REPORT_ERROR,
+ 0,
+ &parsetree->hasSubLinks);
+ }
+
+ /*
* For UPDATE/DELETE, pull up any WHERE quals from the view. We know that
* any Vars in the quals must reference the one base relation, so we need
* only adjust their varnos to reference the new target (just the same as
@@ -3161,6 +3258,8 @@ rewriteTargetView(Query *parsetree, Relation view)
}
}
+ heap_close(base_rel, NoLock);
+
return parsetree;
}