support-merge-into-view-v6.patch
text/x-patch
Filename: support-merge-into-view-v6.patch
Type: text/x-patch
Part: 0
Patch
Format: unified
Series: patch v6
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/create_view.sgml | 20 | 11 |
| doc/src/sgml/ref/merge.sgml | 18 | 4 |
| doc/src/sgml/rules.sgml | 21 | 19 |
| src/backend/commands/copyfrom.c | 1 | 1 |
| src/backend/executor/execMain.c | 53 | 5 |
| src/backend/executor/execPartition.c | 2 | 2 |
| src/backend/executor/nodeModifyTable.c | 103 | 27 |
| src/backend/optimizer/prep/prepjointree.c | 18 | 1 |
| src/backend/optimizer/prep/preptlist.c | 4 | 5 |
| src/backend/optimizer/util/appendinfo.c | 1 | 2 |
| src/backend/parser/parse_merge.c | 11 | 10 |
| src/backend/rewrite/rewriteHandler.c | 296 | 107 |
| src/backend/rewrite/rewriteManip.c | 14 | 6 |
| src/bin/psql/tab-complete.c | 1 | 0 |
| src/include/executor/executor.h | 2 | 1 |
| src/include/nodes/parsenodes.h | 2 | 0 |
| src/test/regress/expected/merge.out | 0 | 10 |
| src/test/regress/expected/updatable_views.out | 492 | 16 |
| src/test/regress/sql/merge.sql | 0 | 9 |
| src/test/regress/sql/updatable_views.sql | 268 | 1 |
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index 3b26205..5037419
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -192,12 +192,14 @@ CREATE VIEW [ <replaceable>schema</repla
<listitem>
<para>
This option controls the behavior of automatically updatable views. When
- this option is specified, <command>INSERT</command> and <command>UPDATE</command>
+ this option is specified, <command>INSERT</command>,
+ <command>UPDATE</command>, and <command>MERGE</command>
commands on the view will be checked to ensure that new rows satisfy the
view-defining condition (that is, the new rows are checked to ensure that
they are visible through the view). If they are not, the update will be
rejected. If the <literal>CHECK OPTION</literal> is not specified,
- <command>INSERT</command> and <command>UPDATE</command> commands on the view are
+ <command>INSERT</command>, <command>UPDATE</command>, and
+ <command>MERGE</command> commands on the view are
allowed to create rows that are not visible through the view. The
following check options are supported:
@@ -360,7 +362,8 @@ CREATE VIEW vista AS SELECT text 'Hello
<para>
Simple views are automatically updatable: the system will allow
- <command>INSERT</command>, <command>UPDATE</command> and <command>DELETE</command> statements
+ <command>INSERT</command>, <command>UPDATE</command>,
+ <command>DELETE</command>, and <command>MERGE</command> statements
to be used on the view in the same way as on a regular table. A view is
automatically updatable if it satisfies all of the following conditions:
@@ -400,13 +403,15 @@ CREATE VIEW vista AS SELECT text 'Hello
An automatically updatable view may contain a mix of updatable and
non-updatable columns. A column is updatable if it is a simple reference
to an updatable column of the underlying base relation; otherwise the
- column is read-only, and an error will be raised if an <command>INSERT</command>
- or <command>UPDATE</command> statement attempts to assign a value to it.
+ column is read-only, and an error will be raised if an
+ <command>INSERT</command>, <command>UPDATE</command>, or
+ <command>MERGE</command> statement attempts to assign a value to it.
</para>
<para>
If the view is automatically updatable the system will convert any
- <command>INSERT</command>, <command>UPDATE</command> or <command>DELETE</command> statement
+ <command>INSERT</command>, <command>UPDATE</command>,
+ <command>DELETE</command>, or <command>MERGE</command> statement
on the view into the corresponding statement on the underlying base
relation. <command>INSERT</command> statements that have an <literal>ON
CONFLICT UPDATE</literal> clause are fully supported.
@@ -415,17 +420,21 @@ CREATE VIEW vista AS SELECT text 'Hello
<para>
If an automatically updatable view contains a <literal>WHERE</literal>
condition, the condition restricts which rows of the base relation are
- available to be modified by <command>UPDATE</command> and <command>DELETE</command>
- statements on the view. However, an <command>UPDATE</command> is allowed to
+ available to be modified by <command>UPDATE</command>,
+ <command>DELETE</command>, and <command>MERGE</command>
+ statements on the view. However, an <command>UPDATE</command> or
+ <command>MERGE</command> is allowed to
change a row so that it no longer satisfies the <literal>WHERE</literal>
condition, and thus is no longer visible through the view. Similarly,
- an <command>INSERT</command> command can potentially insert base-relation rows
+ an <command>INSERT</command> or <command>MERGE</command> command can
+ potentially insert base-relation rows
that do not satisfy the <literal>WHERE</literal> condition and thus are not
visible through the view (<literal>ON CONFLICT UPDATE</literal> may
similarly affect an existing row not visible through the view).
The <literal>CHECK OPTION</literal> may be used to prevent
- <command>INSERT</command> and <command>UPDATE</command> commands from creating
- such rows that are not visible through the view.
+ <command>INSERT</command>, <command>UPDATE</command>, and
+ <command>MERGE</command> commands from creating such rows that are not
+ visible through the view.
</para>
<para>
diff --git a/doc/src/sgml/ref/merge.sgml b/doc/src/sgml/ref/merge.sgml
new file mode 100644
index 0995fe0..5f562f8
--- a/doc/src/sgml/ref/merge.sgml
+++ b/doc/src/sgml/ref/merge.sgml
@@ -128,9 +128,9 @@ DELETE
<term><replaceable class="parameter">target_table_name</replaceable></term>
<listitem>
<para>
- The name (optionally schema-qualified) of the target table to merge into.
- If <literal>ONLY</literal> is specified before the table name, matching
- rows are updated or deleted in the named table only. If
+ The name (optionally schema-qualified) of the target table or view to
+ merge into. If <literal>ONLY</literal> is specified before the table
+ name, matching rows are updated or deleted in the named table only. If
<literal>ONLY</literal> is not specified, matching rows are also updated
or deleted in any tables inheriting from the named table. Optionally,
<literal>*</literal> can be specified after the table name to explicitly
@@ -138,6 +138,16 @@ DELETE
<literal>ONLY</literal> keyword and <literal>*</literal> option do not
affect insert actions, which always insert into the named table only.
</para>
+
+ <para>
+ If <replaceable class="parameter">target_table_name</replaceable> is a
+ view, it must either be automatically updatable with no
+ <literal>INSTEAD OF</literal> triggers, or it must have
+ <literal>INSTEAD OF</literal> triggers for every type of action
+ (<literal>INSERT</literal>, <literal>UPDATE</literal>, and
+ <literal>DELETE</literal>) specified in the <literal>WHEN</literal>
+ clauses. Views with rules are not supported.
+ </para>
</listitem>
</varlistentry>
@@ -484,7 +494,11 @@ MERGE <replaceable class="parameter">tot
the action's event type.
</para>
</listitem>
- </orderedlist></para>
+ </orderedlist>
+ If the target relation is a view with <literal>INSTEAD OF ROW</literal>
+ triggers for the action's event type, they are used to perform the
+ action instead.
+ </para>
</listitem>
</orderedlist></para>
</listitem>
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
new file mode 100644
index d229b94..11b1a10
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -797,9 +797,9 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 W
<para>
What happens if a view is named as the target relation for an
- <command>INSERT</command>, <command>UPDATE</command>, or
- <command>DELETE</command>? Doing the substitutions
- described above would give a query tree in which the result
+ <command>INSERT</command>, <command>UPDATE</command>,
+ <command>DELETE</command>, or <command>MERGE</command>? Doing the
+ substitutions described above would give a query tree in which the result
relation points at a subquery range-table entry, which will not
work. There are several ways in which <productname>PostgreSQL</productname>
can support the appearance of updating a view, however.
@@ -813,11 +813,12 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 W
If the subquery selects from a single base relation and is simple
enough, the rewriter can automatically replace the subquery with the
underlying base relation so that the <command>INSERT</command>,
- <command>UPDATE</command>, or <command>DELETE</command> is applied to
- the base relation in the appropriate way. Views that are
- <quote>simple enough</quote> for this are called <firstterm>automatically
- updatable</firstterm>. For detailed information on the kinds of view that can
- be automatically updated, see <xref linkend="sql-createview"/>.
+ <command>UPDATE</command>, <command>DELETE</command>, or
+ <command>MERGE</command> is applied to the base relation in the
+ appropriate way. Views that are <quote>simple enough</quote> for this
+ are called <firstterm>automatically updatable</firstterm>. For detailed
+ information on the kinds of view that can be automatically updated, see
+ <xref linkend="sql-createview"/>.
</para>
<para>
@@ -827,10 +828,10 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 W
Rewriting works slightly differently
in this case. For <command>INSERT</command>, the rewriter does
nothing at all with the view, leaving it as the result relation
- for the query. For <command>UPDATE</command> and
- <command>DELETE</command>, it's still necessary to expand the
+ for the query. For <command>UPDATE</command>, <command>DELETE</command>,
+ and <command>MERGE</command>, it's still necessary to expand the
view query to produce the <quote>old</quote> rows that the command will
- attempt to update or delete. So the view is expanded as normal,
+ attempt to update, delete or merge. So the view is expanded as normal,
but another unexpanded range-table entry is added to the query
to represent the view in its capacity as the result relation.
</para>
@@ -842,13 +843,13 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 W
list to identify the physical locations of the rows to be updated.
This does not work if the result relation is a view, because a view
does not have any <acronym>CTID</acronym>, since its rows do not have
- actual physical locations. Instead, for an <command>UPDATE</command>
- or <command>DELETE</command> operation, a special <literal>wholerow</literal>
- entry is added to the target list, which expands to include all
- columns from the view. The executor uses this value to supply the
- <quote>old</quote> row to the <literal>INSTEAD OF</literal> trigger. It is
- up to the trigger to work out what to update based on the old and
- new row values.
+ actual physical locations. Instead, for an <command>UPDATE</command>,
+ <command>DELETE</command>, or <command>MERGE</command> operation, a
+ special <literal>wholerow</literal> entry is added to the target list,
+ which expands to include all columns from the view. The executor uses this
+ value to supply the <quote>old</quote> row to the
+ <literal>INSTEAD OF</literal> trigger. It is up to the trigger to work
+ out what to update based on the old and new row values.
</para>
<para>
@@ -857,7 +858,8 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 W
<command>UPDATE</command>, and <command>DELETE</command> commands on
a view. These rules will rewrite the command, typically into a command
that updates one or more tables, rather than views. That is the topic
- of <xref linkend="rules-update"/>.
+ of <xref linkend="rules-update"/>. Note that this will not work with
+ <command>MERGE</command>, which currently does not support rules.
</para>
<para>
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
new file mode 100644
index af52fac..7152e6c
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -768,7 +768,7 @@ CopyFrom(CopyFromState cstate)
estate->es_rteperminfos = cstate->rteperminfos;
/* Verify the named relation is a valid target for INSERT */
- CheckValidResultRel(resultRelInfo, CMD_INSERT);
+ CheckValidResultRel(resultRelInfo, CMD_INSERT, NIL);
ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index 39bfb48..fc8a710
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -985,11 +985,13 @@ InitPlan(QueryDesc *queryDesc, int eflag
* CheckValidRowMarkRel.
*/
void
-CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
+CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
+ List *mergeActions)
{
Relation resultRel = resultRelInfo->ri_RelationDesc;
TriggerDesc *trigDesc = resultRel->trigdesc;
FdwRoutine *fdwroutine;
+ ListCell *lc;
switch (resultRel->rd_rel->relkind)
{
@@ -1013,10 +1015,11 @@ CheckValidResultRel(ResultRelInfo *resul
/*
* Okay only if there's a suitable INSTEAD OF trigger. Messages
- * here should match rewriteHandler.c's rewriteTargetView and
- * RewriteQuery, except that we omit errdetail because we haven't
- * got the information handy (and given that we really shouldn't
- * get here anyway, it's not worth great exertion to get).
+ * here should match ereport_view_not_updatable() in
+ * rewriteHandler.c, except that we omit errdetail because we
+ * haven't got the information handy (and given that we really
+ * shouldn't get here anyway, it's not worth great exertion to
+ * get).
*/
switch (operation)
{
@@ -1044,6 +1047,51 @@ CheckValidResultRel(ResultRelInfo *resul
RelationGetRelationName(resultRel)),
errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
break;
+ case CMD_MERGE:
+
+ /*
+ * Must have a suitable INSTEAD OF trigger for each MERGE
+ * action. Note that the error hints here differ from
+ * above, since MERGE doesn't support rules.
+ */
+ foreach(lc, mergeActions)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+
+ switch (action->commandType)
+ {
+ case CMD_INSERT:
+ if (!trigDesc || !trigDesc->trig_insert_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into view \"%s\"",
+ RelationGetRelationName(resultRel)),
+ errhint("To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.")));
+ break;
+ case CMD_UPDATE:
+ if (!trigDesc || !trigDesc->trig_update_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update view \"%s\"",
+ RelationGetRelationName(resultRel)),
+ errhint("To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger.")));
+ break;
+ case CMD_DELETE:
+ if (!trigDesc || !trigDesc->trig_delete_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot delete from view \"%s\"",
+ RelationGetRelationName(resultRel)),
+ errhint("To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.")));
+ break;
+ case CMD_NOTHING:
+ break;
+ default:
+ elog(ERROR, "unrecognized commandType: %d", action->commandType);
+ break;
+ }
+ }
+ break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
break;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
new file mode 100644
index fd6ca8a..7a7bc3b
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -361,7 +361,7 @@ ExecFindPartition(ModifyTableState *mtst
if (rri)
{
/* Verify this ResultRelInfo allows INSERTs */
- CheckValidResultRel(rri, CMD_INSERT);
+ CheckValidResultRel(rri, CMD_INSERT, NIL);
/*
* Initialize information needed to insert this and
@@ -527,7 +527,7 @@ ExecInitPartitionInfo(ModifyTableState *
* partition-key becomes a DELETE+INSERT operation, so this check is still
* required when the operation is CMD_UPDATE.
*/
- CheckValidResultRel(leaf_part_rri, CMD_INSERT);
+ CheckValidResultRel(leaf_part_rri, CMD_INSERT, NIL);
/*
* Open partition indices. The user may have asked to check for conflicts
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index 6f0543a..815a6bb
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -170,11 +170,13 @@ static TupleTableSlot *internalGetUpdate
static TupleTableSlot *ExecMerge(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
ItemPointer tupleid,
+ HeapTuple oldtuple,
bool canSetTag);
static void ExecInitMerge(ModifyTableState *mtstate, EState *estate);
static bool ExecMergeMatched(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
ItemPointer tupleid,
+ HeapTuple oldtuple,
bool canSetTag);
static void ExecMergeNotMatched(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
@@ -2687,13 +2689,14 @@ ExecOnConflictUpdate(ModifyTableContext
*/
static TupleTableSlot *
ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
- ItemPointer tupleid, bool canSetTag)
+ ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
{
bool matched;
/*-----
- * If we are dealing with a WHEN MATCHED case (tupleid is valid), we
- * execute the first action for which the additional WHEN MATCHED AND
+ * If we are dealing with a WHEN MATCHED case (tupleid or oldtuple is
+ * valid, depending on whether the result relation is a table or a view),
+ * we execute the first action for which the additional WHEN MATCHED AND
* quals pass. If an action without quals is found, that action is
* executed.
*
@@ -2734,9 +2737,10 @@ ExecMerge(ModifyTableContext *context, R
* from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
* livelock.
*/
- matched = tupleid != NULL;
+ matched = tupleid != NULL || oldtuple != NULL;
if (matched)
- matched = ExecMergeMatched(context, resultRelInfo, tupleid, canSetTag);
+ matched = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple,
+ canSetTag);
/*
* Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched()
@@ -2751,8 +2755,10 @@ ExecMerge(ModifyTableContext *context, R
}
/*
- * Check and execute the first qualifying MATCHED action. The current target
- * tuple is identified by tupleid.
+ * Check and execute the first qualifying MATCHED action. If the target
+ * relation is a table, the current target tuple is identified by tupleid.
+ * Otherwise, if the target relation is a view, oldtuple is the current target
+ * tuple from the view.
*
* We start from the first WHEN MATCHED action and check if the WHEN quals
* pass, if any. If the WHEN quals for the first action do not pass, we
@@ -2773,7 +2779,7 @@ ExecMerge(ModifyTableContext *context, R
*/
static bool
ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
- ItemPointer tupleid, bool canSetTag)
+ ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
{
ModifyTableState *mtstate = context->mtstate;
TupleTableSlot *newslot;
@@ -2799,22 +2805,32 @@ ExecMergeMatched(ModifyTableContext *con
econtext->ecxt_innertuple = context->planSlot;
econtext->ecxt_outertuple = NULL;
+ /*
+ * This routine is only invoked for matched rows, so we should either have
+ * the tupleid of the target row, or an old tuple from the target wholerow
+ * junk attr.
+ */
+ if (oldtuple != NULL)
+ ExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot,
+ false);
+
lmerge_matched:
/*
- * This routine is only invoked for matched rows, and we must have found
- * the tupleid of the target row in that case; fetch that tuple.
+ * If not passed an old tuple, use tupleid to fetch the old target row.
*
* We use SnapshotAny for this because we might get called again after
* EvalPlanQual returns us a new tuple, which may not be visible to our
* MVCC snapshot.
*/
-
- if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
- tupleid,
- SnapshotAny,
- resultRelInfo->ri_oldTupleSlot))
- elog(ERROR, "failed to fetch the target tuple");
+ if (oldtuple == NULL)
+ {
+ if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+ tupleid,
+ SnapshotAny,
+ resultRelInfo->ri_oldTupleSlot))
+ elog(ERROR, "failed to fetch the target tuple");
+ }
foreach(l, resultRelInfo->ri_matchedMergeAction)
{
@@ -2876,9 +2892,27 @@ lmerge_matched:
result = TM_Ok;
break;
}
- ExecUpdatePrepareSlot(resultRelInfo, newslot, context->estate);
- result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
- newslot, false, &updateCxt);
+
+ /* INSTEAD OF ROW UPDATE Triggers */
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_instead_row)
+ {
+ if (!ExecIRUpdateTriggers(estate, resultRelInfo,
+ oldtuple, newslot))
+ {
+ result = TM_Ok;
+ break; /* "do nothing" */
+ }
+ result = TM_Ok;
+ }
+ else
+ {
+ ExecUpdatePrepareSlot(resultRelInfo, newslot,
+ context->estate);
+ result = ExecUpdateAct(context, resultRelInfo, tupleid,
+ NULL, newslot, false, &updateCxt);
+ }
+
if (result == TM_Ok && updateCxt.updated)
{
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
@@ -2896,7 +2930,25 @@ lmerge_matched:
result = TM_Ok;
break;
}
- result = ExecDeleteAct(context, resultRelInfo, tupleid, false);
+
+ /* INSTEAD OF ROW DELETE Triggers */
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
+ {
+ if (!ExecIRDeleteTriggers(estate, resultRelInfo,
+ oldtuple))
+ {
+ result = TM_Ok;
+ break; /* "do nothing" */
+ }
+ result = TM_Ok;
+ }
+ else
+ {
+ result = ExecDeleteAct(context, resultRelInfo, tupleid,
+ false);
+ }
+
if (result == TM_Ok)
{
ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
@@ -3667,7 +3719,8 @@ ExecModifyTable(PlanState *pstate)
{
EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
- ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+ ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+ node->canSetTag);
continue; /* no RETURNING support yet */
}
@@ -3745,7 +3798,8 @@ ExecModifyTable(PlanState *pstate)
{
EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
- ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+ ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+ node->canSetTag);
continue; /* no RETURNING support yet */
}
@@ -3778,9 +3832,28 @@ ExecModifyTable(PlanState *pstate)
datum = ExecGetJunkAttribute(slot,
resultRelInfo->ri_RowIdAttNo,
&isNull);
- /* shouldn't ever get a null result... */
+
+ /*
+ * For commands other than MERGE, any tuples having a null row
+ * identifier are errors. For MERGE, we may need to handle
+ * them as WHEN NOT MATCHED clauses if any, so do that.
+ *
+ * Note that we use the node's toplevel resultRelInfo, not any
+ * specific partition's.
+ */
if (isNull)
+ {
+ if (operation == CMD_MERGE)
+ {
+ EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
+
+ ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+ node->canSetTag);
+ continue; /* no RETURNING support yet */
+ }
+
elog(ERROR, "wholerow is NULL");
+ }
oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
oldtupdata.t_len =
@@ -3852,7 +3925,8 @@ ExecModifyTable(PlanState *pstate)
break;
case CMD_MERGE:
- slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
+ slot = ExecMerge(&context, resultRelInfo, tupleid, oldtuple,
+ node->canSetTag);
break;
default:
@@ -4028,6 +4102,10 @@ ExecInitModifyTable(ModifyTable *node, E
foreach(l, node->resultRelations)
{
Index resultRelation = lfirst_int(l);
+ List *mergeActions = NIL;
+
+ if (node->mergeActionLists)
+ mergeActions = list_nth(node->mergeActionLists, i);
if (resultRelInfo != mtstate->rootResultRelInfo)
{
@@ -4049,7 +4127,7 @@ ExecInitModifyTable(ModifyTable *node, E
/*
* Verify result relation is a valid target for the current operation
*/
- CheckValidResultRel(resultRelInfo, operation);
+ CheckValidResultRel(resultRelInfo, operation, mergeActions);
resultRelInfo++;
i++;
@@ -4125,8 +4203,6 @@ ExecInitModifyTable(ModifyTable *node, E
}
else
{
- /* No support for MERGE */
- Assert(operation != CMD_MERGE);
/* Other valid target relkinds must provide wholerow */
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist,
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
new file mode 100644
index 870d84b..7035f0b
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -198,12 +198,17 @@ transform_MERGE_to_join(Query *parse)
/*
* Create a JOIN between the target and the source relation.
+ *
+ * Here the target is identified by parse->mergeTargetSrcIdx. For a
+ * regular table, this will equal parse->resultRelation, but for a
+ * trigger-updatable view, it will be the expanded view subquery that we
+ * need to pull data from.
*/
joinexpr = makeNode(JoinExpr);
joinexpr->jointype = jointype;
joinexpr->isNatural = false;
joinexpr->larg = (Node *) makeNode(RangeTblRef);
- ((RangeTblRef *) joinexpr->larg)->rtindex = parse->resultRelation;
+ ((RangeTblRef *) joinexpr->larg)->rtindex = parse->mergeTargetSrcIdx;
joinexpr->rarg = linitial(parse->jointree->fromlist); /* original join */
joinexpr->usingClause = NIL;
joinexpr->join_using_alias = NULL;
@@ -215,6 +220,18 @@ transform_MERGE_to_join(Query *parse)
/* Make the new join be the sole entry in the query's jointree */
parse->jointree->fromlist = list_make1(joinexpr);
parse->jointree->quals = NULL;
+
+ /*
+ * If necessary, mark parse->targetlist entries that refer to the target
+ * as nullable by the join. Normally the targetlist will be empty for a
+ * MERGE, but if the target is a trigger-updatable view, it will contain a
+ * whole-row Var referring to the expanded view query.
+ */
+ if (jointype == JOIN_RIGHT || jointype == JOIN_FULL)
+ parse->targetList = (List *)
+ add_nulling_relids((Node *) parse->targetList,
+ bms_make_singleton(parse->mergeTargetSrcIdx),
+ bms_make_singleton(joinrti));
}
/*
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index c6d747b..027d2be
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -157,15 +157,14 @@ preprocess_targetlist(PlannerInfo *root)
/*
* Add resjunk entries for any Vars used in each action's
* targetlist and WHEN condition that belong to relations other
- * than target. Note that aggregates, window functions and
- * placeholder vars are not possible anywhere in MERGE's WHEN
- * clauses. (PHVs may be added later, but they don't concern us
- * here.)
+ * than target. Note that aggregates and window functions are not
+ * possible anywhere in MERGE's WHEN clauses, but PlaceHolderVars
+ * may have been added by subquery pullup.
*/
vars = pull_var_clause((Node *)
list_concat_copy((List *) action->qual,
action->targetList),
- 0);
+ PVC_INCLUDE_PLACEHOLDERS);
foreach(l2, vars)
{
Var *var = (Var *) lfirst(l2);
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
new file mode 100644
index 9d37738..8b111f8
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -890,8 +890,7 @@ add_row_identity_columns(PlannerInfo *ro
Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE || commandType == CMD_MERGE);
- if (commandType == CMD_MERGE ||
- relkind == RELKIND_RELATION ||
+ if (relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
new file mode 100644
index d886637..ec82c64
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -165,30 +165,31 @@ transformMergeStmt(ParseState *pstate, M
/*
* Set up the MERGE target table. The target table is added to the
- * namespace below and to joinlist in transform_MERGE_to_join, so don't
- * do it here.
+ * namespace below and to joinlist in transform_MERGE_to_join, so don't do
+ * it here.
+ *
+ * Initially mergeTargetSrcIdx is the same as resultRelation, so data is
+ * read from the table being updated. However, that might be changed by
+ * the rewriter, if the target is a trigger-updatable view, to allow
+ * target data to be read from the expanded view query while updating the
+ * original view relation.
*/
qry->resultRelation = setTargetTable(pstate, stmt->relation,
stmt->relation->inh,
false, targetPerms);
+ qry->mergeTargetSrcIdx = qry->resultRelation;
/*
* MERGE is unsupported in various cases
*/
if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
- pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
+ pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot execute MERGE on relation \"%s\"",
RelationGetRelationName(pstate->p_target_relation)),
errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
- if (pstate->p_target_relation->rd_rules != NULL &&
- pstate->p_target_relation->rd_rules->numLocks > 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot execute MERGE on relation \"%s\"",
- RelationGetRelationName(pstate->p_target_relation)),
- errdetail("MERGE is not supported for relations with rules.")));
/* Now transform the source relation to produce the source RTE. */
transformFromClause(pstate,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index b6452e9..2a64534
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -85,10 +85,10 @@ static void rewriteValuesRTEToNulls(Quer
static void markQueryForLocking(Query *qry, Node *jtnode,
LockClauseStrength strength, LockWaitPolicy waitPolicy,
bool pushedDown);
-static List *matchLocks(CmdType event, RuleLock *rulelocks,
+static List *matchLocks(CmdType event, Relation relation,
int varno, Query *parsetree, bool *hasUpdate);
static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
-static bool view_has_instead_trigger(Relation view, CmdType event);
+static bool view_has_instead_trigger(Relation view, CmdType event, List *mergeActionList);
static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
@@ -1462,7 +1462,7 @@ rewriteValuesRTE(Query *parsetree, Range
*/
isAutoUpdatableView = false;
if (target_relation->rd_rel->relkind == RELKIND_VIEW &&
- !view_has_instead_trigger(target_relation, CMD_INSERT))
+ !view_has_instead_trigger(target_relation, CMD_INSERT, NIL))
{
List *locks;
bool hasUpdate;
@@ -1470,7 +1470,7 @@ rewriteValuesRTE(Query *parsetree, Range
ListCell *l;
/* Look for an unconditional DO INSTEAD rule */
- locks = matchLocks(CMD_INSERT, target_relation->rd_rules,
+ locks = matchLocks(CMD_INSERT, target_relation,
parsetree->resultRelation, parsetree, &hasUpdate);
found = false;
@@ -1638,11 +1638,12 @@ rewriteValuesRTEToNulls(Query *parsetree
*/
static List *
matchLocks(CmdType event,
- RuleLock *rulelocks,
+ Relation relation,
int varno,
Query *parsetree,
bool *hasUpdate)
{
+ RuleLock *rulelocks = relation->rd_rules;
List *matching_locks = NIL;
int nlocks;
int i;
@@ -1650,10 +1651,6 @@ matchLocks(CmdType event,
if (rulelocks == NULL)
return NIL;
- /* No rule support for MERGE */
- if (parsetree->commandType == CMD_MERGE)
- return NIL;
-
if (parsetree->commandType != CMD_SELECT)
{
if (parsetree->resultRelation != varno)
@@ -1662,6 +1659,23 @@ matchLocks(CmdType event,
nlocks = rulelocks->numLocks;
+ /* No rule support for MERGE */
+ if (parsetree->commandType == CMD_MERGE)
+ {
+ for (i = 0; i < nlocks; i++)
+ {
+ RewriteRule *oneLock = rulelocks->rules[i];
+
+ if (oneLock->event != CMD_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot execute MERGE on relation \"%s\"",
+ RelationGetRelationName(relation)),
+ errdetail("MERGE is not supported for relations with rules.")));
+ }
+ return NIL;
+ }
+
for (i = 0; i < nlocks; i++)
{
RewriteRule *oneLock = rulelocks->rules[i];
@@ -1734,9 +1748,9 @@ ApplyRetrieveRule(Query *parsetree,
* For INSERT, we needn't do anything. The unmodified RTE will serve
* fine as the result relation.
*
- * For UPDATE/DELETE, we need to expand the view so as to have source
- * data for the operation. But we also need an unmodified RTE to
- * serve as the target. So, copy the RTE and add the copy to the
+ * For UPDATE/DELETE/MERGE, we need to expand the view so as to have
+ * source data for the operation. But we also need an unmodified RTE
+ * to serve as the target. So, copy the RTE and add the copy to the
* rangetable. Note that the copy does not get added to the jointree.
* Also note that there's a hack in fireRIRrules to avoid calling this
* function again when it arrives at the copied RTE.
@@ -1744,7 +1758,8 @@ ApplyRetrieveRule(Query *parsetree,
if (parsetree->commandType == CMD_INSERT)
return parsetree;
else if (parsetree->commandType == CMD_UPDATE ||
- parsetree->commandType == CMD_DELETE)
+ parsetree->commandType == CMD_DELETE ||
+ parsetree->commandType == CMD_MERGE)
{
RangeTblEntry *newrte;
Var *var;
@@ -1754,6 +1769,7 @@ ApplyRetrieveRule(Query *parsetree,
newrte = copyObject(rte);
parsetree->rtable = lappend(parsetree->rtable, newrte);
parsetree->resultRelation = list_length(parsetree->rtable);
+ /* parsetree->mergeTargetSrcIdx unchanged (use expanded view) */
/*
* For the most part, Vars referencing the view should remain as
@@ -2436,11 +2452,18 @@ get_view_query(Relation view)
* If it does, we don't want to treat it as auto-updatable. This test can't
* be folded into view_query_is_auto_updatable because it's not an error
* condition.
+ *
+ * For MERGE, this will return true if there is an INSTEAD OF trigger for
+ * every action in mergeActionList, and false if there are any actions that
+ * lack an INSTEAD OF trigger. If there are no data-modifying MERGE actions
+ * (only DO NOTHING actions), true is returned so that the view is treated
+ * as trigger-updatable, rather than erroring out if it's not auto-updatable.
*/
static bool
-view_has_instead_trigger(Relation view, CmdType event)
+view_has_instead_trigger(Relation view, CmdType event, List *mergeActionList)
{
TriggerDesc *trigDesc = view->trigdesc;
+ ListCell *lc;
switch (event)
{
@@ -2456,6 +2479,34 @@ view_has_instead_trigger(Relation view,
if (trigDesc && trigDesc->trig_delete_instead_row)
return true;
break;
+ case CMD_MERGE:
+ foreach(lc, mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+
+ switch (action->commandType)
+ {
+ case CMD_INSERT:
+ if (!trigDesc || !trigDesc->trig_insert_instead_row)
+ return false;
+ break;
+ case CMD_UPDATE:
+ if (!trigDesc || !trigDesc->trig_update_instead_row)
+ return false;
+ break;
+ case CMD_DELETE:
+ if (!trigDesc || !trigDesc->trig_delete_instead_row)
+ return false;
+ break;
+ case CMD_NOTHING:
+ /* No trigger required */
+ break;
+ default:
+ elog(ERROR, "unrecognized commandType: %d", action->commandType);
+ break;
+ }
+ }
+ return true; /* no actions without an INSTEAD OF trigger */
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;
@@ -2997,6 +3048,98 @@ adjust_view_column_set(Bitmapset *cols,
/*
+ * ereport_view_not_updatable -
+ * Report an error from an attempt to update a non-updatable view.
+ *
+ * The error messages here match execMain.c's CheckValidResultRel, except that
+ * the executor doesn't have access to the same the error detail that we have
+ * here. In principle, those executor checks are unnecessary, but we keep
+ * them just in case.
+ */
+static void
+ereport_view_not_updatable(Relation view,
+ CmdType command,
+ List *mergeActions,
+ const char *detail)
+{
+ ListCell *lc;
+
+ switch (command)
+ {
+ case CMD_INSERT:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule."));
+ break;
+ case CMD_UPDATE:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."));
+ break;
+ case CMD_DELETE:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot delete from view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule."));
+ break;
+ case CMD_MERGE:
+ /* Different hints, since MERGE doesn't support rules */
+ foreach(lc, mergeActions)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+
+ if (action->commandType != CMD_NOTHING &&
+ !view_has_instead_trigger(view, action->commandType, NIL))
+ {
+ switch (action->commandType)
+ {
+ case CMD_INSERT:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger."));
+ break;
+ case CMD_UPDATE:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger."));
+ break;
+ case CMD_DELETE:
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot delete from view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(detail)),
+ errhint("To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger."));
+ break;
+ default:
+ elog(ERROR, "unrecognized commandType: %d", action->commandType);
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) command);
+ break;
+ }
+}
+
+
+/*
* rewriteTargetView -
* Attempt to rewrite a query where the target relation is a view, so that
* the view's base relation becomes the target relation.
@@ -3009,6 +3152,7 @@ static Query *
rewriteTargetView(Query *parsetree, Relation view)
{
Query *viewquery;
+ bool insert_or_update;
const char *auto_update_detail;
RangeTblRef *rtr;
int base_rt_index;
@@ -3032,55 +3176,49 @@ rewriteTargetView(Query *parsetree, Rela
*/
viewquery = copyObject(get_view_query(view));
- /* The view must be updatable, else fail */
- auto_update_detail =
- view_query_is_auto_updatable(viewquery,
- parsetree->commandType != CMD_DELETE);
+ /*
+ * Are we doing INSERT/UPDATE, or MERGE containing INSERT/UPDATE? If so,
+ * various additional checks on the view columns need to be applied, and
+ * any view CHECK OPTIONs need to be enforced.
+ */
+ insert_or_update =
+ (parsetree->commandType == CMD_INSERT ||
+ parsetree->commandType == CMD_UPDATE);
- if (auto_update_detail)
+ if (parsetree->commandType == CMD_MERGE)
{
- /* messages here should match execMain.c's CheckValidResultRel */
- switch (parsetree->commandType)
+ foreach(lc, parsetree->mergeActionList)
{
- case CMD_INSERT:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot insert into view \"%s\"",
- RelationGetRelationName(view)),
- errdetail_internal("%s", _(auto_update_detail)),
- errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
- break;
- case CMD_UPDATE:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot update view \"%s\"",
- RelationGetRelationName(view)),
- errdetail_internal("%s", _(auto_update_detail)),
- errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
- break;
- case CMD_DELETE:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot delete from view \"%s\"",
- RelationGetRelationName(view)),
- errdetail_internal("%s", _(auto_update_detail)),
- errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
- break;
- default:
- elog(ERROR, "unrecognized CmdType: %d",
- (int) parsetree->commandType);
+ MergeAction *action = (MergeAction *) lfirst(lc);
+
+ if (action->commandType == CMD_INSERT ||
+ action->commandType == CMD_UPDATE)
+ {
+ insert_or_update = true;
break;
+ }
}
}
+ /* The view must be updatable, else fail */
+ auto_update_detail =
+ view_query_is_auto_updatable(viewquery, insert_or_update);
+
+ if (auto_update_detail)
+ ereport_view_not_updatable(view,
+ parsetree->commandType,
+ parsetree->mergeActionList,
+ auto_update_detail);
+
/*
- * For INSERT/UPDATE the modified columns must all be updatable. Note that
- * we get the modified columns from the query's targetlist, not from the
- * result RTE's insertedCols and/or updatedCols set, since
- * rewriteTargetListIU may have added additional targetlist entries for
- * view defaults, and these must also be updatable.
+ * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE) the modified
+ * columns must all be updatable. Note that we get the modified columns
+ * from the query's targetlist, not from the result RTE's insertedCols
+ * and/or updatedCols set, since rewriteTargetListIU may have added
+ * additional targetlist entries for view defaults, and these must also be
+ * updatable.
*/
- if (parsetree->commandType != CMD_DELETE)
+ if (insert_or_update)
{
Bitmapset *modified_cols = NULL;
char *non_updatable_col;
@@ -3106,6 +3244,25 @@ rewriteTargetView(Query *parsetree, Rela
}
}
+ foreach(lc, parsetree->mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+ ListCell *lc2;
+
+ if (action->commandType == CMD_INSERT ||
+ action->commandType == CMD_UPDATE)
+ {
+ foreach(lc2, action->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc2);
+
+ if (!tle->resjunk)
+ modified_cols = bms_add_member(modified_cols,
+ tle->resno - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+ }
+
auto_update_detail = view_cols_are_auto_updatable(viewquery,
modified_cols,
NULL,
@@ -3134,6 +3291,14 @@ rewriteTargetView(Query *parsetree, Rela
RelationGetRelationName(view)),
errdetail_internal("%s", _(auto_update_detail))));
break;
+ case CMD_MERGE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot merge into column \"%s\" of view \"%s\"",
+ non_updatable_col,
+ RelationGetRelationName(view)),
+ errdetail_internal("%s", _(auto_update_detail))));
+ break;
default:
elog(ERROR, "unrecognized CmdType: %d",
(int) parsetree->commandType);
@@ -3142,6 +3307,30 @@ rewriteTargetView(Query *parsetree, Rela
}
}
+ /*
+ * For MERGE, there must not be any INSTEAD OF triggers on an otherwise
+ * updatable view. The caller already checked that there isn't a full set
+ * of INSTEAD OF triggers, so this is to guard against having a partial
+ * set (mixing auto-update and trigger-update actions in a single command
+ * isn't supported).
+ */
+ if (parsetree->commandType == CMD_MERGE)
+ {
+ foreach(lc, parsetree->mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+
+ if (action->commandType != CMD_NOTHING &&
+ view_has_instead_trigger(view, action->commandType, NIL))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot merge into view \"%s\"",
+ RelationGetRelationName(view)),
+ errdetail("MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others."),
+ errhint("To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers."));
+ }
+ }
+
/* Locate RTE describing the view in the outer query */
view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
@@ -3205,8 +3394,8 @@ rewriteTargetView(Query *parsetree, Rela
new_rt_index = list_length(parsetree->rtable);
/*
- * INSERTs never inherit. For UPDATE/DELETE, we use the view query's
- * inheritance flag for the base relation.
+ * INSERTs never inherit. For UPDATE/DELETE/MERGE, we use the view
+ * query's inheritance flag for the base relation.
*/
if (parsetree->commandType == CMD_INSERT)
new_rte->inh = false;
@@ -3328,7 +3517,8 @@ rewriteTargetView(Query *parsetree, Rela
/*
* For INSERT/UPDATE we must also update resnos in the targetlist to refer
* to columns of the base relation, since those indicate the target
- * columns to be affected.
+ * columns to be affected. Similarly, for MERGE we must update the resnos
+ * in the merge action targetlists of any INSERT/UPDATE actions.
*
* Note that this destroys the resno ordering of the targetlist, but that
* will be fixed when we recurse through rewriteQuery, which will invoke
@@ -3351,6 +3541,32 @@ rewriteTargetView(Query *parsetree, Rela
elog(ERROR, "attribute number %d not found in view targetlist",
tle->resno);
}
+
+ foreach(lc, parsetree->mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(lc);
+ ListCell *lc2;
+
+ if (action->commandType == CMD_INSERT ||
+ action->commandType == CMD_UPDATE)
+ {
+ foreach(lc2, action->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc2);
+ 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);
+ }
+ }
+ }
}
/*
@@ -3443,10 +3659,10 @@ rewriteTargetView(Query *parsetree, Rela
}
/*
- * 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
- * we did with the view targetlist).
+ * For UPDATE/DELETE/MERGE, 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 we did with the view targetlist).
*
* If it's a security-barrier view, its WHERE quals must be applied before
* quals from the outer query, so we attach them to the RTE as security
@@ -3498,11 +3714,12 @@ rewriteTargetView(Query *parsetree, Rela
}
/*
- * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent
- * view specified WITH CASCADED CHECK OPTION, add the quals from the view
- * to the query's withCheckOptions list.
+ * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE), if the view has
+ * the WITH CHECK OPTION, or any parent view specified WITH CASCADED CHECK
+ * OPTION, add the quals from the view to the query's withCheckOptions
+ * list.
*/
- if (parsetree->commandType != CMD_DELETE)
+ if (insert_or_update)
{
bool has_wco = RelationHasCheckOption(view);
bool cascaded = RelationHasCascadedCheckOption(view);
@@ -3556,14 +3773,13 @@ rewriteTargetView(Query *parsetree, Rela
ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0);
/*
- * Make sure that the query is marked correctly if the added
- * qual has sublinks. We can skip this check if the query is
- * already marked, or if the command is an UPDATE, in which
- * case the same qual will have already been added, and this
- * check will already have been done.
+ * For INSERT, make sure that the query is marked correctly if
+ * the added qual has sublinks. This can be skipped for
+ * UPDATE/MERGE, since the same qual will have already been
+ * added above, and the check will already have been done.
*/
if (!parsetree->hasSubLinks &&
- parsetree->commandType != CMD_UPDATE)
+ parsetree->commandType == CMD_INSERT)
parsetree->hasSubLinks = checkExprHasSubLink(wco->qual);
}
}
@@ -3833,7 +4049,7 @@ RewriteQuery(Query *parsetree, List *rew
/*
* Collect and apply the appropriate rules.
*/
- locks = matchLocks(event, rt_entry_relation->rd_rules,
+ locks = matchLocks(event, rt_entry_relation,
result_relation, parsetree, &hasUpdate);
product_orig_rt_length = list_length(parsetree->rtable);
@@ -3904,7 +4120,8 @@ RewriteQuery(Query *parsetree, List *rew
* automatically updated. If so, we perform the necessary query
* transformation here and add the resulting query to the
* product_queries list, so that it gets recursively rewritten if
- * necessary.
+ * necessary. For MERGE, the view must be automatically updatable if
+ * any of the merge actions lack a corresponding INSTEAD OF trigger.
*
* If the view cannot be automatically updated, we throw an error here
* which is OK since the query would fail at runtime anyway. Throwing
@@ -3914,7 +4131,8 @@ RewriteQuery(Query *parsetree, List *rew
*/
if (!instead &&
rt_entry_relation->rd_rel->relkind == RELKIND_VIEW &&
- !view_has_instead_trigger(rt_entry_relation, event))
+ !view_has_instead_trigger(rt_entry_relation, event,
+ parsetree->mergeActionList))
{
/*
* If there were any qualified INSTEAD rules, don't allow the view
@@ -3926,39 +4144,10 @@ RewriteQuery(Query *parsetree, List *rew
* we keep them just in case.
*/
if (qual_product != NULL)
- {
- switch (parsetree->commandType)
- {
- case CMD_INSERT:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot insert into view \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
- errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
- errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
- break;
- case CMD_UPDATE:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot update view \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
- errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
- errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
- break;
- case CMD_DELETE:
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot delete from view \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
- errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
- errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
- break;
- default:
- elog(ERROR, "unrecognized CmdType: %d",
- (int) parsetree->commandType);
- break;
- }
- }
+ ereport_view_not_updatable(rt_entry_relation,
+ parsetree->commandType,
+ parsetree->mergeActionList,
+ gettext_noop("Views with conditional DO INSTEAD rules are not automatically updatable."));
/*
* Attempt to rewrite the query to automatically update the view.
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
new file mode 100644
index 04718f6..3c77258
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -496,9 +496,10 @@ OffsetVarNodes(Node *node, int offset, i
/*
* If we are starting at a Query, and sublevels_up is zero, then we
* must also fix rangetable indexes in the Query itself --- namely
- * resultRelation, exclRelIndex and rowMarks entries. sublevels_up
- * cannot be zero when recursing into a subquery, so there's no need
- * to have the same logic inside OffsetVarNodes_walker.
+ * resultRelation, mergeTargetSrcIdx, exclRelIndex and rowMarks
+ * entries. sublevels_up cannot be zero when recursing into a
+ * subquery, so there's no need to have the same logic inside
+ * OffsetVarNodes_walker.
*/
if (sublevels_up == 0)
{
@@ -507,6 +508,9 @@ OffsetVarNodes(Node *node, int offset, i
if (qry->resultRelation)
qry->resultRelation += offset;
+ if (qry->mergeTargetSrcIdx)
+ qry->mergeTargetSrcIdx += offset;
+
if (qry->onConflict && qry->onConflict->exclRelIndex)
qry->onConflict->exclRelIndex += offset;
@@ -687,9 +691,10 @@ ChangeVarNodes(Node *node, int rt_index,
/*
* If we are starting at a Query, and sublevels_up is zero, then we
* must also fix rangetable indexes in the Query itself --- namely
- * resultRelation and rowMarks entries. sublevels_up cannot be zero
- * when recursing into a subquery, so there's no need to have the same
- * logic inside ChangeVarNodes_walker.
+ * resultRelation, mergeTargetSrcIdx, exclRelIndex and rowMarks
+ * entries. sublevels_up cannot be zero when recursing into a
+ * subquery, so there's no need to have the same logic inside
+ * ChangeVarNodes_walker.
*/
if (sublevels_up == 0)
{
@@ -698,6 +703,9 @@ ChangeVarNodes(Node *node, int rt_index,
if (qry->resultRelation == rt_index)
qry->resultRelation = new_index;
+ if (qry->mergeTargetSrcIdx == rt_index)
+ qry->mergeTargetSrcIdx = new_index;
+
/* this is unlikely to ever be used, but ... */
if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index)
qry->onConflict->exclRelIndex = new_index;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
new file mode 100644
index 5e1882e..95e22d6
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -832,6 +832,7 @@ static const SchemaQuery Query_for_list_
.catname = "pg_catalog.pg_class c",
.selcondition =
"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_VIEW) ", "
CppAsString2(RELKIND_PARTITIONED_TABLE) ") ",
.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
.namespace = "c.relnamespace",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
new file mode 100644
index e7e25c0..765cd42
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -200,7 +200,8 @@ extern void standard_ExecutorEnd(QueryDe
extern void ExecutorRewind(QueryDesc *queryDesc);
extern bool ExecCheckPermissions(List *rangeTable,
List *rteperminfos, bool ereport_on_violation);
-extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
+extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
+ List *mergeActions);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index f7d7f10..72edef2
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -186,6 +186,8 @@ typedef struct Query
List *mergeActionList; /* list of actions for MERGE (only) */
/* whether to use outer join */
bool mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
+ /* rtable index of target relation that MERGE pulls data from */
+ int mergeTargetSrcIdx pg_node_attr(query_jumble_ignore);
List *targetList; /* target list (of TargetEntry) */
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
new file mode 100644
index e1d3639..da521f2
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -138,16 +138,6 @@ COPY (
) TO stdout;
ERROR: MERGE not supported in COPY
-- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-ERROR: cannot execute MERGE on relation "tv"
-DETAIL: This operation is not supported for views.
-DROP VIEW tv;
-- materialized view
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
MERGE INTO mv t
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 0cbedc6..4fc0eb4
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -184,6 +184,24 @@ INSERT INTO ro_view13 VALUES (3, 'Row 3'
ERROR: cannot insert into view "ro_view13"
DETAIL: Views that do not select from a single table or view are not automatically updatable.
HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+MERGE INTO ro_view13 AS t USING (VALUES (1, 'Row 1')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DELETE;
+ERROR: cannot delete from view "ro_view13"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (2, 'Row 2')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b;
+ERROR: cannot update view "ro_view13"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b);
+ERROR: cannot insert into view "ro_view13"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DO NOTHING
+ WHEN NOT MATCHED THEN DO NOTHING; -- should be OK to do nothing
-- Partially updatable view
INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
ERROR: cannot insert into column "ctid" of view "rw_view14"
@@ -205,6 +223,41 @@ SELECT * FROM base_tbl;
(6 rows)
DELETE FROM rw_view14 WHERE a=3; -- should be OK
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK, except...
+ WHEN NOT MATCHED THEN INSERT VALUES (null, v.a, v.b); -- should fail
+ERROR: cannot merge into column "ctid" of view "rw_view14"
+DETAIL: View columns that refer to system columns are not updatable.
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK
+ WHEN NOT MATCHED THEN INSERT (a,b) VALUES (v.a, v.b); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+ a | b
+----+--------------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Merged row 2
+ 3 | Merged row 3
+(6 rows)
+
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Row 2'), (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED AND t.a = 2 THEN UPDATE SET b = v.b -- should be OK
+ WHEN MATCHED AND t.a = 3 THEN DELETE; -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+ a | b
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Row 2
+(5 rows)
+
-- Partially updatable view
INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
ERROR: cannot insert into column "upper" of view "rw_view15"
@@ -351,6 +404,10 @@ DELETE FROM rw_view16 WHERE a=2; -- shou
ERROR: cannot delete from view "rw_view16"
DETAIL: Views with conditional DO INSTEAD rules are not automatically updatable.
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+MERGE INTO rw_view16 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+ERROR: cannot execute MERGE on relation "rw_view16"
+DETAIL: MERGE is not supported for relations with rules.
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 16 other objects
DETAIL: drop cascades to view ro_view1
@@ -417,6 +474,23 @@ SELECT * FROM base_tbl;
5 | Unspecified
(6 rows)
+MERGE INTO rw_view1 t
+ USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
+ (2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
+ WHEN MATCHED THEN DELETE
+ WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+SELECT * FROM base_tbl ORDER BY a;
+ a | b
+----+-------------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | ROW 1
+ 2 | Unspecified
+ 5 | Unspecified
+(6 rows)
+
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
QUERY PLAN
--------------------------------------------------
@@ -433,6 +507,50 @@ EXPLAIN (costs off) DELETE FROM rw_view1
Index Cond: ((a > 0) AND (a = 5))
(3 rows)
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t USING (VALUES (5, 'X')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DELETE;
+ QUERY PLAN
+--------------------------------------------------
+ Merge on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: ((a > 0) AND (a = 5))
+(3 rows)
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+ USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = 'Updated';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Merge on base_tbl
+ -> Hash Join
+ Hash Cond: (base_tbl.a = generate_series.generate_series)
+ -> Bitmap Heap Scan on base_tbl
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+ -> Hash
+ -> Function Scan on generate_series
+(9 rows)
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+ USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+ WHEN NOT MATCHED THEN INSERT (a) VALUES (s.a);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Merge on base_tbl
+ -> Hash Right Join
+ Hash Cond: (base_tbl.a = generate_series.generate_series)
+ -> Bitmap Heap Scan on base_tbl
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+ -> Hash
+ -> Function Scan on generate_series
+(9 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
@@ -509,6 +627,19 @@ SELECT * FROM rw_view2;
4 | Row 4
(3 rows)
+MERGE INTO rw_view2 t
+ USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
+ WHEN MATCHED AND aaa = 3 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET bbb = v.b
+ WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+SELECT * FROM rw_view2 ORDER BY aaa;
+ aaa | bbb
+-----+-------------
+ 1 | Row 1
+ 4 | R4
+ 5 | Unspecified
+(3 rows)
+
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
QUERY PLAN
--------------------------------------------------------
@@ -701,6 +832,10 @@ SELECT * FROM rw_view2;
2 | Row 2
(2 rows)
+MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+ERROR: cannot execute MERGE on relation "rw_view1"
+DETAIL: MERGE is not supported for relations with rules.
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
QUERY PLAN
----------------------------------------------------------------
@@ -932,6 +1067,21 @@ SELECT * FROM rw_view2;
2 | Row 2
(2 rows)
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+SELECT * FROM base_tbl ORDER BY a;
+ a | b
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 2 | R2
+ 3 | R3
+(5 rows)
+
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
QUERY PLAN
----------------------------------------------------------
@@ -956,6 +1106,76 @@ EXPLAIN (costs off) DELETE FROM rw_view2
Index Cond: (a > 0)
(7 rows)
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+ QUERY PLAN
+------------------------------------------------------------
+ Merge on rw_view1 rw_view1_1
+ -> Hash Right Join
+ Hash Cond: (rw_view1.a = x.x)
+ -> Subquery Scan on rw_view1
+ Filter: (rw_view1.a < 10)
+ -> Bitmap Heap Scan on base_tbl
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+ -> Hash
+ -> Function Scan on generate_series x
+(11 rows)
+
+-- MERGE with incomplete set of INSTEAD OF triggers
+DROP TRIGGER rw_view1_del_trig ON rw_view1;
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR: cannot delete from view "rw_view1"
+DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
+HINT: To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok
+DROP TRIGGER rw_view1_ins_trig ON rw_view1;
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR: cannot insert into view "rw_view1"
+DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
+HINT: To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+-- MERGE with INSTEAD OF triggers on auto-updatable view
+CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR: cannot merge into view "rw_view2"
+DETAIL: MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others.
+HINT: To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers.
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+SELECT * FROM base_tbl ORDER BY a;
+ a | b
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | R1
+ 2 | R2
+ 3 | R3
+(6 rows)
+
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to view rw_view1
@@ -1041,6 +1261,14 @@ INSERT INTO rw_view1 VALUES ('Row 3', 3.
ERROR: permission denied for view rw_view1
INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t
+ USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t
+ USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR: permission denied for table base_tbl
UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
ERROR: permission denied for table base_tbl
@@ -1050,6 +1278,16 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
UPDATE rw_view2 SET bb=bb; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR: permission denied for table base_tbl
DELETE FROM base_tbl; -- not allowed
ERROR: permission denied for table base_tbl
DELETE FROM rw_view1; -- not allowed
@@ -1069,6 +1307,11 @@ DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
ERROR: permission denied for view rw_view1
DELETE FROM rw_view2 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
SELECT * FROM base_tbl;
a | b | c
---+-------+---
@@ -1092,6 +1335,11 @@ ERROR: permission denied for table base
DELETE FROM rw_view1 WHERE aa=3; -- ok
DELETE FROM rw_view2 WHERE aa=4; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+ERROR: permission denied for table base_tbl
SELECT * FROM base_tbl;
a | b | c
---+-------+---
@@ -1115,6 +1363,9 @@ SELECT * FROM rw_view1 FOR UPDATE; -- n
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
SELECT * FROM rw_view2; -- not allowed
@@ -1123,6 +1374,9 @@ SELECT * FROM rw_view2 FOR UPDATE; -- n
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for view rw_view1
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
@@ -1136,6 +1390,9 @@ SELECT * FROM rw_view1 FOR UPDATE; -- n
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
@@ -1143,6 +1400,9 @@ SELECT * FROM rw_view2 FOR UPDATE; -- n
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
@@ -1156,6 +1416,9 @@ SELECT * FROM rw_view2 FOR UPDATE; -- n
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for view rw_view1
RESET SESSION AUTHORIZATION;
GRANT UPDATE ON base_tbl TO regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
@@ -1172,6 +1435,8 @@ SELECT * FROM rw_view1 FOR UPDATE;
(1 row)
UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo';
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2;
a | b | c
@@ -1183,6 +1448,9 @@ SELECT * FROM rw_view2 FOR UPDATE; -- n
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT UPDATE ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
@@ -1199,30 +1467,38 @@ SELECT * FROM rw_view2 FOR UPDATE;
(1 row)
UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'fud';
RESET SESSION AUTHORIZATION;
REVOKE UPDATE ON base_tbl FROM regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1;
a | b | c
---+-----+---
- 1 | bar | 1
+ 1 | fud | 1
(1 row)
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2;
a | b | c
---+-----+---
- 1 | bar | 1
+ 1 | fud | 1
(1 row)
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR: permission denied for table base_tbl
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
@@ -1250,10 +1526,16 @@ UPDATE base_tbl SET a=a; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR: permission denied for table base_tbl
DELETE FROM base_tbl; -- not allowed
ERROR: permission denied for table base_tbl
DELETE FROM rw_view1; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
@@ -1276,10 +1558,18 @@ UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET cc=cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET cc = cc; -- ok
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view1 SET bb=bb; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
@@ -1289,6 +1579,9 @@ ERROR: permission denied for view rw_vi
DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
@@ -1297,17 +1590,21 @@ INSERT INTO rw_view1 VALUES ('Row 4', 4.
ERROR: permission denied for table base_tbl
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
DELETE FROM rw_view1 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- ok
SELECT * FROM base_tbl; -- ok
a | b | c
---+-------+---
- 3 | Row 3 | 3
4 | Row 4 | 4
-(2 rows)
+(1 row)
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
@@ -1322,6 +1619,9 @@ SELECT * FROM rw_view1; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
@@ -1329,6 +1629,9 @@ SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed
+ERROR: permission denied for view rw_view1
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user1;
GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1;
@@ -1342,16 +1645,27 @@ SELECT * FROM rw_view1; -- ok
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
UPDATE rw_view1 SET cc=cc; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
@@ -1360,11 +1674,17 @@ SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2;
@@ -1380,6 +1700,14 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
@@ -1389,6 +1717,15 @@ UPDATE rw_view2 SET bbb=bbb; -- not all
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
+ERROR: permission denied for table base_tbl
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user3;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3;
@@ -1404,6 +1741,14 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
@@ -1411,6 +1756,9 @@ SELECT * FROM rw_view1; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
@@ -1423,6 +1771,14 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
@@ -1435,6 +1791,14 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
@@ -1446,6 +1810,15 @@ UPDATE rw_view2 SET bbb=bbb; -- not all
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- not allowed
ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
+ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
@@ -1458,6 +1831,14 @@ ERROR: permission denied for view rw_vi
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR: permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR: permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
@@ -1475,6 +1856,8 @@ CREATE VIEW rw_view1 AS SELECT a AS aa,
ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
INSERT INTO rw_view1 VALUES (4, 'Row 4');
INSERT INTO rw_view1 (aa) VALUES (5);
+MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a);
SELECT * FROM base_tbl;
a | b | c
---+--------------+---
@@ -1483,7 +1866,8 @@ SELECT * FROM base_tbl;
3 | Unspecified | 3
4 | Row 4 | 4
5 | View default | 5
-(5 rows)
+ 6 | View default | 6
+(6 rows)
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
@@ -1857,6 +2241,38 @@ SELECT * FROM base_tbl_child ORDER BY a;
20
(6 rows)
+MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11
+MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21
+MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only
+MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+ a
+------
+ -199
+ -99
+ -39
+ -29
+ -20
+ -10
+ 100
+ 200
+(8 rows)
+
+SELECT * FROM base_tbl_child ORDER BY a;
+ a
+----
+ 3
+ 4
+ 7
+ 8
+ 11
+ 21
+(6 rows)
+
CREATE TABLE other_tbl_parent (id int);
CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
INSERT INTO other_tbl_parent VALUES (7),(200);
@@ -1886,10 +2302,10 @@ UPDATE rw_view1 SET a = a + 1000 FROM ot
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
a
------
- -200
- -100
- -40
- -30
+ -199
+ -99
+ -39
+ -29
-20
-10
1100
@@ -1901,8 +2317,8 @@ SELECT * FROM base_tbl_child ORDER BY a;
------
3
4
- 10
- 20
+ 11
+ 21
1007
1008
(6 rows)
@@ -1955,16 +2371,39 @@ INSERT INTO rw_view1(a) VALUES (9); -- o
INSERT INTO rw_view1(a) VALUES (10); -- should fail
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (10, 10).
-SELECT * FROM base_tbl;
+SELECT * FROM base_tbl ORDER BY a, b;
a | b
---+----
+ 1 | -1
1 | 2
2 | 3
- 1 | -1
3 | 5
9 | 10
(5 rows)
+MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok
+MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (11, 10).
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok
+MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (3, 3).
+SELECT * FROM base_tbl ORDER BY a, b;
+ a | b
+----+----
+ 0 | 2
+ 1 | -1
+ 2 | 3
+ 3 | 5
+ 9 | 10
+ 10 | 11
+(6 rows)
+
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
-- WITH LOCAL/CASCADED CHECK OPTION
@@ -2228,8 +2667,16 @@ CREATE VIEW rw_view2 AS
INSERT INTO rw_view2 VALUES (-5); -- should fail
ERROR: new row violates check option for view "rw_view2"
DETAIL: Failing row contains (-5).
+MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (-5).
INSERT INTO rw_view2 VALUES (5); -- ok
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok
INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view
UPDATE rw_view2 SET a = a - 10; -- should fail
ERROR: new row violates check option for view "rw_view2"
DETAIL: Failing row contains (-5).
@@ -2237,8 +2684,10 @@ SELECT * FROM base_tbl;
a | b
----+----
5 | 10
+ 6 | 10
50 | 10
-(2 rows)
+ 60 | 10
+(4 rows)
-- Check option won't cascade down to base view with INSTEAD OF triggers
ALTER VIEW rw_view2 SET (check_option=cascaded);
@@ -2247,10 +2696,12 @@ UPDATE rw_view2 SET a = 200 WHERE a = 5;
SELECT * FROM base_tbl;
a | b
-----+----
+ 6 | 10
50 | 10
+ 60 | 10
100 | 10
200 | 10
-(3 rows)
+(5 rows)
-- Neither local nor cascaded check options work with INSTEAD rules
DROP TRIGGER rw_view1_trig ON rw_view1;
@@ -2267,14 +2718,16 @@ UPDATE rw_view2 SET a = -5 WHERE a = 5;
SELECT * FROM base_tbl;
a | b
-----+----
+ 6 | 10
50 | 10
+ 60 | 10
100 | 10
200 | 10
-10 | 10
20 | 10
30 | 10
-5 | 10
-(7 rows)
+(9 rows)
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
@@ -2909,6 +3362,29 @@ create view uv_ptv_wco as select * from
insert into uv_ptv_wco values (1, 2);
ERROR: new row violates check option for view "uv_ptv_wco"
DETAIL: Failing row contains (1, 2, null).
+merge into uv_ptv t
+ using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1);
+ERROR: MERGE command cannot affect row a second time
+HINT: Ensure that not more than one source row matches any one target row.
+merge into uv_ptv t
+ using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5
+ERROR: no partition of relation "uv_pt1" found for row
+DETAIL: Partition key of the failing row contains (b) = (5).
+merge into uv_ptv t
+ using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1); -- ok
+select tableoid::regclass, * from uv_pt order by a, b;
+ tableoid | a | b | v
+----------+---+---+---
+ uv_pt11 | 1 | 3 |
+ uv_pt11 | 1 | 4 |
+(2 rows)
+
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;
-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
new file mode 100644
index f085416..c04ed98
--- a/src/test/regress/sql/merge.sql
+++ b/src/test/regress/sql/merge.sql
@@ -98,15 +98,6 @@ COPY (
) TO stdout;
-- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-DROP VIEW tv;
-
-- materialized view
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
MERGE INTO mv t
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 7739b3b..bab4506
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -62,6 +62,15 @@ UPDATE ro_view10 SET a=a+1;
UPDATE ro_view11 SET a=a+1;
UPDATE ro_view12 SET a=a+1;
INSERT INTO ro_view13 VALUES (3, 'Row 3');
+MERGE INTO ro_view13 AS t USING (VALUES (1, 'Row 1')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DELETE;
+MERGE INTO ro_view13 AS t USING (VALUES (2, 'Row 2')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b;
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b);
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DO NOTHING
+ WHEN NOT MATCHED THEN DO NOTHING; -- should be OK to do nothing
-- Partially updatable view
INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
@@ -69,6 +78,20 @@ UPDATE rw_view14 SET ctid=null WHERE a=3
UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
SELECT * FROM base_tbl;
DELETE FROM rw_view14 WHERE a=3; -- should be OK
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK, except...
+ WHEN NOT MATCHED THEN INSERT VALUES (null, v.a, v.b); -- should fail
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK
+ WHEN NOT MATCHED THEN INSERT (a,b) VALUES (v.a, v.b); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+MERGE INTO rw_view14 AS t
+ USING (VALUES (2, 'Row 2'), (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED AND t.a = 2 THEN UPDATE SET b = v.b -- should be OK
+ WHEN MATCHED AND t.a = 3 THEN DELETE; -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
-- Partially updatable view
INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
@@ -114,6 +137,8 @@ CREATE RULE rw_view16_del_rule AS ON DEL
INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should fail
UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
DELETE FROM rw_view16 WHERE a=2; -- should fail
+MERGE INTO rw_view16 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
DROP TABLE base_tbl CASCADE;
DROP VIEW ro_view10, ro_view12, ro_view18;
@@ -145,9 +170,31 @@ UPDATE rw_view1 SET a=5 WHERE a=4;
DELETE FROM rw_view1 WHERE b='Row 2';
SELECT * FROM base_tbl;
+MERGE INTO rw_view1 t
+ USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
+ (2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
+ WHEN MATCHED THEN DELETE
+ WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+SELECT * FROM base_tbl ORDER BY a;
+
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t USING (VALUES (5, 'X')) AS v(a,b) ON t.a = v.a
+ WHEN MATCHED THEN DELETE;
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+ USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = 'Updated';
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+ USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+ WHEN NOT MATCHED THEN INSERT (a) VALUES (s.a);
+
-- it's still updatable if we add a DO ALSO rule
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -195,6 +242,13 @@ UPDATE rw_view2 SET bbb='Row 4' WHERE aa
DELETE FROM rw_view2 WHERE aaa=2;
SELECT * FROM rw_view2;
+MERGE INTO rw_view2 t
+ USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
+ WHEN MATCHED AND aaa = 3 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET bbb = v.b
+ WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+SELECT * FROM rw_view2 ORDER BY aaa;
+
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
@@ -283,6 +337,9 @@ SELECT * FROM rw_view2;
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
SELECT * FROM rw_view2;
+MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
@@ -397,9 +454,56 @@ SELECT * FROM rw_view2;
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
SELECT * FROM rw_view2;
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+SELECT * FROM base_tbl ORDER BY a;
+
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+
+-- MERGE with incomplete set of INSTEAD OF triggers
+DROP TRIGGER rw_view1_del_trig ON rw_view1;
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED AND t.a <= 1 THEN DELETE
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok
+
+DROP TRIGGER rw_view1_ins_trig ON rw_view1;
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+
+-- MERGE with INSTEAD OF triggers on auto-updatable view
+CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+ USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+ WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+SELECT * FROM base_tbl ORDER BY a;
+
DROP TABLE base_tbl CASCADE;
DROP FUNCTION rw_view1_trig_fn();
@@ -451,6 +555,13 @@ INSERT INTO base_tbl VALUES (3, 'Row 3',
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
+MERGE INTO rw_view1 t
+ USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+MERGE INTO rw_view2 t
+ USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+
UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
@@ -458,6 +569,15 @@ UPDATE rw_view1 SET aa=aa; -- not allowe
UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
UPDATE rw_view2 SET bb=bb; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+
DELETE FROM base_tbl; -- not allowed
DELETE FROM rw_view1; -- not allowed
DELETE FROM rw_view2; -- not allowed
@@ -474,6 +594,10 @@ INSERT INTO rw_view2 VALUES ('Row 4', 4.
DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
DELETE FROM rw_view2 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
SELECT * FROM base_tbl;
RESET SESSION AUTHORIZATION;
@@ -489,6 +613,10 @@ INSERT INTO rw_view2 VALUES ('Row 6', 6.
DELETE FROM base_tbl WHERE a=3; -- not allowed
DELETE FROM rw_view1 WHERE aa=3; -- ok
DELETE FROM rw_view2 WHERE aa=4; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
SELECT * FROM base_tbl;
RESET SESSION AUTHORIZATION;
@@ -504,12 +632,16 @@ CREATE VIEW rw_view1 AS SELECT * FROM ba
SELECT * FROM rw_view1; -- not allowed
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
SELECT * FROM rw_view2; -- not allowed
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user1;
@@ -518,11 +650,15 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view1;
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
@@ -531,6 +667,8 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view2;
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
RESET SESSION AUTHORIZATION;
GRANT UPDATE ON base_tbl TO regress_view_user1;
@@ -539,11 +677,15 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view1;
SELECT * FROM rw_view1 FOR UPDATE;
UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo';
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2;
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT UPDATE ON rw_view1 TO regress_view_user2;
@@ -552,6 +694,8 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view2;
SELECT * FROM rw_view2 FOR UPDATE;
UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'fud';
RESET SESSION AUTHORIZATION;
REVOKE UPDATE ON base_tbl FROM regress_view_user1;
@@ -560,11 +704,15 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view1;
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2;
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
RESET SESSION AUTHORIZATION;
@@ -588,8 +736,12 @@ INSERT INTO base_tbl VALUES (3, 'Row 3',
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
UPDATE base_tbl SET a=a; -- not allowed
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
DELETE FROM base_tbl; -- not allowed
DELETE FROM rw_view1; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON base_tbl TO regress_view_user2;
@@ -601,8 +753,14 @@ SELECT * FROM rw_view1; -- ok
UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
UPDATE rw_view1 SET cc=cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET cc = cc; -- ok
UPDATE rw_view1 SET aa=aa; -- not allowed
UPDATE rw_view1 SET bb=bb; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
@@ -612,6 +770,8 @@ INSERT INTO base_tbl VALUES (3, 'Row 3',
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
@@ -620,6 +780,8 @@ GRANT INSERT, DELETE ON rw_view1 TO regr
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
@@ -627,6 +789,8 @@ GRANT INSERT, DELETE ON base_tbl TO regr
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
DELETE FROM rw_view1 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN DELETE; -- ok
SELECT * FROM base_tbl; -- ok
RESET SESSION AUTHORIZATION;
@@ -643,12 +807,16 @@ CREATE VIEW rw_view1 AS SELECT b AS bb,
ALTER VIEW rw_view1 SET (security_invoker = true);
SELECT * FROM rw_view1; -- not allowed
UPDATE rw_view1 SET aa=aa; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed
RESET SESSION AUTHORIZATION;
@@ -659,14 +827,22 @@ SET SESSION AUTHORIZATION regress_view_u
SELECT * FROM rw_view1; -- ok
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
UPDATE rw_view1 SET cc=cc; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
@@ -675,10 +851,14 @@ GRANT UPDATE (bb, cc) ON rw_view1 TO reg
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
RESET SESSION AUTHORIZATION;
@@ -690,12 +870,24 @@ SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
RESET SESSION AUTHORIZATION;
@@ -707,6 +899,12 @@ SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
@@ -715,18 +913,32 @@ REVOKE SELECT, UPDATE ON base_tbl FROM r
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1; -- not allowed
UPDATE rw_view1 SET aa=aa; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+ WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
@@ -737,12 +949,24 @@ SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+ WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
RESET SESSION AUTHORIZATION;
@@ -764,6 +988,8 @@ ALTER VIEW rw_view1 ALTER COLUMN bb SET
INSERT INTO rw_view1 VALUES (4, 'Row 4');
INSERT INTO rw_view1 (aa) VALUES (5);
+MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a
+ WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a);
SELECT * FROM base_tbl;
@@ -945,6 +1171,18 @@ DELETE FROM ONLY rw_view2 WHERE a IN (-8
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
SELECT * FROM base_tbl_child ORDER BY a;
+MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11
+MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21
+MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only
+MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only
+
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+SELECT * FROM base_tbl_child ORDER BY a;
+
CREATE TABLE other_tbl_parent (id int);
CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
INSERT INTO other_tbl_parent VALUES (7),(200);
@@ -977,7 +1215,17 @@ UPDATE rw_view1 SET b = 5 WHERE a = 3; -
UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
INSERT INTO rw_view1(a) VALUES (9); -- ok
INSERT INTO rw_view1(a) VALUES (10); -- should fail
-SELECT * FROM base_tbl;
+SELECT * FROM base_tbl ORDER BY a, b;
+
+MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok
+MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok
+MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a
+ WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail
+SELECT * FROM base_tbl ORDER BY a, b;
DROP TABLE base_tbl CASCADE;
@@ -1139,8 +1387,14 @@ CREATE VIEW rw_view2 AS
SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
INSERT INTO rw_view2 VALUES (-5); -- should fail
+MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail
INSERT INTO rw_view2 VALUES (5); -- ok
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok
INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view
UPDATE rw_view2 SET a = a - 10; -- should fail
SELECT * FROM base_tbl;
@@ -1463,6 +1717,19 @@ insert into uv_ptv values (1, 2);
select tableoid::regclass, * from uv_pt;
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
insert into uv_ptv_wco values (1, 2);
+merge into uv_ptv t
+ using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1);
+merge into uv_ptv t
+ using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5
+merge into uv_ptv t
+ using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b
+ when matched then update set b = t.b + 1
+ when not matched then insert values (v.a, v.b + 1); -- ok
+select tableoid::regclass, * from uv_pt order by a, b;
drop view uv_ptv, uv_ptv_wco;
drop table uv_pt, uv_pt1, uv_pt11;