mirror of
https://github.com/postgres/postgres.git
synced 2025-05-14 00:03:46 -04:00
Revert "Modified files for MERGE"
This reverts commit 354f13855e6381d288dfaa52bcd4f2cb0fd4a5eb.
This commit is contained in:
parent
354f13855e
commit
7cf8a5c302
@ -192,52 +192,6 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
|
|||||||
COMMIT
|
COMMIT
|
||||||
(33 rows)
|
(33 rows)
|
||||||
|
|
||||||
-- MERGE support
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO replication_example t
|
|
||||||
USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
|
|
||||||
ON t.id = s.id
|
|
||||||
WHEN MATCHED AND t.id < 0 THEN
|
|
||||||
UPDATE SET somenum = somenum + 1
|
|
||||||
WHEN MATCHED AND t.id >= 0 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.*);
|
|
||||||
COMMIT;
|
|
||||||
/* display results */
|
|
||||||
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
|
|
||||||
data
|
|
||||||
--------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
BEGIN
|
|
||||||
table public.replication_example: INSERT: id[integer]:-20 somedata[integer]:-20 somenum[integer]:-20 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: INSERT: id[integer]:-19 somedata[integer]:-19 somenum[integer]:-19 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: INSERT: id[integer]:-18 somedata[integer]:-18 somenum[integer]:-18 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: INSERT: id[integer]:-17 somedata[integer]:-17 somenum[integer]:-17 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: INSERT: id[integer]:-16 somedata[integer]:-16 somenum[integer]:-16 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-15 somedata[integer]:-15 somenum[integer]:-14 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-14 somedata[integer]:-14 somenum[integer]:-13 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-13 somedata[integer]:-13 somenum[integer]:-12 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-12 somedata[integer]:-12 somenum[integer]:-11 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-11 somedata[integer]:-11 somenum[integer]:-10 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-10 somedata[integer]:-10 somenum[integer]:-9 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-9 somedata[integer]:-9 somenum[integer]:-8 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-8 somedata[integer]:-8 somenum[integer]:-7 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-7 somedata[integer]:-7 somenum[integer]:-6 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-6 somedata[integer]:-6 somenum[integer]:-5 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-5 somedata[integer]:-5 somenum[integer]:-4 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-4 somedata[integer]:-4 somenum[integer]:-3 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-3 somedata[integer]:-3 somenum[integer]:-2 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-2 somedata[integer]:-2 somenum[integer]:-1 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: UPDATE: id[integer]:-1 somedata[integer]:-1 somenum[integer]:0 zaphod1[integer]:null zaphod2[integer]:null
|
|
||||||
table public.replication_example: DELETE: id[integer]:0
|
|
||||||
table public.replication_example: DELETE: id[integer]:1
|
|
||||||
table public.replication_example: DELETE: id[integer]:2
|
|
||||||
table public.replication_example: DELETE: id[integer]:3
|
|
||||||
table public.replication_example: DELETE: id[integer]:4
|
|
||||||
table public.replication_example: DELETE: id[integer]:5
|
|
||||||
COMMIT
|
|
||||||
(28 rows)
|
|
||||||
|
|
||||||
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
|
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
|
||||||
INSERT INTO tr_unique(data) VALUES(10);
|
INSERT INTO tr_unique(data) VALUES(10);
|
||||||
ALTER TABLE tr_unique RENAME TO tr_pkey;
|
ALTER TABLE tr_unique RENAME TO tr_pkey;
|
||||||
|
@ -93,22 +93,6 @@ COMMIT;
|
|||||||
/* display results */
|
/* display results */
|
||||||
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
|
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
|
||||||
|
|
||||||
-- MERGE support
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO replication_example t
|
|
||||||
USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
|
|
||||||
ON t.id = s.id
|
|
||||||
WHEN MATCHED AND t.id < 0 THEN
|
|
||||||
UPDATE SET somenum = somenum + 1
|
|
||||||
WHEN MATCHED AND t.id >= 0 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.*);
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
/* display results */
|
|
||||||
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
|
|
||||||
|
|
||||||
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
|
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
|
||||||
INSERT INTO tr_unique(data) VALUES(10);
|
INSERT INTO tr_unique(data) VALUES(10);
|
||||||
ALTER TABLE tr_unique RENAME TO tr_pkey;
|
ALTER TABLE tr_unique RENAME TO tr_pkey;
|
||||||
|
@ -3917,11 +3917,9 @@ char *PQcmdTuples(PGresult *res);
|
|||||||
<structname>PGresult</structname>. This function can only be used following
|
<structname>PGresult</structname>. This function can only be used following
|
||||||
the execution of a <command>SELECT</command>, <command>CREATE TABLE AS</command>,
|
the execution of a <command>SELECT</command>, <command>CREATE TABLE AS</command>,
|
||||||
<command>INSERT</command>, <command>UPDATE</command>, <command>DELETE</command>,
|
<command>INSERT</command>, <command>UPDATE</command>, <command>DELETE</command>,
|
||||||
<command>MERGE</command>, <command>MOVE</command>, <command>FETCH</command>,
|
<command>MOVE</command>, <command>FETCH</command>, or <command>COPY</command> statement,
|
||||||
or <command>COPY</command> statement, or an <command>EXECUTE</command> of a
|
or an <command>EXECUTE</command> of a prepared query that contains an
|
||||||
prepared query that contains an <command>INSERT</command>,
|
<command>INSERT</command>, <command>UPDATE</command>, or <command>DELETE</command> statement.
|
||||||
<command>UPDATE</command>, <command>DELETE</command>
|
|
||||||
or <command>MERGE</command> statement.
|
|
||||||
If the command that generated the <structname>PGresult</structname> was anything
|
If the command that generated the <structname>PGresult</structname> was anything
|
||||||
else, <function>PQcmdTuples</function> returns an empty string. The caller
|
else, <function>PQcmdTuples</function> returns an empty string. The caller
|
||||||
should not free the return value directly. It will be freed when
|
should not free the return value directly. It will be freed when
|
||||||
|
@ -422,31 +422,6 @@ COMMIT;
|
|||||||
<literal>11</literal>, which no longer matches the criteria.
|
<literal>11</literal>, which no longer matches the criteria.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
The <command>MERGE</command> allows the user to specify various combinations
|
|
||||||
of <command>INSERT</command>, <command>UPDATE</command> or
|
|
||||||
<command>DELETE</command> subcommands. A <command>MERGE</command> command
|
|
||||||
with both <command>INSERT</command> and <command>UPDATE</command>
|
|
||||||
subcommands looks similar to <command>INSERT</command> with an
|
|
||||||
<literal>ON CONFLICT DO UPDATE</literal> clause but does not guarantee
|
|
||||||
that either <command>INSERT</command> and <command>UPDATE</command> will occur.
|
|
||||||
|
|
||||||
If MERGE attempts an UPDATE or DELETE and the row is concurrently updated
|
|
||||||
but the join condition still passes for the current target and the current
|
|
||||||
source tuple, then MERGE will behave the same as the UPDATE or DELETE commands
|
|
||||||
and perform its action on the latest version of the row, using standard
|
|
||||||
EvalPlanQual. MERGE actions can be conditional, so conditions must be
|
|
||||||
re-evaluated on the latest row, starting from the first action.
|
|
||||||
|
|
||||||
On the other hand, if the row is concurrently updated or deleted so that
|
|
||||||
the join condition fails, then MERGE will execute a NOT MATCHED action, if it
|
|
||||||
exists and the AND WHEN qual evaluates to true.
|
|
||||||
|
|
||||||
If MERGE attempts an INSERT and a unique index is present and a duplicate
|
|
||||||
row is concurrently inserted then a uniqueness violation is raised. MERGE
|
|
||||||
does not attempt to avoid the ERROR by attempting an UPDATE.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Because Read Committed mode starts each command with a new snapshot
|
Because Read Committed mode starts each command with a new snapshot
|
||||||
that includes all transactions committed up to that instant,
|
that includes all transactions committed up to that instant,
|
||||||
@ -925,8 +900,7 @@ ERROR: could not serialize access due to read/write dependencies among transact
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
The commands <command>UPDATE</command>,
|
The commands <command>UPDATE</command>,
|
||||||
<command>DELETE</command>, <command>INSERT</command> and
|
<command>DELETE</command>, and <command>INSERT</command>
|
||||||
<command>MERGE</command>
|
|
||||||
acquire this lock mode on the target table (in addition to
|
acquire this lock mode on the target table (in addition to
|
||||||
<literal>ACCESS SHARE</literal> locks on any other referenced
|
<literal>ACCESS SHARE</literal> locks on any other referenced
|
||||||
tables). In general, this lock mode will be acquired by any
|
tables). In general, this lock mode will be acquired by any
|
||||||
|
@ -1246,7 +1246,7 @@ EXECUTE format('SELECT count(*) FROM %I '
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
Another restriction on parameter symbols is that they only work in
|
Another restriction on parameter symbols is that they only work in
|
||||||
<command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>, and
|
<command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>, and
|
||||||
<command>DELETE</command> and <command>MERGE</command> commands. In other statement
|
<command>DELETE</command> commands. In other statement
|
||||||
types (generically called utility statements), you must insert
|
types (generically called utility statements), you must insert
|
||||||
values textually even if they are just data values.
|
values textually even if they are just data values.
|
||||||
</para>
|
</para>
|
||||||
@ -1529,7 +1529,6 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<command>UPDATE</command>, <command>INSERT</command>, and <command>DELETE</command>
|
<command>UPDATE</command>, <command>INSERT</command>, and <command>DELETE</command>
|
||||||
and <command>MERGE</command>
|
|
||||||
statements set <literal>FOUND</literal> true if at least one
|
statements set <literal>FOUND</literal> true if at least one
|
||||||
row is affected, false if no row is affected.
|
row is affected, false if no row is affected.
|
||||||
</para>
|
</para>
|
||||||
|
@ -159,7 +159,6 @@ Complete list of usable sgml source files in this directory.
|
|||||||
<!ENTITY load SYSTEM "load.sgml">
|
<!ENTITY load SYSTEM "load.sgml">
|
||||||
<!ENTITY lock SYSTEM "lock.sgml">
|
<!ENTITY lock SYSTEM "lock.sgml">
|
||||||
<!ENTITY move SYSTEM "move.sgml">
|
<!ENTITY move SYSTEM "move.sgml">
|
||||||
<!ENTITY merge SYSTEM "merge.sgml">
|
|
||||||
<!ENTITY notify SYSTEM "notify.sgml">
|
<!ENTITY notify SYSTEM "notify.sgml">
|
||||||
<!ENTITY prepare SYSTEM "prepare.sgml">
|
<!ENTITY prepare SYSTEM "prepare.sgml">
|
||||||
<!ENTITY prepareTransaction SYSTEM "prepare_transaction.sgml">
|
<!ENTITY prepareTransaction SYSTEM "prepare_transaction.sgml">
|
||||||
|
@ -94,13 +94,6 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
|
|||||||
exist, a <quote>default deny</quote> policy is assumed, so that no rows will
|
exist, a <quote>default deny</quote> policy is assumed, so that no rows will
|
||||||
be visible or updatable.
|
be visible or updatable.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
No separate policy exists for <command>MERGE</command>. Instead policies
|
|
||||||
defined for <literal>SELECT</literal>, <literal>INSERT</literal>,
|
|
||||||
<literal>UPDATE</literal> and <literal>DELETE</literal> are applied
|
|
||||||
while executing MERGE, depending on the actions that are activated.
|
|
||||||
</para>
|
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
|
@ -579,13 +579,6 @@ INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</repl
|
|||||||
is a partition, an error will occur if one of the input rows violates
|
is a partition, an error will occur if one of the input rows violates
|
||||||
the partition constraint.
|
the partition constraint.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
You may also wish to consider using <command>MERGE</command>, since that
|
|
||||||
allows mixed <command>INSERT</command>, <command>UPDATE</command> and
|
|
||||||
<command>DELETE</command> within a single statement.
|
|
||||||
See <xref linkend="sql-merge"/>.
|
|
||||||
</para>
|
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
@ -756,9 +749,7 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
|
|||||||
Also, the case in
|
Also, the case in
|
||||||
which a column name list is omitted, but not all the columns are
|
which a column name list is omitted, but not all the columns are
|
||||||
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
|
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
|
||||||
is disallowed by the standard. If you prefer a more SQL Standard
|
is disallowed by the standard.
|
||||||
conforming statement than <literal>ON CONFLICT</literal>, see
|
|
||||||
<xref linkend="sql-merge"/>.
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -186,7 +186,6 @@
|
|||||||
&listen;
|
&listen;
|
||||||
&load;
|
&load;
|
||||||
&lock;
|
&lock;
|
||||||
&merge;
|
|
||||||
&move;
|
&move;
|
||||||
¬ify;
|
¬ify;
|
||||||
&prepare;
|
&prepare;
|
||||||
|
@ -182,26 +182,6 @@
|
|||||||
will be fired.
|
will be fired.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
No separate triggers are defined for <command>MERGE</command>. Instead,
|
|
||||||
statement-level or row-level <command>UPDATE</command>,
|
|
||||||
<command>DELETE</command> and <command>INSERT</command> triggers are fired
|
|
||||||
depending on what actions are specified in the <command>MERGE</command> query
|
|
||||||
and what actions are activated.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
While running a <command>MERGE</command> command, statement-level
|
|
||||||
<literal>BEFORE</literal> and <literal>AFTER</literal> triggers are fired for
|
|
||||||
events specified in the actions of the <command>MERGE</command> command,
|
|
||||||
irrespective of whether the action is finally activated or not. This is same as
|
|
||||||
an <command>UPDATE</command> statement that updates no rows, yet
|
|
||||||
statement-level triggers are fired. The row-level triggers are fired only
|
|
||||||
when a row is actually updated, inserted or deleted. So it's perfectly legal
|
|
||||||
that while statement-level triggers are fired for certain type of action, no
|
|
||||||
row-level triggers are fired for the same kind of action.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Trigger functions invoked by per-statement triggers should always
|
Trigger functions invoked by per-statement triggers should always
|
||||||
return <symbol>NULL</symbol>. Trigger functions invoked by per-row
|
return <symbol>NULL</symbol>. Trigger functions invoked by per-row
|
||||||
|
@ -3245,7 +3245,6 @@ l1:
|
|||||||
result == HeapTupleUpdated ||
|
result == HeapTupleUpdated ||
|
||||||
result == HeapTupleBeingUpdated);
|
result == HeapTupleBeingUpdated);
|
||||||
Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
|
Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
|
||||||
hufd->result = result;
|
|
||||||
hufd->ctid = tp.t_data->t_ctid;
|
hufd->ctid = tp.t_data->t_ctid;
|
||||||
hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
|
hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
|
||||||
if (result == HeapTupleSelfUpdated)
|
if (result == HeapTupleSelfUpdated)
|
||||||
@ -3508,7 +3507,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
|
|||||||
HTSU_Result
|
HTSU_Result
|
||||||
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
||||||
CommandId cid, Snapshot crosscheck, bool wait,
|
CommandId cid, Snapshot crosscheck, bool wait,
|
||||||
HeapUpdateFailureData *hufd)
|
HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
|
||||||
{
|
{
|
||||||
HTSU_Result result;
|
HTSU_Result result;
|
||||||
TransactionId xid = GetCurrentTransactionId();
|
TransactionId xid = GetCurrentTransactionId();
|
||||||
@ -3548,10 +3547,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
|||||||
infomask2_old_tuple,
|
infomask2_old_tuple,
|
||||||
infomask_new_tuple,
|
infomask_new_tuple,
|
||||||
infomask2_new_tuple;
|
infomask2_new_tuple;
|
||||||
LockTupleMode lockmode;
|
|
||||||
|
|
||||||
Assert(ItemPointerIsValid(otid));
|
Assert(ItemPointerIsValid(otid));
|
||||||
Assert(hufd != NULL);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Forbid this during a parallel operation, lest it allocate a combocid.
|
* Forbid this during a parallel operation, lest it allocate a combocid.
|
||||||
@ -3667,7 +3664,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
|||||||
*/
|
*/
|
||||||
if (!bms_overlap(modified_attrs, key_attrs))
|
if (!bms_overlap(modified_attrs, key_attrs))
|
||||||
{
|
{
|
||||||
lockmode = hufd->lockmode = LockTupleNoKeyExclusive;
|
*lockmode = LockTupleNoKeyExclusive;
|
||||||
mxact_status = MultiXactStatusNoKeyUpdate;
|
mxact_status = MultiXactStatusNoKeyUpdate;
|
||||||
key_intact = true;
|
key_intact = true;
|
||||||
|
|
||||||
@ -3684,7 +3681,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lockmode = hufd->lockmode = LockTupleExclusive;
|
*lockmode = LockTupleExclusive;
|
||||||
mxact_status = MultiXactStatusUpdate;
|
mxact_status = MultiXactStatusUpdate;
|
||||||
key_intact = false;
|
key_intact = false;
|
||||||
}
|
}
|
||||||
@ -3762,12 +3759,12 @@ l2:
|
|||||||
int remain;
|
int remain;
|
||||||
|
|
||||||
if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
|
if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
|
||||||
lockmode))
|
*lockmode))
|
||||||
{
|
{
|
||||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||||
|
|
||||||
/* acquire tuple lock, if necessary */
|
/* acquire tuple lock, if necessary */
|
||||||
heap_acquire_tuplock(relation, &(oldtup.t_self), lockmode,
|
heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
|
||||||
LockWaitBlock, &have_tuple_lock);
|
LockWaitBlock, &have_tuple_lock);
|
||||||
|
|
||||||
/* wait for multixact */
|
/* wait for multixact */
|
||||||
@ -3851,7 +3848,7 @@ l2:
|
|||||||
* lock.
|
* lock.
|
||||||
*/
|
*/
|
||||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||||
heap_acquire_tuplock(relation, &(oldtup.t_self), lockmode,
|
heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
|
||||||
LockWaitBlock, &have_tuple_lock);
|
LockWaitBlock, &have_tuple_lock);
|
||||||
XactLockTableWait(xwait, relation, &oldtup.t_self,
|
XactLockTableWait(xwait, relation, &oldtup.t_self,
|
||||||
XLTW_Update);
|
XLTW_Update);
|
||||||
@ -3890,7 +3887,6 @@ l2:
|
|||||||
result == HeapTupleUpdated ||
|
result == HeapTupleUpdated ||
|
||||||
result == HeapTupleBeingUpdated);
|
result == HeapTupleBeingUpdated);
|
||||||
Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
|
Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
|
||||||
hufd->result = result;
|
|
||||||
hufd->ctid = oldtup.t_data->t_ctid;
|
hufd->ctid = oldtup.t_data->t_ctid;
|
||||||
hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
|
hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
|
||||||
if (result == HeapTupleSelfUpdated)
|
if (result == HeapTupleSelfUpdated)
|
||||||
@ -3899,7 +3895,7 @@ l2:
|
|||||||
hufd->cmax = InvalidCommandId;
|
hufd->cmax = InvalidCommandId;
|
||||||
UnlockReleaseBuffer(buffer);
|
UnlockReleaseBuffer(buffer);
|
||||||
if (have_tuple_lock)
|
if (have_tuple_lock)
|
||||||
UnlockTupleTuplock(relation, &(oldtup.t_self), lockmode);
|
UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
|
||||||
if (vmbuffer != InvalidBuffer)
|
if (vmbuffer != InvalidBuffer)
|
||||||
ReleaseBuffer(vmbuffer);
|
ReleaseBuffer(vmbuffer);
|
||||||
bms_free(hot_attrs);
|
bms_free(hot_attrs);
|
||||||
@ -3937,7 +3933,7 @@ l2:
|
|||||||
compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
|
compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
|
||||||
oldtup.t_data->t_infomask,
|
oldtup.t_data->t_infomask,
|
||||||
oldtup.t_data->t_infomask2,
|
oldtup.t_data->t_infomask2,
|
||||||
xid, lockmode, true,
|
xid, *lockmode, true,
|
||||||
&xmax_old_tuple, &infomask_old_tuple,
|
&xmax_old_tuple, &infomask_old_tuple,
|
||||||
&infomask2_old_tuple);
|
&infomask2_old_tuple);
|
||||||
|
|
||||||
@ -4054,7 +4050,7 @@ l2:
|
|||||||
compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
|
compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
|
||||||
oldtup.t_data->t_infomask,
|
oldtup.t_data->t_infomask,
|
||||||
oldtup.t_data->t_infomask2,
|
oldtup.t_data->t_infomask2,
|
||||||
xid, lockmode, false,
|
xid, *lockmode, false,
|
||||||
&xmax_lock_old_tuple, &infomask_lock_old_tuple,
|
&xmax_lock_old_tuple, &infomask_lock_old_tuple,
|
||||||
&infomask2_lock_old_tuple);
|
&infomask2_lock_old_tuple);
|
||||||
|
|
||||||
@ -4366,7 +4362,7 @@ l2:
|
|||||||
* Release the lmgr tuple lock, if we had it.
|
* Release the lmgr tuple lock, if we had it.
|
||||||
*/
|
*/
|
||||||
if (have_tuple_lock)
|
if (have_tuple_lock)
|
||||||
UnlockTupleTuplock(relation, &(oldtup.t_self), lockmode);
|
UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
|
||||||
|
|
||||||
pgstat_count_heap_update(relation, use_hot_update);
|
pgstat_count_heap_update(relation, use_hot_update);
|
||||||
|
|
||||||
@ -4590,11 +4586,12 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
|
|||||||
{
|
{
|
||||||
HTSU_Result result;
|
HTSU_Result result;
|
||||||
HeapUpdateFailureData hufd;
|
HeapUpdateFailureData hufd;
|
||||||
|
LockTupleMode lockmode;
|
||||||
|
|
||||||
result = heap_update(relation, otid, tup,
|
result = heap_update(relation, otid, tup,
|
||||||
GetCurrentCommandId(true), InvalidSnapshot,
|
GetCurrentCommandId(true), InvalidSnapshot,
|
||||||
true /* wait for commit */ ,
|
true /* wait for commit */ ,
|
||||||
&hufd);
|
&hufd, &lockmode);
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HeapTupleSelfUpdated:
|
case HeapTupleSelfUpdated:
|
||||||
@ -5180,7 +5177,6 @@ failed:
|
|||||||
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
|
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
|
||||||
result == HeapTupleWouldBlock);
|
result == HeapTupleWouldBlock);
|
||||||
Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
|
Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
|
||||||
hufd->result = result;
|
|
||||||
hufd->ctid = tuple->t_data->t_ctid;
|
hufd->ctid = tuple->t_data->t_ctid;
|
||||||
hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
|
hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
|
||||||
if (result == HeapTupleSelfUpdated)
|
if (result == HeapTupleSelfUpdated)
|
||||||
|
@ -229,9 +229,9 @@ F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
|
|||||||
F311 Schema definition statement 03 CREATE VIEW YES
|
F311 Schema definition statement 03 CREATE VIEW YES
|
||||||
F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
|
F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
|
||||||
F311 Schema definition statement 05 GRANT statement YES
|
F311 Schema definition statement 05 GRANT statement YES
|
||||||
F312 MERGE statement YES also consider INSERT ... ON CONFLICT DO UPDATE
|
F312 MERGE statement NO consider INSERT ... ON CONFLICT DO UPDATE
|
||||||
F313 Enhanced MERGE statement YES
|
F313 Enhanced MERGE statement NO
|
||||||
F314 MERGE statement with DELETE branch YES
|
F314 MERGE statement with DELETE branch NO
|
||||||
F321 User authorization YES
|
F321 User authorization YES
|
||||||
F341 Usage tables NO no ROUTINE_*_USAGE tables
|
F341 Usage tables NO no ROUTINE_*_USAGE tables
|
||||||
F361 Subprogram support YES
|
F361 Subprogram support YES
|
||||||
|
@ -946,9 +946,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
|
|||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
pname = operation = "Delete";
|
pname = operation = "Delete";
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
pname = operation = "Merge";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
pname = "???";
|
pname = "???";
|
||||||
break;
|
break;
|
||||||
@ -3010,10 +3007,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
|
|||||||
operation = "Delete";
|
operation = "Delete";
|
||||||
foperation = "Foreign Delete";
|
foperation = "Foreign Delete";
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
operation = "Merge";
|
|
||||||
foperation = "Foreign Merge";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
operation = "???";
|
operation = "???";
|
||||||
foperation = "Foreign ???";
|
foperation = "Foreign ???";
|
||||||
@ -3136,32 +3129,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
|
|||||||
other_path, 0, es);
|
other_path, 0, es);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (node->operation == CMD_MERGE)
|
|
||||||
{
|
|
||||||
/* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
|
|
||||||
if (es->analyze && mtstate->ps.instrument)
|
|
||||||
{
|
|
||||||
double total;
|
|
||||||
double insert_path;
|
|
||||||
double update_path;
|
|
||||||
double delete_path;
|
|
||||||
double skipped_path;
|
|
||||||
|
|
||||||
InstrEndLoop(mtstate->mt_plans[0]->instrument);
|
|
||||||
|
|
||||||
/* count the number of source rows */
|
|
||||||
total = mtstate->mt_plans[0]->instrument->ntuples;
|
|
||||||
insert_path = mtstate->ps.instrument->nfiltered1;
|
|
||||||
update_path = mtstate->ps.instrument->nfiltered2;
|
|
||||||
delete_path = mtstate->ps.instrument->nfiltered3;
|
|
||||||
skipped_path = total - insert_path - update_path - delete_path;
|
|
||||||
|
|
||||||
ExplainPropertyFloat("Tuples Inserted", NULL, insert_path, 0, es);
|
|
||||||
ExplainPropertyFloat("Tuples Updated", NULL, update_path, 0, es);
|
|
||||||
ExplainPropertyFloat("Tuples Deleted", NULL, delete_path, 0, es);
|
|
||||||
ExplainPropertyFloat("Tuples Skipped", NULL, skipped_path, 0, es);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labeltargets)
|
if (labeltargets)
|
||||||
ExplainCloseGroup("Target Tables", "Target Tables", false, es);
|
ExplainCloseGroup("Target Tables", "Target Tables", false, es);
|
||||||
|
@ -151,7 +151,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
|
|||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_MERGE:
|
|
||||||
/* OK */
|
/* OK */
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -85,8 +85,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
|
|||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tid,
|
ItemPointer tid,
|
||||||
LockTupleMode lockmode,
|
LockTupleMode lockmode,
|
||||||
TupleTableSlot **newSlot,
|
TupleTableSlot **newSlot);
|
||||||
HeapUpdateFailureData *hufdp);
|
|
||||||
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
|
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
|
||||||
Trigger *trigger, TriggerEvent event,
|
Trigger *trigger, TriggerEvent event,
|
||||||
Bitmapset *modifiedCols,
|
Bitmapset *modifiedCols,
|
||||||
@ -2730,8 +2729,7 @@ bool
|
|||||||
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
|
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple fdw_trigtuple,
|
HeapTuple fdw_trigtuple)
|
||||||
HeapUpdateFailureData *hufdp)
|
|
||||||
{
|
{
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||||
bool result = true;
|
bool result = true;
|
||||||
@ -2745,7 +2743,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
|
|||||||
if (fdw_trigtuple == NULL)
|
if (fdw_trigtuple == NULL)
|
||||||
{
|
{
|
||||||
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
|
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
|
||||||
LockTupleExclusive, &newSlot, hufdp);
|
LockTupleExclusive, &newSlot);
|
||||||
if (trigtuple == NULL)
|
if (trigtuple == NULL)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2816,7 +2814,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
relinfo,
|
relinfo,
|
||||||
tupleid,
|
tupleid,
|
||||||
LockTupleExclusive,
|
LockTupleExclusive,
|
||||||
NULL,
|
|
||||||
NULL);
|
NULL);
|
||||||
else
|
else
|
||||||
trigtuple = fdw_trigtuple;
|
trigtuple = fdw_trigtuple;
|
||||||
@ -2954,8 +2951,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
|
|||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple fdw_trigtuple,
|
HeapTuple fdw_trigtuple,
|
||||||
TupleTableSlot *slot,
|
TupleTableSlot *slot)
|
||||||
HeapUpdateFailureData *hufdp)
|
|
||||||
{
|
{
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||||
HeapTuple slottuple = ExecMaterializeSlot(slot);
|
HeapTuple slottuple = ExecMaterializeSlot(slot);
|
||||||
@ -2976,7 +2972,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
|
|||||||
{
|
{
|
||||||
/* get a copy of the on-disk tuple we are planning to update */
|
/* get a copy of the on-disk tuple we are planning to update */
|
||||||
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
|
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
|
||||||
lockmode, &newSlot, hufdp);
|
lockmode, &newSlot);
|
||||||
if (trigtuple == NULL)
|
if (trigtuple == NULL)
|
||||||
return NULL; /* cancel the update action */
|
return NULL; /* cancel the update action */
|
||||||
}
|
}
|
||||||
@ -3096,7 +3092,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
relinfo,
|
relinfo,
|
||||||
tupleid,
|
tupleid,
|
||||||
LockTupleExclusive,
|
LockTupleExclusive,
|
||||||
NULL,
|
|
||||||
NULL);
|
NULL);
|
||||||
else
|
else
|
||||||
trigtuple = fdw_trigtuple;
|
trigtuple = fdw_trigtuple;
|
||||||
@ -3245,8 +3240,7 @@ GetTupleForTrigger(EState *estate,
|
|||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tid,
|
ItemPointer tid,
|
||||||
LockTupleMode lockmode,
|
LockTupleMode lockmode,
|
||||||
TupleTableSlot **newSlot,
|
TupleTableSlot **newSlot)
|
||||||
HeapUpdateFailureData *hufdp)
|
|
||||||
{
|
{
|
||||||
Relation relation = relinfo->ri_RelationDesc;
|
Relation relation = relinfo->ri_RelationDesc;
|
||||||
HeapTupleData tuple;
|
HeapTupleData tuple;
|
||||||
@ -3272,11 +3266,6 @@ ltrmark:;
|
|||||||
estate->es_output_cid,
|
estate->es_output_cid,
|
||||||
lockmode, LockWaitBlock,
|
lockmode, LockWaitBlock,
|
||||||
false, &buffer, &hufd);
|
false, &buffer, &hufd);
|
||||||
|
|
||||||
/* Let the caller know about failure reason, if any. */
|
|
||||||
if (hufdp)
|
|
||||||
*hufdp = hufd;
|
|
||||||
|
|
||||||
switch (test)
|
switch (test)
|
||||||
{
|
{
|
||||||
case HeapTupleSelfUpdated:
|
case HeapTupleSelfUpdated:
|
||||||
@ -3313,17 +3302,10 @@ ltrmark:;
|
|||||||
/* it was updated, so look at the updated version */
|
/* it was updated, so look at the updated version */
|
||||||
TupleTableSlot *epqslot;
|
TupleTableSlot *epqslot;
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're running MERGE then we must install the
|
|
||||||
* new tuple in the slot of the underlying join query and
|
|
||||||
* not the result relation itself. If the join does not
|
|
||||||
* yield any tuple, the caller will take the necessary
|
|
||||||
* action.
|
|
||||||
*/
|
|
||||||
epqslot = EvalPlanQual(estate,
|
epqslot = EvalPlanQual(estate,
|
||||||
epqstate,
|
epqstate,
|
||||||
relation,
|
relation,
|
||||||
GetEPQRangeTableIndex(relinfo),
|
relinfo->ri_RangeTableIndex,
|
||||||
lockmode,
|
lockmode,
|
||||||
&hufd.ctid,
|
&hufd.ctid,
|
||||||
hufd.xmax);
|
hufd.xmax);
|
||||||
@ -3846,14 +3828,8 @@ struct AfterTriggersTableData
|
|||||||
bool before_trig_done; /* did we already queue BS triggers? */
|
bool before_trig_done; /* did we already queue BS triggers? */
|
||||||
bool after_trig_done; /* did we already queue AS triggers? */
|
bool after_trig_done; /* did we already queue AS triggers? */
|
||||||
AfterTriggerEventList after_trig_events; /* if so, saved list pointer */
|
AfterTriggerEventList after_trig_events; /* if so, saved list pointer */
|
||||||
/* "old" transition table for UPDATE, if any */
|
Tuplestorestate *old_tuplestore; /* "old" transition table, if any */
|
||||||
Tuplestorestate *old_upd_tuplestore;
|
Tuplestorestate *new_tuplestore; /* "new" transition table, if any */
|
||||||
/* "new" transition table for UPDATE, if any */
|
|
||||||
Tuplestorestate *new_upd_tuplestore;
|
|
||||||
/* "old" transition table for DELETE, if any */
|
|
||||||
Tuplestorestate *old_del_tuplestore;
|
|
||||||
/* "new" transition table INSERT, if any */
|
|
||||||
Tuplestorestate *new_ins_tuplestore;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static AfterTriggersData afterTriggers;
|
static AfterTriggersData afterTriggers;
|
||||||
@ -4320,19 +4296,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
|
|||||||
{
|
{
|
||||||
if (LocTriggerData.tg_trigger->tgoldtable)
|
if (LocTriggerData.tg_trigger->tgoldtable)
|
||||||
{
|
{
|
||||||
if (TRIGGER_FIRED_BY_UPDATE(evtshared->ats_event))
|
LocTriggerData.tg_oldtable = evtshared->ats_table->old_tuplestore;
|
||||||
LocTriggerData.tg_oldtable = evtshared->ats_table->old_upd_tuplestore;
|
|
||||||
else
|
|
||||||
LocTriggerData.tg_oldtable = evtshared->ats_table->old_del_tuplestore;
|
|
||||||
evtshared->ats_table->closed = true;
|
evtshared->ats_table->closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LocTriggerData.tg_trigger->tgnewtable)
|
if (LocTriggerData.tg_trigger->tgnewtable)
|
||||||
{
|
{
|
||||||
if (TRIGGER_FIRED_BY_INSERT(evtshared->ats_event))
|
LocTriggerData.tg_newtable = evtshared->ats_table->new_tuplestore;
|
||||||
LocTriggerData.tg_newtable = evtshared->ats_table->new_ins_tuplestore;
|
|
||||||
else
|
|
||||||
LocTriggerData.tg_newtable = evtshared->ats_table->new_upd_tuplestore;
|
|
||||||
evtshared->ats_table->closed = true;
|
evtshared->ats_table->closed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4667,10 +4637,8 @@ TransitionCaptureState *
|
|||||||
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
|
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
|
||||||
{
|
{
|
||||||
TransitionCaptureState *state;
|
TransitionCaptureState *state;
|
||||||
bool need_old_upd,
|
bool need_old,
|
||||||
need_new_upd,
|
need_new;
|
||||||
need_old_del,
|
|
||||||
need_new_ins;
|
|
||||||
AfterTriggersTableData *table;
|
AfterTriggersTableData *table;
|
||||||
MemoryContext oldcxt;
|
MemoryContext oldcxt;
|
||||||
ResourceOwner saveResourceOwner;
|
ResourceOwner saveResourceOwner;
|
||||||
@ -4682,31 +4650,23 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
|
|||||||
switch (cmdType)
|
switch (cmdType)
|
||||||
{
|
{
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
need_old_upd = need_old_del = need_new_upd = false;
|
need_old = false;
|
||||||
need_new_ins = trigdesc->trig_insert_new_table;
|
need_new = trigdesc->trig_insert_new_table;
|
||||||
break;
|
break;
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
need_old_upd = trigdesc->trig_update_old_table;
|
need_old = trigdesc->trig_update_old_table;
|
||||||
need_new_upd = trigdesc->trig_update_new_table;
|
need_new = trigdesc->trig_update_new_table;
|
||||||
need_old_del = need_new_ins = false;
|
|
||||||
break;
|
break;
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
need_old_del = trigdesc->trig_delete_old_table;
|
need_old = trigdesc->trig_delete_old_table;
|
||||||
need_old_upd = need_new_upd = need_new_ins = false;
|
need_new = false;
|
||||||
break;
|
|
||||||
case CMD_MERGE:
|
|
||||||
need_old_upd = trigdesc->trig_update_old_table;
|
|
||||||
need_new_upd = trigdesc->trig_update_new_table;
|
|
||||||
need_old_del = trigdesc->trig_delete_old_table;
|
|
||||||
need_new_ins = trigdesc->trig_insert_new_table;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
|
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
|
||||||
/* keep compiler quiet */
|
need_old = need_new = false; /* keep compiler quiet */
|
||||||
need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
|
if (!need_old && !need_new)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Check state, like AfterTriggerSaveEvent. */
|
/* Check state, like AfterTriggerSaveEvent. */
|
||||||
@ -4736,14 +4696,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
|
|||||||
saveResourceOwner = CurrentResourceOwner;
|
saveResourceOwner = CurrentResourceOwner;
|
||||||
CurrentResourceOwner = CurTransactionResourceOwner;
|
CurrentResourceOwner = CurTransactionResourceOwner;
|
||||||
|
|
||||||
if (need_old_upd && table->old_upd_tuplestore == NULL)
|
if (need_old && table->old_tuplestore == NULL)
|
||||||
table->old_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
||||||
if (need_new_upd && table->new_upd_tuplestore == NULL)
|
if (need_new && table->new_tuplestore == NULL)
|
||||||
table->new_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
||||||
if (need_old_del && table->old_del_tuplestore == NULL)
|
|
||||||
table->old_del_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
|
||||||
if (need_new_ins && table->new_ins_tuplestore == NULL)
|
|
||||||
table->new_ins_tuplestore = tuplestore_begin_heap(false, false, work_mem);
|
|
||||||
|
|
||||||
CurrentResourceOwner = saveResourceOwner;
|
CurrentResourceOwner = saveResourceOwner;
|
||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
@ -4932,20 +4888,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
|
|||||||
{
|
{
|
||||||
AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);
|
AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);
|
||||||
|
|
||||||
ts = table->old_upd_tuplestore;
|
ts = table->old_tuplestore;
|
||||||
table->old_upd_tuplestore = NULL;
|
table->old_tuplestore = NULL;
|
||||||
if (ts)
|
if (ts)
|
||||||
tuplestore_end(ts);
|
tuplestore_end(ts);
|
||||||
ts = table->new_upd_tuplestore;
|
ts = table->new_tuplestore;
|
||||||
table->new_upd_tuplestore = NULL;
|
table->new_tuplestore = NULL;
|
||||||
if (ts)
|
|
||||||
tuplestore_end(ts);
|
|
||||||
ts = table->old_del_tuplestore;
|
|
||||||
table->old_del_tuplestore = NULL;
|
|
||||||
if (ts)
|
|
||||||
tuplestore_end(ts);
|
|
||||||
ts = table->new_ins_tuplestore;
|
|
||||||
table->new_ins_tuplestore = NULL;
|
|
||||||
if (ts)
|
if (ts)
|
||||||
tuplestore_end(ts);
|
tuplestore_end(ts);
|
||||||
}
|
}
|
||||||
@ -5796,28 +5744,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
|||||||
newtup == NULL));
|
newtup == NULL));
|
||||||
|
|
||||||
if (oldtup != NULL &&
|
if (oldtup != NULL &&
|
||||||
(event == TRIGGER_EVENT_DELETE && delete_old_table))
|
((event == TRIGGER_EVENT_DELETE && delete_old_table) ||
|
||||||
|
(event == TRIGGER_EVENT_UPDATE && update_old_table)))
|
||||||
{
|
{
|
||||||
Tuplestorestate *old_tuplestore;
|
Tuplestorestate *old_tuplestore;
|
||||||
|
|
||||||
old_tuplestore = transition_capture->tcs_private->old_del_tuplestore;
|
old_tuplestore = transition_capture->tcs_private->old_tuplestore;
|
||||||
|
|
||||||
if (map != NULL)
|
|
||||||
{
|
|
||||||
HeapTuple converted = do_convert_tuple(oldtup, map);
|
|
||||||
|
|
||||||
tuplestore_puttuple(old_tuplestore, converted);
|
|
||||||
pfree(converted);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
tuplestore_puttuple(old_tuplestore, oldtup);
|
|
||||||
}
|
|
||||||
if (oldtup != NULL &&
|
|
||||||
(event == TRIGGER_EVENT_UPDATE && update_old_table))
|
|
||||||
{
|
|
||||||
Tuplestorestate *old_tuplestore;
|
|
||||||
|
|
||||||
old_tuplestore = transition_capture->tcs_private->old_upd_tuplestore;
|
|
||||||
|
|
||||||
if (map != NULL)
|
if (map != NULL)
|
||||||
{
|
{
|
||||||
@ -5830,30 +5762,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
|||||||
tuplestore_puttuple(old_tuplestore, oldtup);
|
tuplestore_puttuple(old_tuplestore, oldtup);
|
||||||
}
|
}
|
||||||
if (newtup != NULL &&
|
if (newtup != NULL &&
|
||||||
(event == TRIGGER_EVENT_INSERT && insert_new_table))
|
((event == TRIGGER_EVENT_INSERT && insert_new_table) ||
|
||||||
|
(event == TRIGGER_EVENT_UPDATE && update_new_table)))
|
||||||
{
|
{
|
||||||
Tuplestorestate *new_tuplestore;
|
Tuplestorestate *new_tuplestore;
|
||||||
|
|
||||||
new_tuplestore = transition_capture->tcs_private->new_ins_tuplestore;
|
new_tuplestore = transition_capture->tcs_private->new_tuplestore;
|
||||||
|
|
||||||
if (original_insert_tuple != NULL)
|
|
||||||
tuplestore_puttuple(new_tuplestore, original_insert_tuple);
|
|
||||||
else if (map != NULL)
|
|
||||||
{
|
|
||||||
HeapTuple converted = do_convert_tuple(newtup, map);
|
|
||||||
|
|
||||||
tuplestore_puttuple(new_tuplestore, converted);
|
|
||||||
pfree(converted);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
tuplestore_puttuple(new_tuplestore, newtup);
|
|
||||||
}
|
|
||||||
if (newtup != NULL &&
|
|
||||||
(event == TRIGGER_EVENT_UPDATE && update_new_table))
|
|
||||||
{
|
|
||||||
Tuplestorestate *new_tuplestore;
|
|
||||||
|
|
||||||
new_tuplestore = transition_capture->tcs_private->new_upd_tuplestore;
|
|
||||||
|
|
||||||
if (original_insert_tuple != NULL)
|
if (original_insert_tuple != NULL)
|
||||||
tuplestore_puttuple(new_tuplestore, original_insert_tuple);
|
tuplestore_puttuple(new_tuplestore, original_insert_tuple);
|
||||||
|
@ -22,7 +22,7 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
|
|||||||
nodeCustom.o nodeFunctionscan.o nodeGather.o \
|
nodeCustom.o nodeFunctionscan.o nodeGather.o \
|
||||||
nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
|
nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
|
||||||
nodeLimit.o nodeLockRows.o nodeGatherMerge.o \
|
nodeLimit.o nodeLockRows.o nodeGatherMerge.o \
|
||||||
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeMerge.o nodeModifyTable.o \
|
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
|
||||||
nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \
|
nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \
|
||||||
nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
|
nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
|
||||||
nodeValuesscan.o \
|
nodeValuesscan.o \
|
||||||
|
@ -37,16 +37,6 @@ the plan tree returns the computed tuples to be updated, plus a "junk"
|
|||||||
one. For DELETE, the plan tree need only deliver a CTID column, and the
|
one. For DELETE, the plan tree need only deliver a CTID column, and the
|
||||||
ModifyTable node visits each of those rows and marks the row deleted.
|
ModifyTable node visits each of those rows and marks the row deleted.
|
||||||
|
|
||||||
MERGE runs one generic plan that returns candidate target rows. Each row
|
|
||||||
consists of a super-row that contains all the columns needed by any of the
|
|
||||||
individual actions, plus a CTID and a TABLEOID junk columns. The CTID column is
|
|
||||||
required to know if a matching target row was found or not and the TABLEOID
|
|
||||||
column is needed to find the underlying target partition, in case when the
|
|
||||||
target table is a partition table. If the CTID column is set we attempt to
|
|
||||||
activate WHEN MATCHED actions, or if it is NULL then we will attempt to
|
|
||||||
activate WHEN NOT MATCHED actions. Once we know which action is activated we
|
|
||||||
form the final result row and apply only those changes.
|
|
||||||
|
|
||||||
XXX a great deal more documentation needs to be written here...
|
XXX a great deal more documentation needs to be written here...
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,7 +233,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
|
|||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_MERGE:
|
|
||||||
estate->es_output_cid = GetCurrentCommandId(true);
|
estate->es_output_cid = GetCurrentCommandId(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1358,9 +1357,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
|||||||
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
|
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
|
||||||
resultRelInfo->ri_onConflict = NULL;
|
resultRelInfo->ri_onConflict = NULL;
|
||||||
|
|
||||||
resultRelInfo->ri_mergeTargetRTI = 0;
|
|
||||||
resultRelInfo->ri_mergeState = (MergeState *) palloc0(sizeof (MergeState));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Partition constraint, which also includes the partition constraint of
|
* Partition constraint, which also includes the partition constraint of
|
||||||
* all the ancestors that are partitions. Note that it will be checked
|
* all the ancestors that are partitions. Note that it will be checked
|
||||||
@ -2209,19 +2205,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
|
|||||||
errmsg("new row violates row-level security policy for table \"%s\"",
|
errmsg("new row violates row-level security policy for table \"%s\"",
|
||||||
wco->relname)));
|
wco->relname)));
|
||||||
break;
|
break;
|
||||||
case WCO_RLS_MERGE_UPDATE_CHECK:
|
|
||||||
case WCO_RLS_MERGE_DELETE_CHECK:
|
|
||||||
if (wco->polname != NULL)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
||||||
errmsg("target row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
|
|
||||||
wco->polname, wco->relname)));
|
|
||||||
else
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
||||||
errmsg("target row violates row-level security policy (USING expression) for table \"%s\"",
|
|
||||||
wco->relname)));
|
|
||||||
break;
|
|
||||||
case WCO_RLS_CONFLICT_CHECK:
|
case WCO_RLS_CONFLICT_CHECK:
|
||||||
if (wco->polname != NULL)
|
if (wco->polname != NULL)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
|
@ -67,8 +67,6 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
|
|||||||
ResultRelInfo *update_rri = NULL;
|
ResultRelInfo *update_rri = NULL;
|
||||||
int num_update_rri = 0,
|
int num_update_rri = 0,
|
||||||
update_rri_index = 0;
|
update_rri_index = 0;
|
||||||
bool is_update = false;
|
|
||||||
bool is_merge = false;
|
|
||||||
PartitionTupleRouting *proute;
|
PartitionTupleRouting *proute;
|
||||||
int nparts;
|
int nparts;
|
||||||
ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
|
ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
|
||||||
@ -91,22 +89,13 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
|
|||||||
|
|
||||||
/* Set up details specific to the type of tuple routing we are doing. */
|
/* Set up details specific to the type of tuple routing we are doing. */
|
||||||
if (node && node->operation == CMD_UPDATE)
|
if (node && node->operation == CMD_UPDATE)
|
||||||
is_update = true;
|
|
||||||
else if (node && node->operation == CMD_MERGE)
|
|
||||||
is_merge = true;
|
|
||||||
|
|
||||||
if (is_update)
|
|
||||||
{
|
{
|
||||||
update_rri = mtstate->resultRelInfo;
|
update_rri = mtstate->resultRelInfo;
|
||||||
num_update_rri = list_length(node->plans);
|
num_update_rri = list_length(node->plans);
|
||||||
proute->subplan_partition_offsets =
|
proute->subplan_partition_offsets =
|
||||||
palloc(num_update_rri * sizeof(int));
|
palloc(num_update_rri * sizeof(int));
|
||||||
proute->num_subplan_partition_offsets = num_update_rri;
|
proute->num_subplan_partition_offsets = num_update_rri;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (is_update || is_merge)
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* We need an additional tuple slot for storing transient tuples that
|
* We need an additional tuple slot for storing transient tuples that
|
||||||
* are converted to the root table descriptor.
|
* are converted to the root table descriptor.
|
||||||
@ -310,25 +299,6 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Given OID of the partition leaf, return the index of the leaf in the
|
|
||||||
* partition hierarchy.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < proute->num_partitions; i++)
|
|
||||||
{
|
|
||||||
if (proute->partition_oids[i] == partoid)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert(i < proute->num_partitions);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ExecInitPartitionInfo
|
* ExecInitPartitionInfo
|
||||||
* Initialize ResultRelInfo and other information for a partition if not
|
* Initialize ResultRelInfo and other information for a partition if not
|
||||||
@ -367,8 +337,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
|
|||||||
rootrel,
|
rootrel,
|
||||||
estate->es_instrument);
|
estate->es_instrument);
|
||||||
|
|
||||||
leaf_part_rri->ri_PartitionLeafIndex = partidx;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify result relation is a valid target for an INSERT. An UPDATE of a
|
* Verify result relation is a valid target for an INSERT. An UPDATE of a
|
||||||
* partition-key becomes a DELETE+INSERT operation, so this check is still
|
* partition-key becomes a DELETE+INSERT operation, so this check is still
|
||||||
@ -657,90 +625,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
|
|||||||
Assert(proute->partitions[partidx] == NULL);
|
Assert(proute->partitions[partidx] == NULL);
|
||||||
proute->partitions[partidx] = leaf_part_rri;
|
proute->partitions[partidx] = leaf_part_rri;
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize information about this partition that's needed to handle
|
|
||||||
* MERGE.
|
|
||||||
*/
|
|
||||||
if (node && node->operation == CMD_MERGE)
|
|
||||||
{
|
|
||||||
TupleDesc partrelDesc = RelationGetDescr(partrel);
|
|
||||||
TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
|
|
||||||
int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
|
|
||||||
Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the root parent and partition have the same tuple
|
|
||||||
* descriptor, just reuse the original MERGE state for partition.
|
|
||||||
*/
|
|
||||||
if (map == NULL)
|
|
||||||
{
|
|
||||||
leaf_part_rri->ri_mergeState = resultRelInfo->ri_mergeState;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Convert expressions contain partition's attnos. */
|
|
||||||
List *conv_tl, *conv_qual;
|
|
||||||
ListCell *l;
|
|
||||||
List *matchedActionStates = NIL;
|
|
||||||
List *notMatchedActionStates = NIL;
|
|
||||||
|
|
||||||
foreach (l, node->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = lfirst_node(MergeAction, l);
|
|
||||||
MergeActionState *action_state = makeNode(MergeActionState);
|
|
||||||
TupleDesc tupDesc;
|
|
||||||
ExprContext *econtext;
|
|
||||||
|
|
||||||
action_state->matched = action->matched;
|
|
||||||
action_state->commandType = action->commandType;
|
|
||||||
|
|
||||||
conv_qual = (List *) action->qual;
|
|
||||||
conv_qual = map_partition_varattnos(conv_qual,
|
|
||||||
firstVarno, partrel,
|
|
||||||
firstResultRel, NULL);
|
|
||||||
|
|
||||||
action_state->whenqual = ExecInitQual(conv_qual, &mtstate->ps);
|
|
||||||
|
|
||||||
conv_tl = (List *) action->targetList;
|
|
||||||
conv_tl = map_partition_varattnos(conv_tl,
|
|
||||||
firstVarno, partrel,
|
|
||||||
firstResultRel, NULL);
|
|
||||||
|
|
||||||
conv_tl = adjust_partition_tlist( conv_tl, map);
|
|
||||||
|
|
||||||
tupDesc = ExecTypeFromTL(conv_tl, partrelDesc->tdhasoid);
|
|
||||||
action_state->tupDesc = tupDesc;
|
|
||||||
|
|
||||||
/* build action projection state */
|
|
||||||
econtext = mtstate->ps.ps_ExprContext;
|
|
||||||
action_state->proj =
|
|
||||||
ExecBuildProjectionInfo(conv_tl, econtext,
|
|
||||||
mtstate->mt_mergeproj,
|
|
||||||
&mtstate->ps,
|
|
||||||
partrelDesc);
|
|
||||||
|
|
||||||
if (action_state->matched)
|
|
||||||
matchedActionStates =
|
|
||||||
lappend(matchedActionStates, action_state);
|
|
||||||
else
|
|
||||||
notMatchedActionStates =
|
|
||||||
lappend(notMatchedActionStates, action_state);
|
|
||||||
}
|
|
||||||
leaf_part_rri->ri_mergeState->matchedActionStates =
|
|
||||||
matchedActionStates;
|
|
||||||
leaf_part_rri->ri_mergeState->notMatchedActionStates =
|
|
||||||
notMatchedActionStates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* get_partition_dispatch_recurse() and expand_partitioned_rtentry()
|
|
||||||
* fetch the leaf OIDs in the same order. So we can safely derive the
|
|
||||||
* index of the merge target relation corresponding to this partition
|
|
||||||
* by simply adding partidx + 1 to the root's merge target relation.
|
|
||||||
*/
|
|
||||||
leaf_part_rri->ri_mergeTargetRTI = node->mergeTargetRelation +
|
|
||||||
partidx + 1;
|
|
||||||
}
|
|
||||||
MemoryContextSwitchTo(oldContext);
|
MemoryContextSwitchTo(oldContext);
|
||||||
|
|
||||||
return leaf_part_rri;
|
return leaf_part_rri;
|
||||||
|
@ -454,7 +454,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
|
|||||||
{
|
{
|
||||||
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
|
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
|
||||||
&searchslot->tts_tuple->t_self,
|
&searchslot->tts_tuple->t_self,
|
||||||
NULL, slot, NULL);
|
NULL, slot);
|
||||||
|
|
||||||
if (slot == NULL) /* "do nothing" */
|
if (slot == NULL) /* "do nothing" */
|
||||||
skip_tuple = true;
|
skip_tuple = true;
|
||||||
@ -515,7 +515,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
|
|||||||
{
|
{
|
||||||
skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
|
skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
|
||||||
&searchslot->tts_tuple->t_self,
|
&searchslot->tts_tuple->t_self,
|
||||||
NULL, NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skip_tuple)
|
if (!skip_tuple)
|
||||||
|
@ -42,7 +42,6 @@
|
|||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
#include "executor/execPartition.h"
|
#include "executor/execPartition.h"
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
#include "executor/nodeMerge.h"
|
|
||||||
#include "executor/nodeModifyTable.h"
|
#include "executor/nodeModifyTable.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
@ -63,17 +62,17 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
|
|||||||
EState *estate,
|
EState *estate,
|
||||||
bool canSetTag,
|
bool canSetTag,
|
||||||
TupleTableSlot **returning);
|
TupleTableSlot **returning);
|
||||||
|
static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
||||||
|
EState *estate,
|
||||||
|
PartitionTupleRouting *proute,
|
||||||
|
ResultRelInfo *targetRelInfo,
|
||||||
|
TupleTableSlot *slot);
|
||||||
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
|
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
|
||||||
static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
|
static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
|
||||||
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
|
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
|
||||||
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
|
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
|
||||||
int whichplan);
|
int whichplan);
|
||||||
|
|
||||||
/* flags for mt_merge_subcommands */
|
|
||||||
#define MERGE_INSERT 0x01
|
|
||||||
#define MERGE_UPDATE 0x02
|
|
||||||
#define MERGE_DELETE 0x04
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify that the tuples to be produced by INSERT or UPDATE match the
|
* Verify that the tuples to be produced by INSERT or UPDATE match the
|
||||||
* target relation's rowtype
|
* target relation's rowtype
|
||||||
@ -260,12 +259,11 @@ ExecCheckTIDVisible(EState *estate,
|
|||||||
* Returns RETURNING result if any, otherwise NULL.
|
* Returns RETURNING result if any, otherwise NULL.
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
extern TupleTableSlot *
|
static TupleTableSlot *
|
||||||
ExecInsert(ModifyTableState *mtstate,
|
ExecInsert(ModifyTableState *mtstate,
|
||||||
TupleTableSlot *slot,
|
TupleTableSlot *slot,
|
||||||
TupleTableSlot *planSlot,
|
TupleTableSlot *planSlot,
|
||||||
EState *estate,
|
EState *estate,
|
||||||
MergeActionState *actionState,
|
|
||||||
bool canSetTag)
|
bool canSetTag)
|
||||||
{
|
{
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
@ -392,17 +390,9 @@ ExecInsert(ModifyTableState *mtstate,
|
|||||||
* partition, we should instead check UPDATE policies, because we are
|
* partition, we should instead check UPDATE policies, because we are
|
||||||
* executing policies defined on the target table, and not those
|
* executing policies defined on the target table, and not those
|
||||||
* defined on the child partitions.
|
* defined on the child partitions.
|
||||||
*
|
|
||||||
* If we're running MERGE, we refer to the action that we're executing
|
|
||||||
* to know if we're doing an INSERT or UPDATE to a partition table.
|
|
||||||
*/
|
*/
|
||||||
if (mtstate->operation == CMD_UPDATE)
|
wco_kind = (mtstate->operation == CMD_UPDATE) ?
|
||||||
wco_kind = WCO_RLS_UPDATE_CHECK;
|
WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
|
||||||
else if (mtstate->operation == CMD_MERGE)
|
|
||||||
wco_kind = (actionState->commandType == CMD_UPDATE) ?
|
|
||||||
WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
|
|
||||||
else
|
|
||||||
wco_kind = WCO_RLS_INSERT_CHECK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
|
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
|
||||||
@ -627,19 +617,10 @@ ExecInsert(ModifyTableState *mtstate,
|
|||||||
* passed to foreign table triggers; it is NULL when the foreign
|
* passed to foreign table triggers; it is NULL when the foreign
|
||||||
* table has no relevant triggers.
|
* table has no relevant triggers.
|
||||||
*
|
*
|
||||||
* MERGE passes actionState of the action it's currently executing;
|
|
||||||
* regular DELETE passes NULL. This is used by ExecDelete to know if it's
|
|
||||||
* being called from MERGE or regular DELETE operation.
|
|
||||||
*
|
|
||||||
* If the DELETE fails because the tuple is concurrently updated/deleted
|
|
||||||
* by this or some other transaction, hufdp is filled with the reason as
|
|
||||||
* well as other important information. Currently only MERGE needs this
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* Returns RETURNING result if any, otherwise NULL.
|
* Returns RETURNING result if any, otherwise NULL.
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
TupleTableSlot *
|
static TupleTableSlot *
|
||||||
ExecDelete(ModifyTableState *mtstate,
|
ExecDelete(ModifyTableState *mtstate,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple oldtuple,
|
HeapTuple oldtuple,
|
||||||
@ -648,8 +629,6 @@ ExecDelete(ModifyTableState *mtstate,
|
|||||||
EState *estate,
|
EState *estate,
|
||||||
bool *tupleDeleted,
|
bool *tupleDeleted,
|
||||||
bool processReturning,
|
bool processReturning,
|
||||||
HeapUpdateFailureData *hufdp,
|
|
||||||
MergeActionState *actionState,
|
|
||||||
bool canSetTag)
|
bool canSetTag)
|
||||||
{
|
{
|
||||||
ResultRelInfo *resultRelInfo;
|
ResultRelInfo *resultRelInfo;
|
||||||
@ -662,14 +641,6 @@ ExecDelete(ModifyTableState *mtstate,
|
|||||||
if (tupleDeleted)
|
if (tupleDeleted)
|
||||||
*tupleDeleted = false;
|
*tupleDeleted = false;
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize hufdp. Since the caller is only interested in the failure
|
|
||||||
* status, initialize with the state that is used to indicate successful
|
|
||||||
* operation.
|
|
||||||
*/
|
|
||||||
if (hufdp)
|
|
||||||
hufdp->result = HeapTupleMayBeUpdated;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get information on the (current) result relation
|
* get information on the (current) result relation
|
||||||
*/
|
*/
|
||||||
@ -683,7 +654,7 @@ ExecDelete(ModifyTableState *mtstate,
|
|||||||
bool dodelete;
|
bool dodelete;
|
||||||
|
|
||||||
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
|
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
|
||||||
tupleid, oldtuple, hufdp);
|
tupleid, oldtuple);
|
||||||
|
|
||||||
if (!dodelete) /* "do nothing" */
|
if (!dodelete) /* "do nothing" */
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -750,15 +721,6 @@ ldelete:;
|
|||||||
estate->es_crosscheck_snapshot,
|
estate->es_crosscheck_snapshot,
|
||||||
true /* wait for commit */ ,
|
true /* wait for commit */ ,
|
||||||
&hufd);
|
&hufd);
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy the necessary information, if the caller has asked for it. We
|
|
||||||
* must do this irrespective of whether the tuple was updated or
|
|
||||||
* deleted.
|
|
||||||
*/
|
|
||||||
if (hufdp)
|
|
||||||
*hufdp = hufd;
|
|
||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HeapTupleSelfUpdated:
|
case HeapTupleSelfUpdated:
|
||||||
@ -793,11 +755,7 @@ ldelete:;
|
|||||||
errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
|
errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
|
||||||
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
|
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
|
||||||
|
|
||||||
/*
|
/* Else, already deleted by self; nothing to do */
|
||||||
* Else, already deleted by self; nothing to do but inform
|
|
||||||
* MERGE about it anyways so that it can take necessary
|
|
||||||
* action.
|
|
||||||
*/
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
case HeapTupleMayBeUpdated:
|
case HeapTupleMayBeUpdated:
|
||||||
@ -808,24 +766,14 @@ ldelete:;
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||||
errmsg("could not serialize access due to concurrent update")));
|
errmsg("could not serialize access due to concurrent update")));
|
||||||
|
|
||||||
if (!ItemPointerEquals(tupleid, &hufd.ctid))
|
if (!ItemPointerEquals(tupleid, &hufd.ctid))
|
||||||
{
|
{
|
||||||
TupleTableSlot *epqslot;
|
TupleTableSlot *epqslot;
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're executing MERGE, then the onus of running
|
|
||||||
* EvalPlanQual() and handling its outcome lies with the
|
|
||||||
* caller.
|
|
||||||
*/
|
|
||||||
if (actionState != NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* Normal DELETE path. */
|
|
||||||
epqslot = EvalPlanQual(estate,
|
epqslot = EvalPlanQual(estate,
|
||||||
epqstate,
|
epqstate,
|
||||||
resultRelationDesc,
|
resultRelationDesc,
|
||||||
GetEPQRangeTableIndex(resultRelInfo),
|
resultRelInfo->ri_RangeTableIndex,
|
||||||
LockTupleExclusive,
|
LockTupleExclusive,
|
||||||
&hufd.ctid,
|
&hufd.ctid,
|
||||||
hufd.xmax);
|
hufd.xmax);
|
||||||
@ -835,12 +783,7 @@ ldelete:;
|
|||||||
goto ldelete;
|
goto ldelete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* tuple already deleted; nothing to do */
|
||||||
/*
|
|
||||||
* tuple already deleted; nothing to do. But MERGE might want
|
|
||||||
* to handle it differently. We've already filled-in hufdp
|
|
||||||
* with sufficient information for MERGE to look at.
|
|
||||||
*/
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -968,21 +911,10 @@ ldelete:;
|
|||||||
* foreign table triggers; it is NULL when the foreign table has
|
* foreign table triggers; it is NULL when the foreign table has
|
||||||
* no relevant triggers.
|
* no relevant triggers.
|
||||||
*
|
*
|
||||||
* MERGE passes actionState of the action it's currently executing;
|
|
||||||
* regular UPDATE passes NULL. This is used by ExecUpdate to know if it's
|
|
||||||
* being called from MERGE or regular UPDATE operation. ExecUpdate may
|
|
||||||
* pass this information to ExecInsert if it ends up running DELETE+INSERT
|
|
||||||
* for partition key updates.
|
|
||||||
*
|
|
||||||
* If the UPDATE fails because the tuple is concurrently updated/deleted
|
|
||||||
* by this or some other transaction, hufdp is filled with the reason as
|
|
||||||
* well as other important information. Currently only MERGE needs this
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* Returns RETURNING result if any, otherwise NULL.
|
* Returns RETURNING result if any, otherwise NULL.
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
extern TupleTableSlot *
|
static TupleTableSlot *
|
||||||
ExecUpdate(ModifyTableState *mtstate,
|
ExecUpdate(ModifyTableState *mtstate,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple oldtuple,
|
HeapTuple oldtuple,
|
||||||
@ -990,9 +922,6 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
TupleTableSlot *planSlot,
|
TupleTableSlot *planSlot,
|
||||||
EPQState *epqstate,
|
EPQState *epqstate,
|
||||||
EState *estate,
|
EState *estate,
|
||||||
bool *tuple_updated,
|
|
||||||
HeapUpdateFailureData *hufdp,
|
|
||||||
MergeActionState *actionState,
|
|
||||||
bool canSetTag)
|
bool canSetTag)
|
||||||
{
|
{
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
@ -1009,17 +938,6 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
if (IsBootstrapProcessingMode())
|
if (IsBootstrapProcessingMode())
|
||||||
elog(ERROR, "cannot UPDATE during bootstrap");
|
elog(ERROR, "cannot UPDATE during bootstrap");
|
||||||
|
|
||||||
if (tuple_updated)
|
|
||||||
*tuple_updated = false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize hufdp. Since the caller is only interested in the failure
|
|
||||||
* status, initialize with the state that is used to indicate successful
|
|
||||||
* operation.
|
|
||||||
*/
|
|
||||||
if (hufdp)
|
|
||||||
hufdp->result = HeapTupleMayBeUpdated;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get the heap tuple out of the tuple table slot, making sure we have a
|
* get the heap tuple out of the tuple table slot, making sure we have a
|
||||||
* writable copy
|
* writable copy
|
||||||
@ -1037,7 +955,7 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
resultRelInfo->ri_TrigDesc->trig_update_before_row)
|
resultRelInfo->ri_TrigDesc->trig_update_before_row)
|
||||||
{
|
{
|
||||||
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
|
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
|
||||||
tupleid, oldtuple, slot, hufdp);
|
tupleid, oldtuple, slot);
|
||||||
|
|
||||||
if (slot == NULL) /* "do nothing" */
|
if (slot == NULL) /* "do nothing" */
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1083,6 +1001,7 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
LockTupleMode lockmode;
|
||||||
bool partition_constraint_failed;
|
bool partition_constraint_failed;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1160,9 +1079,8 @@ lreplace:;
|
|||||||
* Row movement, part 1. Delete the tuple, but skip RETURNING
|
* Row movement, part 1. Delete the tuple, but skip RETURNING
|
||||||
* processing. We want to return rows from INSERT.
|
* processing. We want to return rows from INSERT.
|
||||||
*/
|
*/
|
||||||
ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate,
|
ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate, estate,
|
||||||
estate, &tuple_deleted, false, hufdp, NULL,
|
&tuple_deleted, false, false);
|
||||||
false);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For some reason if DELETE didn't happen (e.g. trigger prevented
|
* For some reason if DELETE didn't happen (e.g. trigger prevented
|
||||||
@ -1198,36 +1116,16 @@ lreplace:;
|
|||||||
saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
|
saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We should convert the tuple into root's tuple descriptor, since
|
* resultRelInfo is one of the per-subplan resultRelInfos. So we
|
||||||
* ExecInsert() starts the search from root. To do that, we need to
|
* should convert the tuple into root's tuple descriptor, since
|
||||||
* retrieve the tuple conversion map for this resultRelInfo.
|
* ExecInsert() starts the search from root. The tuple conversion
|
||||||
*
|
* map list is in the order of mtstate->resultRelInfo[], so to
|
||||||
* If we're running MERGE then resultRelInfo is per-partition
|
* retrieve the one for this resultRel, we need to know the
|
||||||
* resultRelInfo as initialized in ExecInitPartitionInfo(). Note
|
* position of the resultRel in mtstate->resultRelInfo[].
|
||||||
* that we don't expand inheritance for the resultRelation in case
|
|
||||||
* of MERGE and hence there is just one subplan. Whereas for
|
|
||||||
* regular UPDATE, resultRelInfo is one of the per-subplan
|
|
||||||
* resultRelInfos. In either case the position of this partition in
|
|
||||||
* tracked in ri_PartitionLeafIndex;
|
|
||||||
*
|
|
||||||
* Retrieve the map either by looking at the resultRelInfo's
|
|
||||||
* position in mtstate->resultRelInfo[] (for UPDATE) or by simply
|
|
||||||
* using the ri_PartitionLeafIndex value (for MERGE).
|
|
||||||
*/
|
*/
|
||||||
if (mtstate->operation == CMD_MERGE)
|
map_index = resultRelInfo - mtstate->resultRelInfo;
|
||||||
{
|
Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
|
||||||
map_index = resultRelInfo->ri_PartitionLeafIndex;
|
tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
|
||||||
Assert(mtstate->rootResultRelInfo == NULL);
|
|
||||||
tupconv_map = TupConvMapForLeaf(proute,
|
|
||||||
mtstate->resultRelInfo,
|
|
||||||
map_index);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
map_index = resultRelInfo - mtstate->resultRelInfo;
|
|
||||||
Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
|
|
||||||
tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
|
|
||||||
}
|
|
||||||
tuple = ConvertPartitionTupleSlot(tupconv_map,
|
tuple = ConvertPartitionTupleSlot(tupconv_map,
|
||||||
tuple,
|
tuple,
|
||||||
proute->root_tuple_slot,
|
proute->root_tuple_slot,
|
||||||
@ -1237,16 +1135,12 @@ lreplace:;
|
|||||||
* Prepare for tuple routing, making it look like we're inserting
|
* Prepare for tuple routing, making it look like we're inserting
|
||||||
* into the root.
|
* into the root.
|
||||||
*/
|
*/
|
||||||
|
Assert(mtstate->rootResultRelInfo != NULL);
|
||||||
slot = ExecPrepareTupleRouting(mtstate, estate, proute,
|
slot = ExecPrepareTupleRouting(mtstate, estate, proute,
|
||||||
getTargetResultRelInfo(mtstate),
|
mtstate->rootResultRelInfo, slot);
|
||||||
slot);
|
|
||||||
|
|
||||||
ret_slot = ExecInsert(mtstate, slot, planSlot,
|
ret_slot = ExecInsert(mtstate, slot, planSlot,
|
||||||
estate, actionState, canSetTag);
|
estate, canSetTag);
|
||||||
|
|
||||||
/* Update is successful. */
|
|
||||||
if (tuple_updated)
|
|
||||||
*tuple_updated = true;
|
|
||||||
|
|
||||||
/* Revert ExecPrepareTupleRouting's node change. */
|
/* Revert ExecPrepareTupleRouting's node change. */
|
||||||
estate->es_result_relation_info = resultRelInfo;
|
estate->es_result_relation_info = resultRelInfo;
|
||||||
@ -1284,16 +1178,7 @@ lreplace:;
|
|||||||
estate->es_output_cid,
|
estate->es_output_cid,
|
||||||
estate->es_crosscheck_snapshot,
|
estate->es_crosscheck_snapshot,
|
||||||
true /* wait for commit */ ,
|
true /* wait for commit */ ,
|
||||||
&hufd);
|
&hufd, &lockmode);
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy the necessary information, if the caller has asked for it. We
|
|
||||||
* must do this irrespective of whether the tuple was updated or
|
|
||||||
* deleted.
|
|
||||||
*/
|
|
||||||
if (hufdp)
|
|
||||||
*hufdp = hufd;
|
|
||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HeapTupleSelfUpdated:
|
case HeapTupleSelfUpdated:
|
||||||
@ -1338,42 +1223,26 @@ lreplace:;
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||||
errmsg("could not serialize access due to concurrent update")));
|
errmsg("could not serialize access due to concurrent update")));
|
||||||
|
|
||||||
if (!ItemPointerEquals(tupleid, &hufd.ctid))
|
if (!ItemPointerEquals(tupleid, &hufd.ctid))
|
||||||
{
|
{
|
||||||
TupleTableSlot *epqslot;
|
TupleTableSlot *epqslot;
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're executing MERGE, then the onus of running
|
|
||||||
* EvalPlanQual() and handling its outcome lies with the
|
|
||||||
* caller.
|
|
||||||
*/
|
|
||||||
if (actionState != NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* Regular UPDATE path. */
|
|
||||||
epqslot = EvalPlanQual(estate,
|
epqslot = EvalPlanQual(estate,
|
||||||
epqstate,
|
epqstate,
|
||||||
resultRelationDesc,
|
resultRelationDesc,
|
||||||
GetEPQRangeTableIndex(resultRelInfo),
|
resultRelInfo->ri_RangeTableIndex,
|
||||||
hufd.lockmode,
|
lockmode,
|
||||||
&hufd.ctid,
|
&hufd.ctid,
|
||||||
hufd.xmax);
|
hufd.xmax);
|
||||||
if (!TupIsNull(epqslot))
|
if (!TupIsNull(epqslot))
|
||||||
{
|
{
|
||||||
*tupleid = hufd.ctid;
|
*tupleid = hufd.ctid;
|
||||||
/* Normal UPDATE path */
|
|
||||||
slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
|
slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
|
||||||
tuple = ExecMaterializeSlot(slot);
|
tuple = ExecMaterializeSlot(slot);
|
||||||
goto lreplace;
|
goto lreplace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* tuple already deleted; nothing to do */
|
||||||
/*
|
|
||||||
* tuple already deleted; nothing to do. But MERGE might want
|
|
||||||
* to handle it differently. We've already filled-in hufdp
|
|
||||||
* with sufficient information for MERGE to look at.
|
|
||||||
*/
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1402,9 +1271,6 @@ lreplace:;
|
|||||||
estate, false, NULL, NIL);
|
estate, false, NULL, NIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tuple_updated)
|
|
||||||
*tuple_updated = true;
|
|
||||||
|
|
||||||
if (canSetTag)
|
if (canSetTag)
|
||||||
(estate->es_processed)++;
|
(estate->es_processed)++;
|
||||||
|
|
||||||
@ -1499,9 +1365,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
|
|||||||
* there's no historical behavior to break.
|
* there's no historical behavior to break.
|
||||||
*
|
*
|
||||||
* It is the user's responsibility to prevent this situation from
|
* It is the user's responsibility to prevent this situation from
|
||||||
* occurring. These problems are why SQL Standard similarly
|
* occurring. These problems are why SQL-2003 similarly specifies
|
||||||
* specifies that for SQL MERGE, an exception must be raised in
|
* that for SQL MERGE, an exception must be raised in the event of
|
||||||
* the event of an attempt to update the same row twice.
|
* an attempt to update the same row twice.
|
||||||
*/
|
*/
|
||||||
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1623,7 +1489,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
|
|||||||
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
|
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
|
||||||
mtstate->mt_conflproj, planSlot,
|
mtstate->mt_conflproj, planSlot,
|
||||||
&mtstate->mt_epqstate, mtstate->ps.state,
|
&mtstate->mt_epqstate, mtstate->ps.state,
|
||||||
NULL, NULL, NULL, canSetTag);
|
canSetTag);
|
||||||
|
|
||||||
ReleaseBuffer(buffer);
|
ReleaseBuffer(buffer);
|
||||||
return true;
|
return true;
|
||||||
@ -1661,14 +1527,6 @@ fireBSTriggers(ModifyTableState *node)
|
|||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
|
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
if (node->mt_merge_subcommands & MERGE_INSERT)
|
|
||||||
ExecBSInsertTriggers(node->ps.state, resultRelInfo);
|
|
||||||
if (node->mt_merge_subcommands & MERGE_UPDATE)
|
|
||||||
ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
|
|
||||||
if (node->mt_merge_subcommands & MERGE_DELETE)
|
|
||||||
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unknown operation");
|
elog(ERROR, "unknown operation");
|
||||||
break;
|
break;
|
||||||
@ -1724,17 +1582,6 @@ fireASTriggers(ModifyTableState *node)
|
|||||||
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
|
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
|
||||||
node->mt_transition_capture);
|
node->mt_transition_capture);
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
if (node->mt_merge_subcommands & MERGE_DELETE)
|
|
||||||
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
|
|
||||||
node->mt_transition_capture);
|
|
||||||
if (node->mt_merge_subcommands & MERGE_UPDATE)
|
|
||||||
ExecASUpdateTriggers(node->ps.state, resultRelInfo,
|
|
||||||
node->mt_transition_capture);
|
|
||||||
if (node->mt_merge_subcommands & MERGE_INSERT)
|
|
||||||
ExecASInsertTriggers(node->ps.state, resultRelInfo,
|
|
||||||
node->mt_transition_capture);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unknown operation");
|
elog(ERROR, "unknown operation");
|
||||||
break;
|
break;
|
||||||
@ -1797,7 +1644,7 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
|
|||||||
*
|
*
|
||||||
* Returns a slot holding the tuple of the partition rowtype.
|
* Returns a slot holding the tuple of the partition rowtype.
|
||||||
*/
|
*/
|
||||||
TupleTableSlot *
|
static TupleTableSlot *
|
||||||
ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
||||||
EState *estate,
|
EState *estate,
|
||||||
PartitionTupleRouting *proute,
|
PartitionTupleRouting *proute,
|
||||||
@ -2120,7 +1967,6 @@ ExecModifyTable(PlanState *pstate)
|
|||||||
{
|
{
|
||||||
/* advance to next subplan if any */
|
/* advance to next subplan if any */
|
||||||
node->mt_whichplan++;
|
node->mt_whichplan++;
|
||||||
|
|
||||||
if (node->mt_whichplan < node->mt_nplans)
|
if (node->mt_whichplan < node->mt_nplans)
|
||||||
{
|
{
|
||||||
resultRelInfo++;
|
resultRelInfo++;
|
||||||
@ -2169,12 +2015,6 @@ ExecModifyTable(PlanState *pstate)
|
|||||||
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
|
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
|
||||||
slot = planSlot;
|
slot = planSlot;
|
||||||
|
|
||||||
if (operation == CMD_MERGE)
|
|
||||||
{
|
|
||||||
ExecMerge(node, estate, slot, junkfilter, resultRelInfo);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
tupleid = NULL;
|
tupleid = NULL;
|
||||||
oldtuple = NULL;
|
oldtuple = NULL;
|
||||||
if (junkfilter != NULL)
|
if (junkfilter != NULL)
|
||||||
@ -2256,20 +2096,19 @@ ExecModifyTable(PlanState *pstate)
|
|||||||
slot = ExecPrepareTupleRouting(node, estate, proute,
|
slot = ExecPrepareTupleRouting(node, estate, proute,
|
||||||
resultRelInfo, slot);
|
resultRelInfo, slot);
|
||||||
slot = ExecInsert(node, slot, planSlot,
|
slot = ExecInsert(node, slot, planSlot,
|
||||||
estate, NULL, node->canSetTag);
|
estate, node->canSetTag);
|
||||||
/* Revert ExecPrepareTupleRouting's state change. */
|
/* Revert ExecPrepareTupleRouting's state change. */
|
||||||
if (proute)
|
if (proute)
|
||||||
estate->es_result_relation_info = resultRelInfo;
|
estate->es_result_relation_info = resultRelInfo;
|
||||||
break;
|
break;
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
|
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
|
||||||
&node->mt_epqstate, estate,
|
&node->mt_epqstate, estate, node->canSetTag);
|
||||||
NULL, NULL, NULL, node->canSetTag);
|
|
||||||
break;
|
break;
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
slot = ExecDelete(node, tupleid, oldtuple, planSlot,
|
slot = ExecDelete(node, tupleid, oldtuple, planSlot,
|
||||||
&node->mt_epqstate, estate,
|
&node->mt_epqstate, estate,
|
||||||
NULL, true, NULL, NULL, node->canSetTag);
|
NULL, true, node->canSetTag);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unknown operation");
|
elog(ERROR, "unknown operation");
|
||||||
@ -2359,16 +2198,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
saved_resultRelInfo = estate->es_result_relation_info;
|
saved_resultRelInfo = estate->es_result_relation_info;
|
||||||
|
|
||||||
resultRelInfo = mtstate->resultRelInfo;
|
resultRelInfo = mtstate->resultRelInfo;
|
||||||
|
|
||||||
/*
|
|
||||||
* mergeTargetRelation must be set if we're running MERGE and mustn't be
|
|
||||||
* set if we're not.
|
|
||||||
*/
|
|
||||||
Assert(operation != CMD_MERGE || node->mergeTargetRelation > 0);
|
|
||||||
Assert(operation == CMD_MERGE || node->mergeTargetRelation == 0);
|
|
||||||
|
|
||||||
resultRelInfo->ri_mergeTargetRTI = node->mergeTargetRelation;
|
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
foreach(l, node->plans)
|
foreach(l, node->plans)
|
||||||
{
|
{
|
||||||
@ -2447,8 +2276,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
* partition key.
|
* partition key.
|
||||||
*/
|
*/
|
||||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
|
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
|
||||||
(operation == CMD_INSERT || operation == CMD_MERGE ||
|
(operation == CMD_INSERT || update_tuple_routing_needed))
|
||||||
update_tuple_routing_needed))
|
|
||||||
mtstate->mt_partition_tuple_routing =
|
mtstate->mt_partition_tuple_routing =
|
||||||
ExecSetupPartitionTupleRouting(mtstate, rel);
|
ExecSetupPartitionTupleRouting(mtstate, rel);
|
||||||
|
|
||||||
@ -2459,15 +2287,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
|
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
|
||||||
ExecSetupTransitionCaptureState(mtstate, estate);
|
ExecSetupTransitionCaptureState(mtstate, estate);
|
||||||
|
|
||||||
/*
|
|
||||||
* If we are doing MERGE then setup child-parent mapping. This will be
|
|
||||||
* required in case we end up doing a partition-key update, triggering a
|
|
||||||
* tuple routing.
|
|
||||||
*/
|
|
||||||
if (mtstate->operation == CMD_MERGE &&
|
|
||||||
mtstate->mt_partition_tuple_routing != NULL)
|
|
||||||
ExecSetupChildParentMapForLeaf(mtstate->mt_partition_tuple_routing);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Construct mapping from each of the per-subplan partition attnos to the
|
* Construct mapping from each of the per-subplan partition attnos to the
|
||||||
* root attno. This is required when during update row movement the tuple
|
* root attno. This is required when during update row movement the tuple
|
||||||
@ -2659,106 +2478,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultRelInfo = mtstate->resultRelInfo;
|
|
||||||
|
|
||||||
if (node->mergeActionList)
|
|
||||||
{
|
|
||||||
ListCell *l;
|
|
||||||
ExprContext *econtext;
|
|
||||||
List *mergeMatchedActionStates = NIL;
|
|
||||||
List *mergeNotMatchedActionStates = NIL;
|
|
||||||
TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
|
|
||||||
|
|
||||||
mtstate->mt_merge_subcommands = 0;
|
|
||||||
|
|
||||||
if (mtstate->ps.ps_ExprContext == NULL)
|
|
||||||
ExecAssignExprContext(estate, &mtstate->ps);
|
|
||||||
|
|
||||||
econtext = mtstate->ps.ps_ExprContext;
|
|
||||||
|
|
||||||
/* initialize slot for the existing tuple */
|
|
||||||
Assert(mtstate->mt_existing == NULL);
|
|
||||||
mtstate->mt_existing =
|
|
||||||
ExecInitExtraTupleSlot(mtstate->ps.state,
|
|
||||||
mtstate->mt_partition_tuple_routing ?
|
|
||||||
NULL : relationDesc);
|
|
||||||
|
|
||||||
/* initialize slot for merge actions */
|
|
||||||
Assert(mtstate->mt_mergeproj == NULL);
|
|
||||||
mtstate->mt_mergeproj =
|
|
||||||
ExecInitExtraTupleSlot(mtstate->ps.state,
|
|
||||||
mtstate->mt_partition_tuple_routing ?
|
|
||||||
NULL : relationDesc);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a MergeActionState for each action on the mergeActionList
|
|
||||||
* and add it to either a list of matched actions or not-matched
|
|
||||||
* actions.
|
|
||||||
*/
|
|
||||||
foreach(l, node->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) lfirst(l);
|
|
||||||
MergeActionState *action_state = makeNode(MergeActionState);
|
|
||||||
TupleDesc tupDesc;
|
|
||||||
|
|
||||||
action_state->matched = action->matched;
|
|
||||||
action_state->commandType = action->commandType;
|
|
||||||
action_state->whenqual = ExecInitQual((List *) action->qual,
|
|
||||||
&mtstate->ps);
|
|
||||||
|
|
||||||
/* create target slot for this action's projection */
|
|
||||||
tupDesc = ExecTypeFromTL((List *) action->targetList,
|
|
||||||
resultRelInfo->ri_RelationDesc->rd_rel->relhasoids);
|
|
||||||
action_state->tupDesc = tupDesc;
|
|
||||||
|
|
||||||
/* build action projection state */
|
|
||||||
action_state->proj =
|
|
||||||
ExecBuildProjectionInfo(action->targetList, econtext,
|
|
||||||
mtstate->mt_mergeproj, &mtstate->ps,
|
|
||||||
resultRelInfo->ri_RelationDesc->rd_att);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We create two lists - one for WHEN MATCHED actions and one
|
|
||||||
* for WHEN NOT MATCHED actions - and stick the
|
|
||||||
* MergeActionState into the appropriate list.
|
|
||||||
*/
|
|
||||||
if (action_state->matched)
|
|
||||||
mergeMatchedActionStates =
|
|
||||||
lappend(mergeMatchedActionStates, action_state);
|
|
||||||
else
|
|
||||||
mergeNotMatchedActionStates =
|
|
||||||
lappend(mergeNotMatchedActionStates, action_state);
|
|
||||||
|
|
||||||
switch (action->commandType)
|
|
||||||
{
|
|
||||||
case CMD_INSERT:
|
|
||||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
|
|
||||||
action->targetList);
|
|
||||||
mtstate->mt_merge_subcommands |= MERGE_INSERT;
|
|
||||||
break;
|
|
||||||
case CMD_UPDATE:
|
|
||||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
|
|
||||||
action->targetList);
|
|
||||||
mtstate->mt_merge_subcommands |= MERGE_UPDATE;
|
|
||||||
break;
|
|
||||||
case CMD_DELETE:
|
|
||||||
mtstate->mt_merge_subcommands |= MERGE_DELETE;
|
|
||||||
break;
|
|
||||||
case CMD_NOTHING:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
elog(ERROR, "unknown operation");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
resultRelInfo->ri_mergeState->matchedActionStates =
|
|
||||||
mergeMatchedActionStates;
|
|
||||||
resultRelInfo->ri_mergeState->notMatchedActionStates =
|
|
||||||
mergeNotMatchedActionStates;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* select first subplan */
|
/* select first subplan */
|
||||||
mtstate->mt_whichplan = 0;
|
mtstate->mt_whichplan = 0;
|
||||||
subplan = (Plan *) linitial(node->plans);
|
subplan = (Plan *) linitial(node->plans);
|
||||||
@ -2772,7 +2491,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
* --- no need to look first. Typically, this will be a 'ctid' or
|
* --- no need to look first. Typically, this will be a 'ctid' or
|
||||||
* 'wholerow' attribute, but in the case of a foreign data wrapper it
|
* 'wholerow' attribute, but in the case of a foreign data wrapper it
|
||||||
* might be a set of junk attributes sufficient to identify the remote
|
* might be a set of junk attributes sufficient to identify the remote
|
||||||
* row. We follow this logic for MERGE, so it always has a junk attributes.
|
* row.
|
||||||
*
|
*
|
||||||
* If there are multiple result relations, each one needs its own junk
|
* If there are multiple result relations, each one needs its own junk
|
||||||
* filter. Note multiple rels are only possible for UPDATE/DELETE, so we
|
* filter. Note multiple rels are only possible for UPDATE/DELETE, so we
|
||||||
@ -2800,7 +2519,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
break;
|
break;
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_MERGE:
|
|
||||||
junk_filter_needed = true;
|
junk_filter_needed = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -2816,7 +2534,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
JunkFilter *j;
|
JunkFilter *j;
|
||||||
|
|
||||||
subplan = mtstate->mt_plans[i]->plan;
|
subplan = mtstate->mt_plans[i]->plan;
|
||||||
|
|
||||||
if (operation == CMD_INSERT || operation == CMD_UPDATE)
|
if (operation == CMD_INSERT || operation == CMD_UPDATE)
|
||||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
|
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
|
||||||
subplan->targetlist);
|
subplan->targetlist);
|
||||||
@ -2825,9 +2542,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
|
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
|
||||||
ExecInitExtraTupleSlot(estate, NULL));
|
ExecInitExtraTupleSlot(estate, NULL));
|
||||||
|
|
||||||
if (operation == CMD_UPDATE ||
|
if (operation == CMD_UPDATE || operation == CMD_DELETE)
|
||||||
operation == CMD_DELETE ||
|
|
||||||
operation == CMD_MERGE)
|
|
||||||
{
|
{
|
||||||
/* For UPDATE/DELETE, find the appropriate junk attr now */
|
/* For UPDATE/DELETE, find the appropriate junk attr now */
|
||||||
char relkind;
|
char relkind;
|
||||||
@ -2840,15 +2555,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||||||
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
|
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
|
||||||
if (!AttributeNumberIsValid(j->jf_junkAttNo))
|
if (!AttributeNumberIsValid(j->jf_junkAttNo))
|
||||||
elog(ERROR, "could not find junk ctid column");
|
elog(ERROR, "could not find junk ctid column");
|
||||||
|
|
||||||
if (operation == CMD_MERGE &&
|
|
||||||
relkind == RELKIND_PARTITIONED_TABLE)
|
|
||||||
{
|
|
||||||
j->jf_otherJunkAttNo = ExecFindJunkAttribute(j, "tableoid");
|
|
||||||
if (!AttributeNumberIsValid(j->jf_otherJunkAttNo))
|
|
||||||
elog(ERROR, "could not find junk tableoid column");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (relkind == RELKIND_FOREIGN_TABLE)
|
else if (relkind == RELKIND_FOREIGN_TABLE)
|
||||||
{
|
{
|
||||||
|
@ -2420,9 +2420,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
|
|||||||
else
|
else
|
||||||
res = SPI_OK_UPDATE;
|
res = SPI_OK_UPDATE;
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
res = SPI_OK_MERGE;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return SPI_ERROR_OPUNKNOWN;
|
return SPI_ERROR_OPUNKNOWN;
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,6 @@ _copyModifyTable(const ModifyTable *from)
|
|||||||
COPY_NODE_FIELD(partitioned_rels);
|
COPY_NODE_FIELD(partitioned_rels);
|
||||||
COPY_SCALAR_FIELD(partColsUpdated);
|
COPY_SCALAR_FIELD(partColsUpdated);
|
||||||
COPY_NODE_FIELD(resultRelations);
|
COPY_NODE_FIELD(resultRelations);
|
||||||
COPY_SCALAR_FIELD(mergeTargetRelation);
|
|
||||||
COPY_SCALAR_FIELD(resultRelIndex);
|
COPY_SCALAR_FIELD(resultRelIndex);
|
||||||
COPY_SCALAR_FIELD(rootResultRelIndex);
|
COPY_SCALAR_FIELD(rootResultRelIndex);
|
||||||
COPY_NODE_FIELD(plans);
|
COPY_NODE_FIELD(plans);
|
||||||
@ -223,8 +222,6 @@ _copyModifyTable(const ModifyTable *from)
|
|||||||
COPY_NODE_FIELD(onConflictWhere);
|
COPY_NODE_FIELD(onConflictWhere);
|
||||||
COPY_SCALAR_FIELD(exclRelRTI);
|
COPY_SCALAR_FIELD(exclRelRTI);
|
||||||
COPY_NODE_FIELD(exclRelTlist);
|
COPY_NODE_FIELD(exclRelTlist);
|
||||||
COPY_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
COPY_NODE_FIELD(mergeActionList);
|
|
||||||
|
|
||||||
return newnode;
|
return newnode;
|
||||||
}
|
}
|
||||||
@ -2980,9 +2977,6 @@ _copyQuery(const Query *from)
|
|||||||
COPY_NODE_FIELD(setOperations);
|
COPY_NODE_FIELD(setOperations);
|
||||||
COPY_NODE_FIELD(constraintDeps);
|
COPY_NODE_FIELD(constraintDeps);
|
||||||
COPY_NODE_FIELD(withCheckOptions);
|
COPY_NODE_FIELD(withCheckOptions);
|
||||||
COPY_SCALAR_FIELD(mergeTarget_relation);
|
|
||||||
COPY_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
COPY_NODE_FIELD(mergeActionList);
|
|
||||||
COPY_LOCATION_FIELD(stmt_location);
|
COPY_LOCATION_FIELD(stmt_location);
|
||||||
COPY_LOCATION_FIELD(stmt_len);
|
COPY_LOCATION_FIELD(stmt_len);
|
||||||
|
|
||||||
@ -3046,34 +3040,6 @@ _copyUpdateStmt(const UpdateStmt *from)
|
|||||||
return newnode;
|
return newnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static MergeStmt *
|
|
||||||
_copyMergeStmt(const MergeStmt *from)
|
|
||||||
{
|
|
||||||
MergeStmt *newnode = makeNode(MergeStmt);
|
|
||||||
|
|
||||||
COPY_NODE_FIELD(relation);
|
|
||||||
COPY_NODE_FIELD(source_relation);
|
|
||||||
COPY_NODE_FIELD(join_condition);
|
|
||||||
COPY_NODE_FIELD(mergeActionList);
|
|
||||||
|
|
||||||
return newnode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MergeAction *
|
|
||||||
_copyMergeAction(const MergeAction *from)
|
|
||||||
{
|
|
||||||
MergeAction *newnode = makeNode(MergeAction);
|
|
||||||
|
|
||||||
COPY_SCALAR_FIELD(matched);
|
|
||||||
COPY_SCALAR_FIELD(commandType);
|
|
||||||
COPY_NODE_FIELD(condition);
|
|
||||||
COPY_NODE_FIELD(qual);
|
|
||||||
COPY_NODE_FIELD(stmt);
|
|
||||||
COPY_NODE_FIELD(targetList);
|
|
||||||
|
|
||||||
return newnode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SelectStmt *
|
static SelectStmt *
|
||||||
_copySelectStmt(const SelectStmt *from)
|
_copySelectStmt(const SelectStmt *from)
|
||||||
{
|
{
|
||||||
@ -5136,12 +5102,6 @@ copyObjectImpl(const void *from)
|
|||||||
case T_UpdateStmt:
|
case T_UpdateStmt:
|
||||||
retval = _copyUpdateStmt(from);
|
retval = _copyUpdateStmt(from);
|
||||||
break;
|
break;
|
||||||
case T_MergeStmt:
|
|
||||||
retval = _copyMergeStmt(from);
|
|
||||||
break;
|
|
||||||
case T_MergeAction:
|
|
||||||
retval = _copyMergeAction(from);
|
|
||||||
break;
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
retval = _copySelectStmt(from);
|
retval = _copySelectStmt(from);
|
||||||
break;
|
break;
|
||||||
|
@ -987,8 +987,6 @@ _equalQuery(const Query *a, const Query *b)
|
|||||||
COMPARE_NODE_FIELD(setOperations);
|
COMPARE_NODE_FIELD(setOperations);
|
||||||
COMPARE_NODE_FIELD(constraintDeps);
|
COMPARE_NODE_FIELD(constraintDeps);
|
||||||
COMPARE_NODE_FIELD(withCheckOptions);
|
COMPARE_NODE_FIELD(withCheckOptions);
|
||||||
COMPARE_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
COMPARE_NODE_FIELD(mergeActionList);
|
|
||||||
COMPARE_LOCATION_FIELD(stmt_location);
|
COMPARE_LOCATION_FIELD(stmt_location);
|
||||||
COMPARE_LOCATION_FIELD(stmt_len);
|
COMPARE_LOCATION_FIELD(stmt_len);
|
||||||
|
|
||||||
@ -1044,30 +1042,6 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
_equalMergeStmt(const MergeStmt *a, const MergeStmt *b)
|
|
||||||
{
|
|
||||||
COMPARE_NODE_FIELD(relation);
|
|
||||||
COMPARE_NODE_FIELD(source_relation);
|
|
||||||
COMPARE_NODE_FIELD(join_condition);
|
|
||||||
COMPARE_NODE_FIELD(mergeActionList);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
_equalMergeAction(const MergeAction *a, const MergeAction *b)
|
|
||||||
{
|
|
||||||
COMPARE_SCALAR_FIELD(matched);
|
|
||||||
COMPARE_SCALAR_FIELD(commandType);
|
|
||||||
COMPARE_NODE_FIELD(condition);
|
|
||||||
COMPARE_NODE_FIELD(qual);
|
|
||||||
COMPARE_NODE_FIELD(stmt);
|
|
||||||
COMPARE_NODE_FIELD(targetList);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
_equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
|
_equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
|
||||||
{
|
{
|
||||||
@ -3259,12 +3233,6 @@ equal(const void *a, const void *b)
|
|||||||
case T_UpdateStmt:
|
case T_UpdateStmt:
|
||||||
retval = _equalUpdateStmt(a, b);
|
retval = _equalUpdateStmt(a, b);
|
||||||
break;
|
break;
|
||||||
case T_MergeStmt:
|
|
||||||
retval = _equalMergeStmt(a, b);
|
|
||||||
break;
|
|
||||||
case T_MergeAction:
|
|
||||||
retval = _equalMergeAction(a, b);
|
|
||||||
break;
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
retval = _equalSelectStmt(a, b);
|
retval = _equalSelectStmt(a, b);
|
||||||
break;
|
break;
|
||||||
|
@ -2146,16 +2146,6 @@ expression_tree_walker(Node *node,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_MergeAction:
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) node;
|
|
||||||
|
|
||||||
if (walker(action->targetList, context))
|
|
||||||
return true;
|
|
||||||
if (walker(action->qual, context))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case T_JoinExpr:
|
case T_JoinExpr:
|
||||||
{
|
{
|
||||||
JoinExpr *join = (JoinExpr *) node;
|
JoinExpr *join = (JoinExpr *) node;
|
||||||
@ -2265,10 +2255,6 @@ query_tree_walker(Query *query,
|
|||||||
return true;
|
return true;
|
||||||
if (walker((Node *) query->onConflict, context))
|
if (walker((Node *) query->onConflict, context))
|
||||||
return true;
|
return true;
|
||||||
if (walker((Node *) query->mergeSourceTargetList, context))
|
|
||||||
return true;
|
|
||||||
if (walker((Node *) query->mergeActionList, context))
|
|
||||||
return true;
|
|
||||||
if (walker((Node *) query->returningList, context))
|
if (walker((Node *) query->returningList, context))
|
||||||
return true;
|
return true;
|
||||||
if (walker((Node *) query->jointree, context))
|
if (walker((Node *) query->jointree, context))
|
||||||
@ -2946,18 +2932,6 @@ expression_tree_mutator(Node *node,
|
|||||||
return (Node *) newnode;
|
return (Node *) newnode;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_MergeAction:
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) node;
|
|
||||||
MergeAction *newnode;
|
|
||||||
|
|
||||||
FLATCOPY(newnode, action, MergeAction);
|
|
||||||
MUTATE(newnode->qual, action->qual, Node *);
|
|
||||||
MUTATE(newnode->targetList, action->targetList, List *);
|
|
||||||
|
|
||||||
return (Node *) newnode;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case T_JoinExpr:
|
case T_JoinExpr:
|
||||||
{
|
{
|
||||||
JoinExpr *join = (JoinExpr *) node;
|
JoinExpr *join = (JoinExpr *) node;
|
||||||
@ -3109,8 +3083,6 @@ query_tree_mutator(Query *query,
|
|||||||
MUTATE(query->targetList, query->targetList, List *);
|
MUTATE(query->targetList, query->targetList, List *);
|
||||||
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
|
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
|
||||||
MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
|
MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
|
||||||
MUTATE(query->mergeSourceTargetList, query->mergeSourceTargetList, List *);
|
|
||||||
MUTATE(query->mergeActionList, query->mergeActionList, List *);
|
|
||||||
MUTATE(query->returningList, query->returningList, List *);
|
MUTATE(query->returningList, query->returningList, List *);
|
||||||
MUTATE(query->jointree, query->jointree, FromExpr *);
|
MUTATE(query->jointree, query->jointree, FromExpr *);
|
||||||
MUTATE(query->setOperations, query->setOperations, Node *);
|
MUTATE(query->setOperations, query->setOperations, Node *);
|
||||||
@ -3252,9 +3224,9 @@ query_or_expression_tree_mutator(Node *node,
|
|||||||
* boundaries: we descend to everything that's possibly interesting.
|
* boundaries: we descend to everything that's possibly interesting.
|
||||||
*
|
*
|
||||||
* Currently, the node type coverage here extends only to DML statements
|
* Currently, the node type coverage here extends only to DML statements
|
||||||
* (SELECT/INSERT/UPDATE/DELETE/MERGE) and nodes that can appear in them,
|
* (SELECT/INSERT/UPDATE/DELETE) and nodes that can appear in them, because
|
||||||
* because this is used mainly during analysis of CTEs, and only DML
|
* this is used mainly during analysis of CTEs, and only DML statements can
|
||||||
* statements can appear in CTEs.
|
* appear in CTEs.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
raw_expression_tree_walker(Node *node,
|
raw_expression_tree_walker(Node *node,
|
||||||
@ -3434,20 +3406,6 @@ raw_expression_tree_walker(Node *node,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_MergeStmt:
|
|
||||||
{
|
|
||||||
MergeStmt *stmt = (MergeStmt *) node;
|
|
||||||
|
|
||||||
if (walker(stmt->relation, context))
|
|
||||||
return true;
|
|
||||||
if (walker(stmt->source_relation, context))
|
|
||||||
return true;
|
|
||||||
if (walker(stmt->join_condition, context))
|
|
||||||
return true;
|
|
||||||
if (walker(stmt->mergeActionList, context))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
{
|
{
|
||||||
SelectStmt *stmt = (SelectStmt *) node;
|
SelectStmt *stmt = (SelectStmt *) node;
|
||||||
|
@ -375,7 +375,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
|
|||||||
WRITE_NODE_FIELD(partitioned_rels);
|
WRITE_NODE_FIELD(partitioned_rels);
|
||||||
WRITE_BOOL_FIELD(partColsUpdated);
|
WRITE_BOOL_FIELD(partColsUpdated);
|
||||||
WRITE_NODE_FIELD(resultRelations);
|
WRITE_NODE_FIELD(resultRelations);
|
||||||
WRITE_INT_FIELD(mergeTargetRelation);
|
|
||||||
WRITE_INT_FIELD(resultRelIndex);
|
WRITE_INT_FIELD(resultRelIndex);
|
||||||
WRITE_INT_FIELD(rootResultRelIndex);
|
WRITE_INT_FIELD(rootResultRelIndex);
|
||||||
WRITE_NODE_FIELD(plans);
|
WRITE_NODE_FIELD(plans);
|
||||||
@ -391,21 +390,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
|
|||||||
WRITE_NODE_FIELD(onConflictWhere);
|
WRITE_NODE_FIELD(onConflictWhere);
|
||||||
WRITE_UINT_FIELD(exclRelRTI);
|
WRITE_UINT_FIELD(exclRelRTI);
|
||||||
WRITE_NODE_FIELD(exclRelTlist);
|
WRITE_NODE_FIELD(exclRelTlist);
|
||||||
WRITE_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
WRITE_NODE_FIELD(mergeActionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_outMergeAction(StringInfo str, const MergeAction *node)
|
|
||||||
{
|
|
||||||
WRITE_NODE_TYPE("MERGEACTION");
|
|
||||||
|
|
||||||
WRITE_BOOL_FIELD(matched);
|
|
||||||
WRITE_ENUM_FIELD(commandType, CmdType);
|
|
||||||
WRITE_NODE_FIELD(condition);
|
|
||||||
WRITE_NODE_FIELD(qual);
|
|
||||||
/* We don't dump the stmt node */
|
|
||||||
WRITE_NODE_FIELD(targetList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2130,7 +2114,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
|
|||||||
WRITE_NODE_FIELD(partitioned_rels);
|
WRITE_NODE_FIELD(partitioned_rels);
|
||||||
WRITE_BOOL_FIELD(partColsUpdated);
|
WRITE_BOOL_FIELD(partColsUpdated);
|
||||||
WRITE_NODE_FIELD(resultRelations);
|
WRITE_NODE_FIELD(resultRelations);
|
||||||
WRITE_INT_FIELD(mergeTargetRelation);
|
|
||||||
WRITE_NODE_FIELD(subpaths);
|
WRITE_NODE_FIELD(subpaths);
|
||||||
WRITE_NODE_FIELD(subroots);
|
WRITE_NODE_FIELD(subroots);
|
||||||
WRITE_NODE_FIELD(withCheckOptionLists);
|
WRITE_NODE_FIELD(withCheckOptionLists);
|
||||||
@ -2138,8 +2121,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
|
|||||||
WRITE_NODE_FIELD(rowMarks);
|
WRITE_NODE_FIELD(rowMarks);
|
||||||
WRITE_NODE_FIELD(onconflict);
|
WRITE_NODE_FIELD(onconflict);
|
||||||
WRITE_INT_FIELD(epqParam);
|
WRITE_INT_FIELD(epqParam);
|
||||||
WRITE_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
WRITE_NODE_FIELD(mergeActionList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2961,9 +2942,6 @@ _outQuery(StringInfo str, const Query *node)
|
|||||||
WRITE_NODE_FIELD(setOperations);
|
WRITE_NODE_FIELD(setOperations);
|
||||||
WRITE_NODE_FIELD(constraintDeps);
|
WRITE_NODE_FIELD(constraintDeps);
|
||||||
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
|
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
|
||||||
WRITE_INT_FIELD(mergeTarget_relation);
|
|
||||||
WRITE_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
WRITE_NODE_FIELD(mergeActionList);
|
|
||||||
WRITE_LOCATION_FIELD(stmt_location);
|
WRITE_LOCATION_FIELD(stmt_location);
|
||||||
WRITE_LOCATION_FIELD(stmt_len);
|
WRITE_LOCATION_FIELD(stmt_len);
|
||||||
}
|
}
|
||||||
@ -3679,9 +3657,6 @@ outNode(StringInfo str, const void *obj)
|
|||||||
case T_ModifyTable:
|
case T_ModifyTable:
|
||||||
_outModifyTable(str, obj);
|
_outModifyTable(str, obj);
|
||||||
break;
|
break;
|
||||||
case T_MergeAction:
|
|
||||||
_outMergeAction(str, obj);
|
|
||||||
break;
|
|
||||||
case T_Append:
|
case T_Append:
|
||||||
_outAppend(str, obj);
|
_outAppend(str, obj);
|
||||||
break;
|
break;
|
||||||
|
@ -270,9 +270,6 @@ _readQuery(void)
|
|||||||
READ_NODE_FIELD(setOperations);
|
READ_NODE_FIELD(setOperations);
|
||||||
READ_NODE_FIELD(constraintDeps);
|
READ_NODE_FIELD(constraintDeps);
|
||||||
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
|
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
|
||||||
READ_INT_FIELD(mergeTarget_relation);
|
|
||||||
READ_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
READ_NODE_FIELD(mergeActionList);
|
|
||||||
READ_LOCATION_FIELD(stmt_location);
|
READ_LOCATION_FIELD(stmt_location);
|
||||||
READ_LOCATION_FIELD(stmt_len);
|
READ_LOCATION_FIELD(stmt_len);
|
||||||
|
|
||||||
@ -1579,7 +1576,6 @@ _readModifyTable(void)
|
|||||||
READ_NODE_FIELD(partitioned_rels);
|
READ_NODE_FIELD(partitioned_rels);
|
||||||
READ_BOOL_FIELD(partColsUpdated);
|
READ_BOOL_FIELD(partColsUpdated);
|
||||||
READ_NODE_FIELD(resultRelations);
|
READ_NODE_FIELD(resultRelations);
|
||||||
READ_INT_FIELD(mergeTargetRelation);
|
|
||||||
READ_INT_FIELD(resultRelIndex);
|
READ_INT_FIELD(resultRelIndex);
|
||||||
READ_INT_FIELD(rootResultRelIndex);
|
READ_INT_FIELD(rootResultRelIndex);
|
||||||
READ_NODE_FIELD(plans);
|
READ_NODE_FIELD(plans);
|
||||||
@ -1595,8 +1591,6 @@ _readModifyTable(void)
|
|||||||
READ_NODE_FIELD(onConflictWhere);
|
READ_NODE_FIELD(onConflictWhere);
|
||||||
READ_UINT_FIELD(exclRelRTI);
|
READ_UINT_FIELD(exclRelRTI);
|
||||||
READ_NODE_FIELD(exclRelTlist);
|
READ_NODE_FIELD(exclRelTlist);
|
||||||
READ_NODE_FIELD(mergeSourceTargetList);
|
|
||||||
READ_NODE_FIELD(mergeActionList);
|
|
||||||
|
|
||||||
READ_DONE();
|
READ_DONE();
|
||||||
}
|
}
|
||||||
|
@ -288,13 +288,9 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
|
|||||||
CmdType operation, bool canSetTag,
|
CmdType operation, bool canSetTag,
|
||||||
Index nominalRelation, List *partitioned_rels,
|
Index nominalRelation, List *partitioned_rels,
|
||||||
bool partColsUpdated,
|
bool partColsUpdated,
|
||||||
List *resultRelations,
|
List *resultRelations, List *subplans,
|
||||||
Index mergeTargetRelation,
|
|
||||||
List *subplans,
|
|
||||||
List *withCheckOptionLists, List *returningLists,
|
List *withCheckOptionLists, List *returningLists,
|
||||||
List *rowMarks, OnConflictExpr *onconflict,
|
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
|
||||||
List *mergeSourceTargetList,
|
|
||||||
List *mergeActionList, int epqParam);
|
|
||||||
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
|
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
|
||||||
GatherMergePath *best_path);
|
GatherMergePath *best_path);
|
||||||
|
|
||||||
@ -2450,14 +2446,11 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
|
|||||||
best_path->partitioned_rels,
|
best_path->partitioned_rels,
|
||||||
best_path->partColsUpdated,
|
best_path->partColsUpdated,
|
||||||
best_path->resultRelations,
|
best_path->resultRelations,
|
||||||
best_path->mergeTargetRelation,
|
|
||||||
subplans,
|
subplans,
|
||||||
best_path->withCheckOptionLists,
|
best_path->withCheckOptionLists,
|
||||||
best_path->returningLists,
|
best_path->returningLists,
|
||||||
best_path->rowMarks,
|
best_path->rowMarks,
|
||||||
best_path->onconflict,
|
best_path->onconflict,
|
||||||
best_path->mergeSourceTargetList,
|
|
||||||
best_path->mergeActionList,
|
|
||||||
best_path->epqParam);
|
best_path->epqParam);
|
||||||
|
|
||||||
copy_generic_path_info(&plan->plan, &best_path->path);
|
copy_generic_path_info(&plan->plan, &best_path->path);
|
||||||
@ -6524,13 +6517,9 @@ make_modifytable(PlannerInfo *root,
|
|||||||
CmdType operation, bool canSetTag,
|
CmdType operation, bool canSetTag,
|
||||||
Index nominalRelation, List *partitioned_rels,
|
Index nominalRelation, List *partitioned_rels,
|
||||||
bool partColsUpdated,
|
bool partColsUpdated,
|
||||||
List *resultRelations,
|
List *resultRelations, List *subplans,
|
||||||
Index mergeTargetRelation,
|
|
||||||
List *subplans,
|
|
||||||
List *withCheckOptionLists, List *returningLists,
|
List *withCheckOptionLists, List *returningLists,
|
||||||
List *rowMarks, OnConflictExpr *onconflict,
|
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
|
||||||
List *mergeSourceTargetList,
|
|
||||||
List *mergeActionList, int epqParam)
|
|
||||||
{
|
{
|
||||||
ModifyTable *node = makeNode(ModifyTable);
|
ModifyTable *node = makeNode(ModifyTable);
|
||||||
List *fdw_private_list;
|
List *fdw_private_list;
|
||||||
@ -6556,7 +6545,6 @@ make_modifytable(PlannerInfo *root,
|
|||||||
node->partitioned_rels = partitioned_rels;
|
node->partitioned_rels = partitioned_rels;
|
||||||
node->partColsUpdated = partColsUpdated;
|
node->partColsUpdated = partColsUpdated;
|
||||||
node->resultRelations = resultRelations;
|
node->resultRelations = resultRelations;
|
||||||
node->mergeTargetRelation = mergeTargetRelation;
|
|
||||||
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
|
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
|
||||||
node->rootResultRelIndex = -1; /* will be set correctly in setrefs.c */
|
node->rootResultRelIndex = -1; /* will be set correctly in setrefs.c */
|
||||||
node->plans = subplans;
|
node->plans = subplans;
|
||||||
@ -6589,8 +6577,6 @@ make_modifytable(PlannerInfo *root,
|
|||||||
node->withCheckOptionLists = withCheckOptionLists;
|
node->withCheckOptionLists = withCheckOptionLists;
|
||||||
node->returningLists = returningLists;
|
node->returningLists = returningLists;
|
||||||
node->rowMarks = rowMarks;
|
node->rowMarks = rowMarks;
|
||||||
node->mergeSourceTargetList = mergeSourceTargetList;
|
|
||||||
node->mergeActionList = mergeActionList;
|
|
||||||
node->epqParam = epqParam;
|
node->epqParam = epqParam;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -794,24 +794,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||||||
/* exclRelTlist contains only Vars, so no preprocessing needed */
|
/* exclRelTlist contains only Vars, so no preprocessing needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(l, parse->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) lfirst(l);
|
|
||||||
|
|
||||||
action->targetList = (List *)
|
|
||||||
preprocess_expression(root,
|
|
||||||
(Node *) action->targetList,
|
|
||||||
EXPRKIND_TARGET);
|
|
||||||
action->qual =
|
|
||||||
preprocess_expression(root,
|
|
||||||
(Node *) action->qual,
|
|
||||||
EXPRKIND_QUAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse->mergeSourceTargetList = (List *)
|
|
||||||
preprocess_expression(root, (Node *) parse->mergeSourceTargetList,
|
|
||||||
EXPRKIND_TARGET);
|
|
||||||
|
|
||||||
root->append_rel_list = (List *)
|
root->append_rel_list = (List *)
|
||||||
preprocess_expression(root, (Node *) root->append_rel_list,
|
preprocess_expression(root, (Node *) root->append_rel_list,
|
||||||
EXPRKIND_APPINFO);
|
EXPRKIND_APPINFO);
|
||||||
@ -1553,7 +1535,6 @@ inheritance_planner(PlannerInfo *root)
|
|||||||
subroot->parse->returningList);
|
subroot->parse->returningList);
|
||||||
|
|
||||||
Assert(!parse->onConflict);
|
Assert(!parse->onConflict);
|
||||||
Assert(parse->mergeActionList == NIL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Result path must go into outer query's FINAL upperrel */
|
/* Result path must go into outer query's FINAL upperrel */
|
||||||
@ -1612,15 +1593,12 @@ inheritance_planner(PlannerInfo *root)
|
|||||||
partitioned_rels,
|
partitioned_rels,
|
||||||
partColsUpdated,
|
partColsUpdated,
|
||||||
resultRelations,
|
resultRelations,
|
||||||
0,
|
|
||||||
subpaths,
|
subpaths,
|
||||||
subroots,
|
subroots,
|
||||||
withCheckOptionLists,
|
withCheckOptionLists,
|
||||||
returningLists,
|
returningLists,
|
||||||
rowMarks,
|
rowMarks,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
SS_assign_special_param(root)));
|
SS_assign_special_param(root)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2151,8 +2129,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If this is an INSERT/UPDATE/DELETE/MERGE, and we're not being
|
* If this is an INSERT/UPDATE/DELETE, and we're not being called from
|
||||||
* called from inheritance_planner, add the ModifyTable node.
|
* inheritance_planner, add the ModifyTable node.
|
||||||
*/
|
*/
|
||||||
if (parse->commandType != CMD_SELECT && !inheritance_update)
|
if (parse->commandType != CMD_SELECT && !inheritance_update)
|
||||||
{
|
{
|
||||||
@ -2192,15 +2170,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
|
|||||||
NIL,
|
NIL,
|
||||||
false,
|
false,
|
||||||
list_make1_int(parse->resultRelation),
|
list_make1_int(parse->resultRelation),
|
||||||
parse->mergeTarget_relation,
|
|
||||||
list_make1(path),
|
list_make1(path),
|
||||||
list_make1(root),
|
list_make1(root),
|
||||||
withCheckOptionLists,
|
withCheckOptionLists,
|
||||||
returningLists,
|
returningLists,
|
||||||
rowMarks,
|
rowMarks,
|
||||||
parse->onConflict,
|
parse->onConflict,
|
||||||
parse->mergeSourceTargetList,
|
|
||||||
parse->mergeActionList,
|
|
||||||
SS_assign_special_param(root));
|
SS_assign_special_param(root));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,60 +851,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
|
|||||||
fix_scan_list(root, splan->exclRelTlist, rtoffset);
|
fix_scan_list(root, splan->exclRelTlist, rtoffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* The MERGE produces the target rows by performing a right
|
|
||||||
* join between the target relation and the source relation
|
|
||||||
* (which could be a plain relation or a subquery). The INSERT
|
|
||||||
* and UPDATE actions of the MERGE requires access to the
|
|
||||||
* columns from the source relation. We arrange things so that
|
|
||||||
* the source relation attributes are available as INNER_VAR
|
|
||||||
* and the target relation attributes are available from the
|
|
||||||
* scan tuple.
|
|
||||||
*/
|
|
||||||
if (splan->mergeActionList != NIL)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* mergeSourceTargetList is already setup correctly to
|
|
||||||
* include all Vars coming from the source relation. So we
|
|
||||||
* fix the targetList of individual action nodes by
|
|
||||||
* ensuring that the source relation Vars are referenced
|
|
||||||
* as INNER_VAR. Note that for this to work correctly,
|
|
||||||
* during execution, the ecxt_innertuple must be set to
|
|
||||||
* the tuple obtained from the source relation.
|
|
||||||
*
|
|
||||||
* We leave the Vars from the result relation (i.e. the
|
|
||||||
* target relation) unchanged i.e. those Vars would be
|
|
||||||
* picked from the scan slot. So during execution, we must
|
|
||||||
* ensure that ecxt_scantuple is setup correctly to refer
|
|
||||||
* to the tuple from the target relation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
indexed_tlist *itlist;
|
|
||||||
|
|
||||||
itlist = build_tlist_index(splan->mergeSourceTargetList);
|
|
||||||
|
|
||||||
splan->mergeTargetRelation += rtoffset;
|
|
||||||
|
|
||||||
foreach(l, splan->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) lfirst(l);
|
|
||||||
|
|
||||||
/* Fix targetList of each action. */
|
|
||||||
action->targetList = fix_join_expr(root,
|
|
||||||
action->targetList,
|
|
||||||
NULL, itlist,
|
|
||||||
linitial_int(splan->resultRelations),
|
|
||||||
rtoffset);
|
|
||||||
|
|
||||||
/* Fix quals too. */
|
|
||||||
action->qual = (Node *) fix_join_expr(root,
|
|
||||||
(List *) action->qual,
|
|
||||||
NULL, itlist,
|
|
||||||
linitial_int(splan->resultRelations),
|
|
||||||
rtoffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
splan->nominalRelation += rtoffset;
|
splan->nominalRelation += rtoffset;
|
||||||
splan->exclRelRTI += rtoffset;
|
splan->exclRelRTI += rtoffset;
|
||||||
|
|
||||||
|
@ -118,46 +118,6 @@ preprocess_targetlist(PlannerInfo *root)
|
|||||||
tlist = expand_targetlist(tlist, command_type,
|
tlist = expand_targetlist(tlist, command_type,
|
||||||
result_relation, target_relation);
|
result_relation, target_relation);
|
||||||
|
|
||||||
if (command_type == CMD_MERGE)
|
|
||||||
{
|
|
||||||
ListCell *l;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For MERGE, add any junk column(s) needed to allow the executor to
|
|
||||||
* identify the rows to be updated or deleted, with different
|
|
||||||
* handling for partitioned tables.
|
|
||||||
*/
|
|
||||||
rewriteTargetListMerge(parse, target_relation);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For MERGE command, handle targetlist of each MergeAction separately.
|
|
||||||
* Give the same treatment to MergeAction->targetList as we would have
|
|
||||||
* given to a regular INSERT/UPDATE/DELETE.
|
|
||||||
*/
|
|
||||||
foreach(l, parse->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) lfirst(l);
|
|
||||||
|
|
||||||
switch (action->commandType)
|
|
||||||
{
|
|
||||||
case CMD_INSERT:
|
|
||||||
case CMD_UPDATE:
|
|
||||||
action->targetList = expand_targetlist(action->targetList,
|
|
||||||
action->commandType,
|
|
||||||
result_relation,
|
|
||||||
target_relation);
|
|
||||||
break;
|
|
||||||
case CMD_DELETE:
|
|
||||||
break;
|
|
||||||
case CMD_NOTHING:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
elog(ERROR, "unknown action in MERGE WHEN clause");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add necessary junk columns for rowmarked rels. These values are needed
|
* Add necessary junk columns for rowmarked rels. These values are needed
|
||||||
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
|
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
|
||||||
@ -388,7 +348,6 @@ expand_targetlist(List *tlist, int command_type,
|
|||||||
true /* byval */ );
|
true /* byval */ );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
if (!att_tup->attisdropped)
|
if (!att_tup->attisdropped)
|
||||||
{
|
{
|
||||||
|
@ -3284,21 +3284,17 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
|
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
|
||||||
* 'onconflict' is the ON CONFLICT clause, or NULL
|
* 'onconflict' is the ON CONFLICT clause, or NULL
|
||||||
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
|
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
|
||||||
* 'mergeActionList' is a list of MERGE actions
|
|
||||||
*/
|
*/
|
||||||
ModifyTablePath *
|
ModifyTablePath *
|
||||||
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
|
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
CmdType operation, bool canSetTag,
|
CmdType operation, bool canSetTag,
|
||||||
Index nominalRelation, List *partitioned_rels,
|
Index nominalRelation, List *partitioned_rels,
|
||||||
bool partColsUpdated,
|
bool partColsUpdated,
|
||||||
List *resultRelations,
|
List *resultRelations, List *subpaths,
|
||||||
Index mergeTargetRelation,
|
|
||||||
List *subpaths,
|
|
||||||
List *subroots,
|
List *subroots,
|
||||||
List *withCheckOptionLists, List *returningLists,
|
List *withCheckOptionLists, List *returningLists,
|
||||||
List *rowMarks, OnConflictExpr *onconflict,
|
List *rowMarks, OnConflictExpr *onconflict,
|
||||||
List *mergeSourceTargetList,
|
int epqParam)
|
||||||
List *mergeActionList, int epqParam)
|
|
||||||
{
|
{
|
||||||
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
|
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
|
||||||
double total_size;
|
double total_size;
|
||||||
@ -3363,7 +3359,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
pathnode->partitioned_rels = list_copy(partitioned_rels);
|
pathnode->partitioned_rels = list_copy(partitioned_rels);
|
||||||
pathnode->partColsUpdated = partColsUpdated;
|
pathnode->partColsUpdated = partColsUpdated;
|
||||||
pathnode->resultRelations = resultRelations;
|
pathnode->resultRelations = resultRelations;
|
||||||
pathnode->mergeTargetRelation = mergeTargetRelation;
|
|
||||||
pathnode->subpaths = subpaths;
|
pathnode->subpaths = subpaths;
|
||||||
pathnode->subroots = subroots;
|
pathnode->subroots = subroots;
|
||||||
pathnode->withCheckOptionLists = withCheckOptionLists;
|
pathnode->withCheckOptionLists = withCheckOptionLists;
|
||||||
@ -3371,8 +3366,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
pathnode->rowMarks = rowMarks;
|
pathnode->rowMarks = rowMarks;
|
||||||
pathnode->onconflict = onconflict;
|
pathnode->onconflict = onconflict;
|
||||||
pathnode->epqParam = epqParam;
|
pathnode->epqParam = epqParam;
|
||||||
pathnode->mergeSourceTargetList = mergeSourceTargetList;
|
|
||||||
pathnode->mergeActionList = mergeActionList;
|
|
||||||
|
|
||||||
return pathnode;
|
return pathnode;
|
||||||
}
|
}
|
||||||
|
@ -1835,10 +1835,6 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
|
|||||||
trigDesc->trig_delete_before_row))
|
trigDesc->trig_delete_before_row))
|
||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
|
|
||||||
case CMD_MERGE:
|
|
||||||
result = false;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized CmdType: %d", (int) event);
|
elog(ERROR, "unrecognized CmdType: %d", (int) event);
|
||||||
break;
|
break;
|
||||||
|
@ -14,7 +14,7 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
|
|||||||
|
|
||||||
OBJS= analyze.o gram.o scan.o parser.o \
|
OBJS= analyze.o gram.o scan.o parser.o \
|
||||||
parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
|
parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
|
||||||
parse_enr.o parse_expr.o parse_func.o parse_merge.o parse_node.o parse_oper.o \
|
parse_enr.o parse_expr.o parse_func.o parse_node.o parse_oper.o \
|
||||||
parse_param.o parse_relation.o parse_target.o parse_type.o \
|
parse_param.o parse_relation.o parse_target.o parse_type.o \
|
||||||
parse_utilcmd.o scansup.o
|
parse_utilcmd.o scansup.o
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@
|
|||||||
#include "parser/parse_cte.h"
|
#include "parser/parse_cte.h"
|
||||||
#include "parser/parse_expr.h"
|
#include "parser/parse_expr.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
#include "parser/parse_merge.h"
|
|
||||||
#include "parser/parse_oper.h"
|
#include "parser/parse_oper.h"
|
||||||
#include "parser/parse_param.h"
|
#include "parser/parse_param.h"
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
@ -54,6 +53,9 @@ post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
|
|||||||
static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree);
|
static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree);
|
||||||
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
|
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
|
||||||
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
|
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
|
||||||
|
static List *transformInsertRow(ParseState *pstate, List *exprlist,
|
||||||
|
List *stmtcols, List *icolumns, List *attrnos,
|
||||||
|
bool strip_indirection);
|
||||||
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
|
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
|
||||||
OnConflictClause *onConflictClause);
|
OnConflictClause *onConflictClause);
|
||||||
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
|
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
|
||||||
@ -66,6 +68,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
|
|||||||
Node *larg, List *nrtargetlist);
|
Node *larg, List *nrtargetlist);
|
||||||
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
|
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
|
||||||
static List *transformReturningList(ParseState *pstate, List *returningList);
|
static List *transformReturningList(ParseState *pstate, List *returningList);
|
||||||
|
static List *transformUpdateTargetList(ParseState *pstate,
|
||||||
|
List *targetList);
|
||||||
static Query *transformDeclareCursorStmt(ParseState *pstate,
|
static Query *transformDeclareCursorStmt(ParseState *pstate,
|
||||||
DeclareCursorStmt *stmt);
|
DeclareCursorStmt *stmt);
|
||||||
static Query *transformExplainStmt(ParseState *pstate,
|
static Query *transformExplainStmt(ParseState *pstate,
|
||||||
@ -263,7 +267,6 @@ transformStmt(ParseState *pstate, Node *parseTree)
|
|||||||
case T_InsertStmt:
|
case T_InsertStmt:
|
||||||
case T_UpdateStmt:
|
case T_UpdateStmt:
|
||||||
case T_DeleteStmt:
|
case T_DeleteStmt:
|
||||||
case T_MergeStmt:
|
|
||||||
(void) test_raw_expression_coverage(parseTree, NULL);
|
(void) test_raw_expression_coverage(parseTree, NULL);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -288,10 +291,6 @@ transformStmt(ParseState *pstate, Node *parseTree)
|
|||||||
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
|
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_MergeStmt:
|
|
||||||
result = transformMergeStmt(pstate, (MergeStmt *) parseTree);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
{
|
{
|
||||||
SelectStmt *n = (SelectStmt *) parseTree;
|
SelectStmt *n = (SelectStmt *) parseTree;
|
||||||
@ -367,7 +366,6 @@ analyze_requires_snapshot(RawStmt *parseTree)
|
|||||||
case T_InsertStmt:
|
case T_InsertStmt:
|
||||||
case T_DeleteStmt:
|
case T_DeleteStmt:
|
||||||
case T_UpdateStmt:
|
case T_UpdateStmt:
|
||||||
case T_MergeStmt:
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
@ -898,7 +896,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
|||||||
* attrnos: integer column numbers (must be same length as icolumns)
|
* attrnos: integer column numbers (must be same length as icolumns)
|
||||||
* strip_indirection: if true, remove any field/array assignment nodes
|
* strip_indirection: if true, remove any field/array assignment nodes
|
||||||
*/
|
*/
|
||||||
List *
|
static List *
|
||||||
transformInsertRow(ParseState *pstate, List *exprlist,
|
transformInsertRow(ParseState *pstate, List *exprlist,
|
||||||
List *stmtcols, List *icolumns, List *attrnos,
|
List *stmtcols, List *icolumns, List *attrnos,
|
||||||
bool strip_indirection)
|
bool strip_indirection)
|
||||||
@ -2262,9 +2260,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* transformUpdateTargetList -
|
* transformUpdateTargetList -
|
||||||
* handle SET clause in UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE
|
* handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
|
||||||
*/
|
*/
|
||||||
List *
|
static List *
|
||||||
transformUpdateTargetList(ParseState *pstate, List *origTlist)
|
transformUpdateTargetList(ParseState *pstate, List *origTlist)
|
||||||
{
|
{
|
||||||
List *tlist = NIL;
|
List *tlist = NIL;
|
||||||
|
@ -282,7 +282,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
|
CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
|
||||||
CreatePublicationStmt AlterPublicationStmt
|
CreatePublicationStmt AlterPublicationStmt
|
||||||
CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt
|
CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt
|
||||||
MergeStmt
|
|
||||||
|
|
||||||
%type <node> select_no_parens select_with_parens select_clause
|
%type <node> select_no_parens select_with_parens select_clause
|
||||||
simple_select values_clause
|
simple_select values_clause
|
||||||
@ -585,10 +584,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
%type <list> hash_partbound partbound_datum_list range_datum_list
|
%type <list> hash_partbound partbound_datum_list range_datum_list
|
||||||
%type <defelt> hash_partbound_elem
|
%type <defelt> hash_partbound_elem
|
||||||
|
|
||||||
%type <node> merge_when_clause opt_and_condition
|
|
||||||
%type <list> merge_when_list
|
|
||||||
%type <node> merge_update merge_delete merge_insert
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Non-keyword token types. These are hard-wired into the "flex" lexer.
|
* Non-keyword token types. These are hard-wired into the "flex" lexer.
|
||||||
* They must be listed first so that their numeric codes do not depend on
|
* They must be listed first so that their numeric codes do not depend on
|
||||||
@ -656,8 +651,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
|
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
|
||||||
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
|
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
|
||||||
|
|
||||||
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
|
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
|
||||||
MINUTE_P MINVALUE MODE MONTH_P MOVE
|
|
||||||
|
|
||||||
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
|
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
|
||||||
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
|
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
|
||||||
@ -926,7 +920,6 @@ stmt :
|
|||||||
| RefreshMatViewStmt
|
| RefreshMatViewStmt
|
||||||
| LoadStmt
|
| LoadStmt
|
||||||
| LockStmt
|
| LockStmt
|
||||||
| MergeStmt
|
|
||||||
| NotifyStmt
|
| NotifyStmt
|
||||||
| PrepareStmt
|
| PrepareStmt
|
||||||
| ReassignOwnedStmt
|
| ReassignOwnedStmt
|
||||||
@ -10667,7 +10660,6 @@ ExplainableStmt:
|
|||||||
| InsertStmt
|
| InsertStmt
|
||||||
| UpdateStmt
|
| UpdateStmt
|
||||||
| DeleteStmt
|
| DeleteStmt
|
||||||
| MergeStmt
|
|
||||||
| DeclareCursorStmt
|
| DeclareCursorStmt
|
||||||
| CreateAsStmt
|
| CreateAsStmt
|
||||||
| CreateMatViewStmt
|
| CreateMatViewStmt
|
||||||
@ -10730,7 +10722,6 @@ PreparableStmt:
|
|||||||
| InsertStmt
|
| InsertStmt
|
||||||
| UpdateStmt
|
| UpdateStmt
|
||||||
| DeleteStmt /* by default all are $$=$1 */
|
| DeleteStmt /* by default all are $$=$1 */
|
||||||
| MergeStmt
|
|
||||||
;
|
;
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
@ -11097,151 +11088,6 @@ set_target_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
|
||||||
*
|
|
||||||
* QUERY:
|
|
||||||
* MERGE STATEMENTS
|
|
||||||
*
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
MergeStmt:
|
|
||||||
MERGE INTO relation_expr_opt_alias
|
|
||||||
USING table_ref
|
|
||||||
ON a_expr
|
|
||||||
merge_when_list
|
|
||||||
{
|
|
||||||
MergeStmt *m = makeNode(MergeStmt);
|
|
||||||
|
|
||||||
m->relation = $3;
|
|
||||||
m->source_relation = $5;
|
|
||||||
m->join_condition = $7;
|
|
||||||
m->mergeActionList = $8;
|
|
||||||
|
|
||||||
$$ = (Node *)m;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
merge_when_list:
|
|
||||||
merge_when_clause { $$ = list_make1($1); }
|
|
||||||
| merge_when_list merge_when_clause { $$ = lappend($1,$2); }
|
|
||||||
;
|
|
||||||
|
|
||||||
merge_when_clause:
|
|
||||||
WHEN MATCHED opt_and_condition THEN merge_update
|
|
||||||
{
|
|
||||||
MergeAction *m = makeNode(MergeAction);
|
|
||||||
|
|
||||||
m->matched = true;
|
|
||||||
m->commandType = CMD_UPDATE;
|
|
||||||
m->condition = $3;
|
|
||||||
m->stmt = $5;
|
|
||||||
|
|
||||||
$$ = (Node *)m;
|
|
||||||
}
|
|
||||||
| WHEN MATCHED opt_and_condition THEN merge_delete
|
|
||||||
{
|
|
||||||
MergeAction *m = makeNode(MergeAction);
|
|
||||||
|
|
||||||
m->matched = true;
|
|
||||||
m->commandType = CMD_DELETE;
|
|
||||||
m->condition = $3;
|
|
||||||
m->stmt = $5;
|
|
||||||
|
|
||||||
$$ = (Node *)m;
|
|
||||||
}
|
|
||||||
| WHEN NOT MATCHED opt_and_condition THEN merge_insert
|
|
||||||
{
|
|
||||||
MergeAction *m = makeNode(MergeAction);
|
|
||||||
|
|
||||||
m->matched = false;
|
|
||||||
m->commandType = CMD_INSERT;
|
|
||||||
m->condition = $4;
|
|
||||||
m->stmt = $6;
|
|
||||||
|
|
||||||
$$ = (Node *)m;
|
|
||||||
}
|
|
||||||
| WHEN NOT MATCHED opt_and_condition THEN DO NOTHING
|
|
||||||
{
|
|
||||||
MergeAction *m = makeNode(MergeAction);
|
|
||||||
|
|
||||||
m->matched = false;
|
|
||||||
m->commandType = CMD_NOTHING;
|
|
||||||
m->condition = $4;
|
|
||||||
m->stmt = NULL;
|
|
||||||
|
|
||||||
$$ = (Node *)m;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
opt_and_condition:
|
|
||||||
AND a_expr { $$ = $2; }
|
|
||||||
| { $$ = NULL; }
|
|
||||||
;
|
|
||||||
|
|
||||||
merge_delete:
|
|
||||||
DELETE_P
|
|
||||||
{
|
|
||||||
DeleteStmt *n = makeNode(DeleteStmt);
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
merge_update:
|
|
||||||
UPDATE SET set_clause_list
|
|
||||||
{
|
|
||||||
UpdateStmt *n = makeNode(UpdateStmt);
|
|
||||||
n->targetList = $3;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
merge_insert:
|
|
||||||
INSERT values_clause
|
|
||||||
{
|
|
||||||
InsertStmt *n = makeNode(InsertStmt);
|
|
||||||
n->cols = NIL;
|
|
||||||
n->selectStmt = $2;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
| INSERT OVERRIDING override_kind VALUE_P values_clause
|
|
||||||
{
|
|
||||||
InsertStmt *n = makeNode(InsertStmt);
|
|
||||||
n->cols = NIL;
|
|
||||||
n->override = $3;
|
|
||||||
n->selectStmt = $5;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
| INSERT '(' insert_column_list ')' values_clause
|
|
||||||
{
|
|
||||||
InsertStmt *n = makeNode(InsertStmt);
|
|
||||||
n->cols = $3;
|
|
||||||
n->selectStmt = $5;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
| INSERT '(' insert_column_list ')' OVERRIDING override_kind VALUE_P values_clause
|
|
||||||
{
|
|
||||||
InsertStmt *n = makeNode(InsertStmt);
|
|
||||||
n->cols = $3;
|
|
||||||
n->override = $6;
|
|
||||||
n->selectStmt = $8;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
| INSERT DEFAULT VALUES
|
|
||||||
{
|
|
||||||
InsertStmt *n = makeNode(InsertStmt);
|
|
||||||
n->cols = NIL;
|
|
||||||
n->selectStmt = NULL;
|
|
||||||
|
|
||||||
$$ = (Node *)n;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
*
|
*
|
||||||
* QUERY:
|
* QUERY:
|
||||||
@ -15242,10 +15088,8 @@ unreserved_keyword:
|
|||||||
| LOGGED
|
| LOGGED
|
||||||
| MAPPING
|
| MAPPING
|
||||||
| MATCH
|
| MATCH
|
||||||
| MATCHED
|
|
||||||
| MATERIALIZED
|
| MATERIALIZED
|
||||||
| MAXVALUE
|
| MAXVALUE
|
||||||
| MERGE
|
|
||||||
| METHOD
|
| METHOD
|
||||||
| MINUTE_P
|
| MINUTE_P
|
||||||
| MINVALUE
|
| MINVALUE
|
||||||
|
@ -455,13 +455,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
|
|||||||
case EXPR_KIND_VALUES_SINGLE:
|
case EXPR_KIND_VALUES_SINGLE:
|
||||||
errkind = true;
|
errkind = true;
|
||||||
break;
|
break;
|
||||||
case EXPR_KIND_MERGE_WHEN_AND:
|
|
||||||
if (isAgg)
|
|
||||||
err = _("aggregate functions are not allowed in WHEN AND conditions");
|
|
||||||
else
|
|
||||||
err = _("grouping operations are not allowed in WHEN AND conditions");
|
|
||||||
|
|
||||||
break;
|
|
||||||
case EXPR_KIND_CHECK_CONSTRAINT:
|
case EXPR_KIND_CHECK_CONSTRAINT:
|
||||||
case EXPR_KIND_DOMAIN_CHECK:
|
case EXPR_KIND_DOMAIN_CHECK:
|
||||||
if (isAgg)
|
if (isAgg)
|
||||||
@ -880,9 +873,6 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
|
|||||||
case EXPR_KIND_VALUES_SINGLE:
|
case EXPR_KIND_VALUES_SINGLE:
|
||||||
errkind = true;
|
errkind = true;
|
||||||
break;
|
break;
|
||||||
case EXPR_KIND_MERGE_WHEN_AND:
|
|
||||||
err = _("window functions are not allowed in WHEN AND conditions");
|
|
||||||
break;
|
|
||||||
case EXPR_KIND_CHECK_CONSTRAINT:
|
case EXPR_KIND_CHECK_CONSTRAINT:
|
||||||
case EXPR_KIND_DOMAIN_CHECK:
|
case EXPR_KIND_DOMAIN_CHECK:
|
||||||
err = _("window functions are not allowed in check constraints");
|
err = _("window functions are not allowed in check constraints");
|
||||||
|
@ -76,6 +76,9 @@ static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
|
|||||||
RangeTableFunc *t);
|
RangeTableFunc *t);
|
||||||
static TableSampleClause *transformRangeTableSample(ParseState *pstate,
|
static TableSampleClause *transformRangeTableSample(ParseState *pstate,
|
||||||
RangeTableSample *rts);
|
RangeTableSample *rts);
|
||||||
|
static Node *transformFromClauseItem(ParseState *pstate, Node *n,
|
||||||
|
RangeTblEntry **top_rte, int *top_rti,
|
||||||
|
List **namespace);
|
||||||
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
|
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
|
||||||
Var *l_colvar, Var *r_colvar);
|
Var *l_colvar, Var *r_colvar);
|
||||||
static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
|
static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
|
||||||
@ -136,7 +139,6 @@ transformFromClause(ParseState *pstate, List *frmList)
|
|||||||
n = transformFromClauseItem(pstate, n,
|
n = transformFromClauseItem(pstate, n,
|
||||||
&rte,
|
&rte,
|
||||||
&rtindex,
|
&rtindex,
|
||||||
NULL, NULL,
|
|
||||||
&namespace);
|
&namespace);
|
||||||
|
|
||||||
checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
|
checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
|
||||||
@ -1094,20 +1096,13 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
|
|||||||
*
|
*
|
||||||
* *top_rti: receives the rangetable index of top_rte. (Ditto.)
|
* *top_rti: receives the rangetable index of top_rte. (Ditto.)
|
||||||
*
|
*
|
||||||
* *right_rte: receives the RTE corresponding to the right side of the
|
|
||||||
* jointree. Only MERGE really needs to know about this and only MERGE passes a
|
|
||||||
* non-NULL pointer.
|
|
||||||
*
|
|
||||||
* *right_rti: receives the rangetable index of the right_rte.
|
|
||||||
*
|
|
||||||
* *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
|
* *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
|
||||||
* as table/column names by this item. (The lateral_only flags in these items
|
* as table/column names by this item. (The lateral_only flags in these items
|
||||||
* are indeterminate and should be explicitly set by the caller before use.)
|
* are indeterminate and should be explicitly set by the caller before use.)
|
||||||
*/
|
*/
|
||||||
Node *
|
static Node *
|
||||||
transformFromClauseItem(ParseState *pstate, Node *n,
|
transformFromClauseItem(ParseState *pstate, Node *n,
|
||||||
RangeTblEntry **top_rte, int *top_rti,
|
RangeTblEntry **top_rte, int *top_rti,
|
||||||
RangeTblEntry **right_rte, int *right_rti,
|
|
||||||
List **namespace)
|
List **namespace)
|
||||||
{
|
{
|
||||||
if (IsA(n, RangeVar))
|
if (IsA(n, RangeVar))
|
||||||
@ -1199,7 +1194,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
|
|||||||
|
|
||||||
/* Recursively transform the contained relation */
|
/* Recursively transform the contained relation */
|
||||||
rel = transformFromClauseItem(pstate, rts->relation,
|
rel = transformFromClauseItem(pstate, rts->relation,
|
||||||
top_rte, top_rti, NULL, NULL, namespace);
|
top_rte, top_rti, namespace);
|
||||||
/* Currently, grammar could only return a RangeVar as contained rel */
|
/* Currently, grammar could only return a RangeVar as contained rel */
|
||||||
rtr = castNode(RangeTblRef, rel);
|
rtr = castNode(RangeTblRef, rel);
|
||||||
rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
|
rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
|
||||||
@ -1227,7 +1222,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
|
|||||||
List *l_namespace,
|
List *l_namespace,
|
||||||
*r_namespace,
|
*r_namespace,
|
||||||
*my_namespace,
|
*my_namespace,
|
||||||
*save_namespace,
|
|
||||||
*l_colnames,
|
*l_colnames,
|
||||||
*r_colnames,
|
*r_colnames,
|
||||||
*res_colnames,
|
*res_colnames,
|
||||||
@ -1246,7 +1240,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
|
|||||||
j->larg = transformFromClauseItem(pstate, j->larg,
|
j->larg = transformFromClauseItem(pstate, j->larg,
|
||||||
&l_rte,
|
&l_rte,
|
||||||
&l_rtindex,
|
&l_rtindex,
|
||||||
NULL, NULL,
|
|
||||||
&l_namespace);
|
&l_namespace);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1270,34 +1263,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
|
|||||||
sv_namespace_length = list_length(pstate->p_namespace);
|
sv_namespace_length = list_length(pstate->p_namespace);
|
||||||
pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);
|
pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);
|
||||||
|
|
||||||
/*
|
|
||||||
* If we are running MERGE, don't make the other RTEs visible while
|
|
||||||
* parsing the source relation. It mustn't see them.
|
|
||||||
*
|
|
||||||
* Currently, only MERGE passes non-NULL value for right_rte, so we
|
|
||||||
* can safely deduce if we're running MERGE or not by just looking at
|
|
||||||
* the right_rte. If that ever changes, we should look at other means
|
|
||||||
* to find that.
|
|
||||||
*/
|
|
||||||
if (right_rte)
|
|
||||||
{
|
|
||||||
save_namespace = pstate->p_namespace;
|
|
||||||
pstate->p_namespace = NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* And now we can process the RHS */
|
/* And now we can process the RHS */
|
||||||
j->rarg = transformFromClauseItem(pstate, j->rarg,
|
j->rarg = transformFromClauseItem(pstate, j->rarg,
|
||||||
&r_rte,
|
&r_rte,
|
||||||
&r_rtindex,
|
&r_rtindex,
|
||||||
NULL, NULL,
|
|
||||||
&r_namespace);
|
&r_namespace);
|
||||||
|
|
||||||
/*
|
|
||||||
* And now restore the namespace again so that join-quals can see it.
|
|
||||||
*/
|
|
||||||
if (right_rte)
|
|
||||||
pstate->p_namespace = save_namespace;
|
|
||||||
|
|
||||||
/* Remove the left-side RTEs from the namespace list again */
|
/* Remove the left-side RTEs from the namespace list again */
|
||||||
pstate->p_namespace = list_truncate(pstate->p_namespace,
|
pstate->p_namespace = list_truncate(pstate->p_namespace,
|
||||||
sv_namespace_length);
|
sv_namespace_length);
|
||||||
@ -1324,12 +1295,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
|
|||||||
expandRTE(r_rte, r_rtindex, 0, -1, false,
|
expandRTE(r_rte, r_rtindex, 0, -1, false,
|
||||||
&r_colnames, &r_colvars);
|
&r_colnames, &r_colvars);
|
||||||
|
|
||||||
if (right_rte)
|
|
||||||
*right_rte = r_rte;
|
|
||||||
|
|
||||||
if (right_rti)
|
|
||||||
*right_rti = r_rtindex;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Natural join does not explicitly specify columns; must generate
|
* Natural join does not explicitly specify columns; must generate
|
||||||
* columns to join. Need to run through the list of columns from each
|
* columns to join. Need to run through the list of columns from each
|
||||||
|
@ -485,7 +485,6 @@ assign_collations_walker(Node *node, assign_collations_context *context)
|
|||||||
case T_FromExpr:
|
case T_FromExpr:
|
||||||
case T_OnConflictExpr:
|
case T_OnConflictExpr:
|
||||||
case T_SortGroupClause:
|
case T_SortGroupClause:
|
||||||
case T_MergeAction:
|
|
||||||
(void) expression_tree_walker(node,
|
(void) expression_tree_walker(node,
|
||||||
assign_collations_walker,
|
assign_collations_walker,
|
||||||
(void *) &loccontext);
|
(void *) &loccontext);
|
||||||
|
@ -1818,7 +1818,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
|
|||||||
case EXPR_KIND_RETURNING:
|
case EXPR_KIND_RETURNING:
|
||||||
case EXPR_KIND_VALUES:
|
case EXPR_KIND_VALUES:
|
||||||
case EXPR_KIND_VALUES_SINGLE:
|
case EXPR_KIND_VALUES_SINGLE:
|
||||||
case EXPR_KIND_MERGE_WHEN_AND:
|
|
||||||
/* okay */
|
/* okay */
|
||||||
break;
|
break;
|
||||||
case EXPR_KIND_CHECK_CONSTRAINT:
|
case EXPR_KIND_CHECK_CONSTRAINT:
|
||||||
@ -3476,8 +3475,6 @@ ParseExprKindName(ParseExprKind exprKind)
|
|||||||
return "PARTITION BY";
|
return "PARTITION BY";
|
||||||
case EXPR_KIND_CALL_ARGUMENT:
|
case EXPR_KIND_CALL_ARGUMENT:
|
||||||
return "CALL";
|
return "CALL";
|
||||||
case EXPR_KIND_MERGE_WHEN_AND:
|
|
||||||
return "MERGE WHEN AND";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* There is intentionally no default: case here, so that the
|
* There is intentionally no default: case here, so that the
|
||||||
|
@ -2277,9 +2277,6 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
|
|||||||
/* okay, since we process this like a SELECT tlist */
|
/* okay, since we process this like a SELECT tlist */
|
||||||
pstate->p_hasTargetSRFs = true;
|
pstate->p_hasTargetSRFs = true;
|
||||||
break;
|
break;
|
||||||
case EXPR_KIND_MERGE_WHEN_AND:
|
|
||||||
err = _("set-returning functions are not allowed in WHEN AND conditions");
|
|
||||||
break;
|
|
||||||
case EXPR_KIND_CHECK_CONSTRAINT:
|
case EXPR_KIND_CHECK_CONSTRAINT:
|
||||||
case EXPR_KIND_DOMAIN_CHECK:
|
case EXPR_KIND_DOMAIN_CHECK:
|
||||||
err = _("set-returning functions are not allowed in check constraints");
|
err = _("set-returning functions are not allowed in check constraints");
|
||||||
|
@ -728,16 +728,6 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, const char *colname,
|
|||||||
colname),
|
colname),
|
||||||
parser_errposition(pstate, location)));
|
parser_errposition(pstate, location)));
|
||||||
|
|
||||||
/* In MERGE WHEN AND condition, no system column is allowed except tableOid or OID */
|
|
||||||
if (pstate->p_expr_kind == EXPR_KIND_MERGE_WHEN_AND &&
|
|
||||||
attnum < InvalidAttrNumber &&
|
|
||||||
!(attnum == TableOidAttributeNumber || attnum == ObjectIdAttributeNumber))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
|
||||||
errmsg("system column \"%s\" reference in WHEN AND condition is invalid",
|
|
||||||
colname),
|
|
||||||
parser_errposition(pstate, location)));
|
|
||||||
|
|
||||||
if (attnum != InvalidAttrNumber)
|
if (attnum != InvalidAttrNumber)
|
||||||
{
|
{
|
||||||
/* now check to see if column actually is defined */
|
/* now check to see if column actually is defined */
|
||||||
|
@ -1377,57 +1377,6 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
rewriteTargetListMerge(Query *parsetree, Relation target_relation)
|
|
||||||
{
|
|
||||||
Var *var = NULL;
|
|
||||||
const char *attrname;
|
|
||||||
TargetEntry *tle;
|
|
||||||
|
|
||||||
Assert(target_relation->rd_rel->relkind == RELKIND_RELATION ||
|
|
||||||
target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
|
|
||||||
target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Emit CTID so that executor can find the row to update or delete.
|
|
||||||
*/
|
|
||||||
var = makeVar(parsetree->mergeTarget_relation,
|
|
||||||
SelfItemPointerAttributeNumber,
|
|
||||||
TIDOID,
|
|
||||||
-1,
|
|
||||||
InvalidOid,
|
|
||||||
0);
|
|
||||||
|
|
||||||
attrname = "ctid";
|
|
||||||
tle = makeTargetEntry((Expr *) var,
|
|
||||||
list_length(parsetree->targetList) + 1,
|
|
||||||
pstrdup(attrname),
|
|
||||||
true);
|
|
||||||
|
|
||||||
parsetree->targetList = lappend(parsetree->targetList, tle);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we are dealing with partitioned table, then emit TABLEOID so that
|
|
||||||
* executor can find the partition the row belongs to.
|
|
||||||
*/
|
|
||||||
if (target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
|
||||||
{
|
|
||||||
var = makeVar(parsetree->mergeTarget_relation,
|
|
||||||
TableOidAttributeNumber,
|
|
||||||
OIDOID,
|
|
||||||
-1,
|
|
||||||
InvalidOid,
|
|
||||||
0);
|
|
||||||
|
|
||||||
attrname = "tableoid";
|
|
||||||
tle = makeTargetEntry((Expr *) var,
|
|
||||||
list_length(parsetree->targetList) + 1,
|
|
||||||
pstrdup(attrname),
|
|
||||||
true);
|
|
||||||
|
|
||||||
parsetree->targetList = lappend(parsetree->targetList, tle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* matchLocks -
|
* matchLocks -
|
||||||
@ -3382,7 +3331,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
|||||||
}
|
}
|
||||||
else if (event == CMD_UPDATE)
|
else if (event == CMD_UPDATE)
|
||||||
{
|
{
|
||||||
Assert(parsetree->override == OVERRIDING_NOT_SET);
|
|
||||||
parsetree->targetList =
|
parsetree->targetList =
|
||||||
rewriteTargetListIU(parsetree->targetList,
|
rewriteTargetListIU(parsetree->targetList,
|
||||||
parsetree->commandType,
|
parsetree->commandType,
|
||||||
@ -3390,50 +3338,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
|||||||
rt_entry_relation,
|
rt_entry_relation,
|
||||||
parsetree->resultRelation, NULL);
|
parsetree->resultRelation, NULL);
|
||||||
}
|
}
|
||||||
else if (event == CMD_MERGE)
|
|
||||||
{
|
|
||||||
Assert(parsetree->override == OVERRIDING_NOT_SET);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Rewrite each action targetlist separately
|
|
||||||
*/
|
|
||||||
foreach(lc1, parsetree->mergeActionList)
|
|
||||||
{
|
|
||||||
MergeAction *action = (MergeAction *) lfirst(lc1);
|
|
||||||
|
|
||||||
switch (action->commandType)
|
|
||||||
{
|
|
||||||
case CMD_NOTHING:
|
|
||||||
case CMD_DELETE: /* Nothing to do here */
|
|
||||||
break;
|
|
||||||
case CMD_UPDATE:
|
|
||||||
action->targetList =
|
|
||||||
rewriteTargetListIU(action->targetList,
|
|
||||||
action->commandType,
|
|
||||||
parsetree->override,
|
|
||||||
rt_entry_relation,
|
|
||||||
parsetree->resultRelation,
|
|
||||||
NULL);
|
|
||||||
break;
|
|
||||||
case CMD_INSERT:
|
|
||||||
{
|
|
||||||
InsertStmt *istmt = (InsertStmt *) action->stmt;
|
|
||||||
|
|
||||||
action->targetList =
|
|
||||||
rewriteTargetListIU(action->targetList,
|
|
||||||
action->commandType,
|
|
||||||
istmt->override,
|
|
||||||
rt_entry_relation,
|
|
||||||
parsetree->resultRelation,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
elog(ERROR, "unrecognized commandType: %d", action->commandType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event == CMD_DELETE)
|
else if (event == CMD_DELETE)
|
||||||
{
|
{
|
||||||
/* Nothing to do here */
|
/* Nothing to do here */
|
||||||
@ -3447,20 +3351,13 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
|||||||
locks = matchLocks(event, rt_entry_relation->rd_rules,
|
locks = matchLocks(event, rt_entry_relation->rd_rules,
|
||||||
result_relation, parsetree, &hasUpdate);
|
result_relation, parsetree, &hasUpdate);
|
||||||
|
|
||||||
/*
|
product_queries = fireRules(parsetree,
|
||||||
* XXX MERGE doesn't support write rules because they would violate
|
result_relation,
|
||||||
* the SQL Standard spec and would be unclear how they should work.
|
event,
|
||||||
*/
|
locks,
|
||||||
if (event == CMD_MERGE)
|
&instead,
|
||||||
product_queries = NIL;
|
&returning,
|
||||||
else
|
&qual_product);
|
||||||
product_queries = fireRules(parsetree,
|
|
||||||
result_relation,
|
|
||||||
event,
|
|
||||||
locks,
|
|
||||||
&instead,
|
|
||||||
&returning,
|
|
||||||
&qual_product);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there were no INSTEAD rules, and the target relation is a view
|
* If there were no INSTEAD rules, and the target relation is a view
|
||||||
|
@ -379,95 +379,6 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
|
|
||||||
* and set them up so that we can enforce the appropriate policy depending
|
|
||||||
* on the final action we take.
|
|
||||||
*
|
|
||||||
* We don't fetch the SELECT policies since they are correctly applied to
|
|
||||||
* the root->mergeTarget_relation. The target rows are selected after
|
|
||||||
* joining the mergeTarget_relation and the source relation and hence it's
|
|
||||||
* enough to apply SELECT policies to the mergeTarget_relation.
|
|
||||||
*
|
|
||||||
* We don't push the UPDATE/DELETE USING quals to the RTE because we don't
|
|
||||||
* really want to apply them while scanning the relation since we don't
|
|
||||||
* know whether we will be doing a UPDATE or a DELETE at the end. We apply
|
|
||||||
* the respective policy once we decide the final action on the target
|
|
||||||
* tuple.
|
|
||||||
*
|
|
||||||
* XXX We are setting up USING quals as WITH CHECK. If RLS prohibits
|
|
||||||
* UPDATE/DELETE on the target row, we shall throw an error instead of
|
|
||||||
* silently ignoring the row. This is different than how normal
|
|
||||||
* UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE
|
|
||||||
* handling.
|
|
||||||
*/
|
|
||||||
if (commandType == CMD_MERGE)
|
|
||||||
{
|
|
||||||
List *merge_permissive_policies;
|
|
||||||
List *merge_restrictive_policies;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fetch the UPDATE policies and set them up to execute on the
|
|
||||||
* existing target row before doing UPDATE.
|
|
||||||
*/
|
|
||||||
get_policies_for_relation(rel, CMD_UPDATE, user_id,
|
|
||||||
&merge_permissive_policies,
|
|
||||||
&merge_restrictive_policies);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
|
|
||||||
* the existing target row.
|
|
||||||
*/
|
|
||||||
add_with_check_options(rel, rt_index,
|
|
||||||
WCO_RLS_MERGE_UPDATE_CHECK,
|
|
||||||
merge_permissive_policies,
|
|
||||||
merge_restrictive_policies,
|
|
||||||
withCheckOptions,
|
|
||||||
hasSubLinks,
|
|
||||||
true);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Same with DELETE policies.
|
|
||||||
*/
|
|
||||||
get_policies_for_relation(rel, CMD_DELETE, user_id,
|
|
||||||
&merge_permissive_policies,
|
|
||||||
&merge_restrictive_policies);
|
|
||||||
|
|
||||||
add_with_check_options(rel, rt_index,
|
|
||||||
WCO_RLS_MERGE_DELETE_CHECK,
|
|
||||||
merge_permissive_policies,
|
|
||||||
merge_restrictive_policies,
|
|
||||||
withCheckOptions,
|
|
||||||
hasSubLinks,
|
|
||||||
true);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* No special handling is required for INSERT policies. They will be
|
|
||||||
* checked and enforced during ExecInsert(). But we must add them to
|
|
||||||
* withCheckOptions.
|
|
||||||
*/
|
|
||||||
get_policies_for_relation(rel, CMD_INSERT, user_id,
|
|
||||||
&merge_permissive_policies,
|
|
||||||
&merge_restrictive_policies);
|
|
||||||
|
|
||||||
add_with_check_options(rel, rt_index,
|
|
||||||
WCO_RLS_INSERT_CHECK,
|
|
||||||
merge_permissive_policies,
|
|
||||||
merge_restrictive_policies,
|
|
||||||
withCheckOptions,
|
|
||||||
hasSubLinks,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Enforce the WITH CHECK clauses of the UPDATE policies */
|
|
||||||
add_with_check_options(rel, rt_index,
|
|
||||||
WCO_RLS_UPDATE_CHECK,
|
|
||||||
merge_permissive_policies,
|
|
||||||
merge_restrictive_policies,
|
|
||||||
withCheckOptions,
|
|
||||||
hasSubLinks,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
heap_close(rel, NoLock);
|
heap_close(rel, NoLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -527,14 +438,6 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
|
|||||||
if (policy->polcmd == ACL_DELETE_CHR)
|
if (policy->polcmd == ACL_DELETE_CHR)
|
||||||
cmd_matches = true;
|
cmd_matches = true;
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We do not support a separate policy for MERGE command.
|
|
||||||
* Instead it derives from the policies defined for other
|
|
||||||
* commands.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized policy command type %d",
|
elog(ERROR, "unrecognized policy command type %d",
|
||||||
(int) cmd);
|
(int) cmd);
|
||||||
|
@ -193,11 +193,6 @@ ProcessQuery(PlannedStmt *plan,
|
|||||||
"DELETE " UINT64_FORMAT,
|
"DELETE " UINT64_FORMAT,
|
||||||
queryDesc->estate->es_processed);
|
queryDesc->estate->es_processed);
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
|
|
||||||
"MERGE " UINT64_FORMAT,
|
|
||||||
queryDesc->estate->es_processed);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
strcpy(completionTag, "???");
|
strcpy(completionTag, "???");
|
||||||
break;
|
break;
|
||||||
|
@ -110,7 +110,6 @@ CommandIsReadOnly(PlannedStmt *pstmt)
|
|||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_MERGE:
|
|
||||||
return false;
|
return false;
|
||||||
case CMD_UTILITY:
|
case CMD_UTILITY:
|
||||||
/* For now, treat all utility commands as read/write */
|
/* For now, treat all utility commands as read/write */
|
||||||
@ -1833,8 +1832,6 @@ QueryReturnsTuples(Query *parsetree)
|
|||||||
case CMD_SELECT:
|
case CMD_SELECT:
|
||||||
/* returns tuples */
|
/* returns tuples */
|
||||||
return true;
|
return true;
|
||||||
case CMD_MERGE:
|
|
||||||
return false;
|
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
@ -2079,10 +2076,6 @@ CreateCommandTag(Node *parsetree)
|
|||||||
tag = "UPDATE";
|
tag = "UPDATE";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_MergeStmt:
|
|
||||||
tag = "MERGE";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case T_SelectStmt:
|
case T_SelectStmt:
|
||||||
tag = "SELECT";
|
tag = "SELECT";
|
||||||
break;
|
break;
|
||||||
@ -2826,9 +2819,6 @@ CreateCommandTag(Node *parsetree)
|
|||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
tag = "DELETE";
|
tag = "DELETE";
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
tag = "MERGE";
|
|
||||||
break;
|
|
||||||
case CMD_UTILITY:
|
case CMD_UTILITY:
|
||||||
tag = CreateCommandTag(stmt->utilityStmt);
|
tag = CreateCommandTag(stmt->utilityStmt);
|
||||||
break;
|
break;
|
||||||
@ -2889,9 +2879,6 @@ CreateCommandTag(Node *parsetree)
|
|||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
tag = "DELETE";
|
tag = "DELETE";
|
||||||
break;
|
break;
|
||||||
case CMD_MERGE:
|
|
||||||
tag = "MERGE";
|
|
||||||
break;
|
|
||||||
case CMD_UTILITY:
|
case CMD_UTILITY:
|
||||||
tag = CreateCommandTag(stmt->utilityStmt);
|
tag = CreateCommandTag(stmt->utilityStmt);
|
||||||
break;
|
break;
|
||||||
@ -2940,7 +2927,6 @@ GetCommandLogLevel(Node *parsetree)
|
|||||||
case T_InsertStmt:
|
case T_InsertStmt:
|
||||||
case T_DeleteStmt:
|
case T_DeleteStmt:
|
||||||
case T_UpdateStmt:
|
case T_UpdateStmt:
|
||||||
case T_MergeStmt:
|
|
||||||
lev = LOGSTMT_MOD;
|
lev = LOGSTMT_MOD;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -3380,7 +3366,6 @@ GetCommandLogLevel(Node *parsetree)
|
|||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_MERGE:
|
|
||||||
lev = LOGSTMT_MOD;
|
lev = LOGSTMT_MOD;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -3411,7 +3396,6 @@ GetCommandLogLevel(Node *parsetree)
|
|||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
case CMD_MERGE:
|
|
||||||
lev = LOGSTMT_MOD;
|
lev = LOGSTMT_MOD;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -53,34 +53,23 @@ typedef enum LockTupleMode
|
|||||||
* When heap_update, heap_delete, or heap_lock_tuple fail because the target
|
* When heap_update, heap_delete, or heap_lock_tuple fail because the target
|
||||||
* tuple is already outdated, they fill in this struct to provide information
|
* tuple is already outdated, they fill in this struct to provide information
|
||||||
* to the caller about what happened.
|
* to the caller about what happened.
|
||||||
*
|
|
||||||
* result is the result of HeapTupleSatisfiesUpdate, leading to the failure.
|
|
||||||
* It's set to HeapTupleMayBeUpdated when there is no failure.
|
|
||||||
*
|
|
||||||
* ctid is the target's ctid link: it is the same as the target's TID if the
|
* ctid is the target's ctid link: it is the same as the target's TID if the
|
||||||
* target was deleted, or the location of the replacement tuple if the target
|
* target was deleted, or the location of the replacement tuple if the target
|
||||||
* was updated.
|
* was updated.
|
||||||
*
|
|
||||||
* xmax is the outdating transaction's XID. If the caller wants to visit the
|
* xmax is the outdating transaction's XID. If the caller wants to visit the
|
||||||
* replacement tuple, it must check that this matches before believing the
|
* replacement tuple, it must check that this matches before believing the
|
||||||
* replacement is really a match.
|
* replacement is really a match.
|
||||||
*
|
|
||||||
* cmax is the outdating command's CID, but only when the failure code is
|
* cmax is the outdating command's CID, but only when the failure code is
|
||||||
* HeapTupleSelfUpdated (i.e., something in the current transaction outdated
|
* HeapTupleSelfUpdated (i.e., something in the current transaction outdated
|
||||||
* the tuple); otherwise cmax is zero. (We make this restriction because
|
* the tuple); otherwise cmax is zero. (We make this restriction because
|
||||||
* HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
|
* HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
|
||||||
* transactions.)
|
* transactions.)
|
||||||
*
|
|
||||||
* lockmode is only relevant for callers of heap_update() and is the mode which
|
|
||||||
* the caller should use in case it needs to lock the updated tuple.
|
|
||||||
*/
|
*/
|
||||||
typedef struct HeapUpdateFailureData
|
typedef struct HeapUpdateFailureData
|
||||||
{
|
{
|
||||||
HTSU_Result result;
|
|
||||||
ItemPointerData ctid;
|
ItemPointerData ctid;
|
||||||
TransactionId xmax;
|
TransactionId xmax;
|
||||||
CommandId cmax;
|
CommandId cmax;
|
||||||
LockTupleMode lockmode;
|
|
||||||
} HeapUpdateFailureData;
|
} HeapUpdateFailureData;
|
||||||
|
|
||||||
|
|
||||||
@ -173,7 +162,7 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
|
|||||||
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
|
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
|
||||||
HeapTuple newtup,
|
HeapTuple newtup,
|
||||||
CommandId cid, Snapshot crosscheck, bool wait,
|
CommandId cid, Snapshot crosscheck, bool wait,
|
||||||
HeapUpdateFailureData *hufd);
|
HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
|
||||||
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
|
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
|
||||||
CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
|
CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
|
||||||
bool follow_update,
|
bool follow_update,
|
||||||
|
@ -206,8 +206,7 @@ extern bool ExecBRDeleteTriggers(EState *estate,
|
|||||||
EPQState *epqstate,
|
EPQState *epqstate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple fdw_trigtuple,
|
HeapTuple fdw_trigtuple);
|
||||||
HeapUpdateFailureData *hufdp);
|
|
||||||
extern void ExecARDeleteTriggers(EState *estate,
|
extern void ExecARDeleteTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
@ -226,8 +225,7 @@ extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
|
|||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
HeapTuple fdw_trigtuple,
|
HeapTuple fdw_trigtuple,
|
||||||
TupleTableSlot *slot,
|
TupleTableSlot *slot);
|
||||||
HeapUpdateFailureData *hufdp);
|
|
||||||
extern void ExecARUpdateTriggers(EState *estate,
|
extern void ExecARUpdateTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
|
@ -114,7 +114,6 @@ extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
|
|||||||
PartitionDispatch *pd,
|
PartitionDispatch *pd,
|
||||||
TupleTableSlot *slot,
|
TupleTableSlot *slot,
|
||||||
EState *estate);
|
EState *estate);
|
||||||
extern int ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid);
|
|
||||||
extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
|
extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
|
||||||
ResultRelInfo *resultRelInfo,
|
ResultRelInfo *resultRelInfo,
|
||||||
PartitionTupleRouting *proute,
|
PartitionTupleRouting *proute,
|
||||||
|
@ -58,11 +58,8 @@ typedef struct Instrumentation
|
|||||||
double total; /* Total total time (in seconds) */
|
double total; /* Total total time (in seconds) */
|
||||||
double ntuples; /* Total tuples produced */
|
double ntuples; /* Total tuples produced */
|
||||||
double nloops; /* # of run cycles for this node */
|
double nloops; /* # of run cycles for this node */
|
||||||
double nfiltered1; /* # tuples removed by scanqual or joinqual OR
|
double nfiltered1; /* # tuples removed by scanqual or joinqual */
|
||||||
* # tuples inserted by MERGE */
|
double nfiltered2; /* # tuples removed by "other" quals */
|
||||||
double nfiltered2; /* # tuples removed by "other" quals OR
|
|
||||||
* # tuples updated by MERGE */
|
|
||||||
double nfiltered3; /* # tuples deleted by MERGE */
|
|
||||||
BufferUsage bufusage; /* Total buffer usage */
|
BufferUsage bufusage; /* Total buffer usage */
|
||||||
} Instrumentation;
|
} Instrumentation;
|
||||||
|
|
||||||
|
@ -18,26 +18,5 @@
|
|||||||
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
|
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
|
||||||
extern void ExecEndModifyTable(ModifyTableState *node);
|
extern void ExecEndModifyTable(ModifyTableState *node);
|
||||||
extern void ExecReScanModifyTable(ModifyTableState *node);
|
extern void ExecReScanModifyTable(ModifyTableState *node);
|
||||||
extern TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
|
||||||
EState *estate,
|
|
||||||
struct PartitionTupleRouting *proute,
|
|
||||||
ResultRelInfo *targetRelInfo,
|
|
||||||
TupleTableSlot *slot);
|
|
||||||
extern TupleTableSlot *ExecDelete(ModifyTableState *mtstate,
|
|
||||||
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *planSlot,
|
|
||||||
EPQState *epqstate, EState *estate, bool *tupleDeleted,
|
|
||||||
bool processReturning, HeapUpdateFailureData *hufdp,
|
|
||||||
MergeActionState *actionState, bool canSetTag);
|
|
||||||
extern TupleTableSlot *ExecUpdate(ModifyTableState *mtstate,
|
|
||||||
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
|
|
||||||
TupleTableSlot *planSlot, EPQState *epqstate, EState *estate,
|
|
||||||
bool *tuple_updated, HeapUpdateFailureData *hufdp,
|
|
||||||
MergeActionState *actionState, bool canSetTag);
|
|
||||||
extern TupleTableSlot *ExecInsert(ModifyTableState *mtstate,
|
|
||||||
TupleTableSlot *slot,
|
|
||||||
TupleTableSlot *planSlot,
|
|
||||||
EState *estate,
|
|
||||||
MergeActionState *actionState,
|
|
||||||
bool canSetTag);
|
|
||||||
|
|
||||||
#endif /* NODEMODIFYTABLE_H */
|
#endif /* NODEMODIFYTABLE_H */
|
||||||
|
@ -64,7 +64,6 @@ typedef struct _SPI_plan *SPIPlanPtr;
|
|||||||
#define SPI_OK_REL_REGISTER 15
|
#define SPI_OK_REL_REGISTER 15
|
||||||
#define SPI_OK_REL_UNREGISTER 16
|
#define SPI_OK_REL_UNREGISTER 16
|
||||||
#define SPI_OK_TD_REGISTER 17
|
#define SPI_OK_TD_REGISTER 17
|
||||||
#define SPI_OK_MERGE 18
|
|
||||||
|
|
||||||
#define SPI_OPT_NONATOMIC (1 << 0)
|
#define SPI_OPT_NONATOMIC (1 << 0)
|
||||||
|
|
||||||
|
@ -360,17 +360,8 @@ typedef struct JunkFilter
|
|||||||
AttrNumber *jf_cleanMap;
|
AttrNumber *jf_cleanMap;
|
||||||
TupleTableSlot *jf_resultSlot;
|
TupleTableSlot *jf_resultSlot;
|
||||||
AttrNumber jf_junkAttNo;
|
AttrNumber jf_junkAttNo;
|
||||||
AttrNumber jf_otherJunkAttNo;
|
|
||||||
} JunkFilter;
|
} JunkFilter;
|
||||||
|
|
||||||
typedef struct MergeState
|
|
||||||
{
|
|
||||||
/* List of MERGE MATCHED action states */
|
|
||||||
List *matchedActionStates;
|
|
||||||
/* List of MERGE NOT MATCHED action states */
|
|
||||||
List *notMatchedActionStates;
|
|
||||||
} MergeState;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* OnConflictSetState
|
* OnConflictSetState
|
||||||
*
|
*
|
||||||
@ -461,38 +452,8 @@ typedef struct ResultRelInfo
|
|||||||
|
|
||||||
/* relation descriptor for root partitioned table */
|
/* relation descriptor for root partitioned table */
|
||||||
Relation ri_PartitionRoot;
|
Relation ri_PartitionRoot;
|
||||||
|
|
||||||
int ri_PartitionLeafIndex;
|
|
||||||
/* for running MERGE on this result relation */
|
|
||||||
MergeState *ri_mergeState;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* While executing MERGE, the target relation is processed twice; once
|
|
||||||
* as a target relation and once to run a join between the target and the
|
|
||||||
* source. We generate two different RTEs for these two purposes, one with
|
|
||||||
* rte->inh set to false and other with rte->inh set to true.
|
|
||||||
*
|
|
||||||
* Since the plan re-evaluated by EvalPlanQual uses the join RTE, we must
|
|
||||||
* install the updated tuple in the scan corresponding to that RTE. The
|
|
||||||
* following member tracks the index of the second RTE for EvalPlanQual
|
|
||||||
* purposes. ri_mergeTargetRTI is non-zero only when MERGE is in-progress.
|
|
||||||
* We use ri_mergeTargetRTI to run EvalPlanQual for MERGE and
|
|
||||||
* ri_RangeTableIndex elsewhere.
|
|
||||||
*/
|
|
||||||
Index ri_mergeTargetRTI;
|
|
||||||
} ResultRelInfo;
|
} ResultRelInfo;
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the Range table index for EvalPlanQual.
|
|
||||||
*
|
|
||||||
* We use the ri_mergeTargetRTI if set, otherwise use ri_RangeTableIndex.
|
|
||||||
* ri_mergeTargetRTI should really be ever set iff we're running MERGE.
|
|
||||||
*/
|
|
||||||
#define GetEPQRangeTableIndex(r) \
|
|
||||||
(((r)->ri_mergeTargetRTI > 0) \
|
|
||||||
? (r)->ri_mergeTargetRTI \
|
|
||||||
: (r)->ri_RangeTableIndex)
|
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
* EState information
|
* EState information
|
||||||
*
|
*
|
||||||
@ -1005,11 +966,6 @@ typedef struct PlanState
|
|||||||
if (((PlanState *)(node))->instrument) \
|
if (((PlanState *)(node))->instrument) \
|
||||||
((PlanState *)(node))->instrument->nfiltered2 += (delta); \
|
((PlanState *)(node))->instrument->nfiltered2 += (delta); \
|
||||||
} while(0)
|
} while(0)
|
||||||
#define InstrCountFiltered3(node, delta) \
|
|
||||||
do { \
|
|
||||||
if (((PlanState *)(node))->instrument) \
|
|
||||||
((PlanState *)(node))->instrument->nfiltered3 += (delta); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EPQState is state for executing an EvalPlanQual recheck on a candidate
|
* EPQState is state for executing an EvalPlanQual recheck on a candidate
|
||||||
@ -1056,20 +1012,6 @@ typedef struct ProjectSetState
|
|||||||
MemoryContext argcontext; /* context for SRF arguments */
|
MemoryContext argcontext; /* context for SRF arguments */
|
||||||
} ProjectSetState;
|
} ProjectSetState;
|
||||||
|
|
||||||
/* ----------------
|
|
||||||
* MergeActionState information
|
|
||||||
* ----------------
|
|
||||||
*/
|
|
||||||
typedef struct MergeActionState
|
|
||||||
{
|
|
||||||
NodeTag type;
|
|
||||||
bool matched; /* true=MATCHED, false=NOT MATCHED */
|
|
||||||
ExprState *whenqual; /* WHEN AND conditions */
|
|
||||||
CmdType commandType; /* INSERT/UPDATE/DELETE/DO NOTHING */
|
|
||||||
ProjectionInfo *proj; /* tuple projection info */
|
|
||||||
TupleDesc tupDesc; /* tuple descriptor for projection */
|
|
||||||
} MergeActionState;
|
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
* ModifyTableState information
|
* ModifyTableState information
|
||||||
* ----------------
|
* ----------------
|
||||||
@ -1077,7 +1019,7 @@ typedef struct MergeActionState
|
|||||||
typedef struct ModifyTableState
|
typedef struct ModifyTableState
|
||||||
{
|
{
|
||||||
PlanState ps; /* its first field is NodeTag */
|
PlanState ps; /* its first field is NodeTag */
|
||||||
CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
|
CmdType operation; /* INSERT, UPDATE, or DELETE */
|
||||||
bool canSetTag; /* do we set the command tag/es_processed? */
|
bool canSetTag; /* do we set the command tag/es_processed? */
|
||||||
bool mt_done; /* are we done? */
|
bool mt_done; /* are we done? */
|
||||||
PlanState **mt_plans; /* subplans (one per target rel) */
|
PlanState **mt_plans; /* subplans (one per target rel) */
|
||||||
@ -1093,8 +1035,6 @@ typedef struct ModifyTableState
|
|||||||
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
|
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
|
||||||
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection target */
|
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection target */
|
||||||
|
|
||||||
TupleTableSlot *mt_mergeproj; /* MERGE action projection target */
|
|
||||||
|
|
||||||
/* Tuple-routing support info */
|
/* Tuple-routing support info */
|
||||||
struct PartitionTupleRouting *mt_partition_tuple_routing;
|
struct PartitionTupleRouting *mt_partition_tuple_routing;
|
||||||
|
|
||||||
@ -1106,9 +1046,6 @@ typedef struct ModifyTableState
|
|||||||
|
|
||||||
/* Per plan map for tuple conversion from child to root */
|
/* Per plan map for tuple conversion from child to root */
|
||||||
TupleConversionMap **mt_per_subplan_tupconv_maps;
|
TupleConversionMap **mt_per_subplan_tupconv_maps;
|
||||||
|
|
||||||
/* Flags showing which subcommands are present INS/UPD/DEL/DO NOTHING */
|
|
||||||
int mt_merge_subcommands;
|
|
||||||
} ModifyTableState;
|
} ModifyTableState;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -97,7 +97,6 @@ typedef enum NodeTag
|
|||||||
T_PlanState,
|
T_PlanState,
|
||||||
T_ResultState,
|
T_ResultState,
|
||||||
T_ProjectSetState,
|
T_ProjectSetState,
|
||||||
T_MergeActionState,
|
|
||||||
T_ModifyTableState,
|
T_ModifyTableState,
|
||||||
T_AppendState,
|
T_AppendState,
|
||||||
T_MergeAppendState,
|
T_MergeAppendState,
|
||||||
@ -309,8 +308,6 @@ typedef enum NodeTag
|
|||||||
T_InsertStmt,
|
T_InsertStmt,
|
||||||
T_DeleteStmt,
|
T_DeleteStmt,
|
||||||
T_UpdateStmt,
|
T_UpdateStmt,
|
||||||
T_MergeStmt,
|
|
||||||
T_MergeAction,
|
|
||||||
T_SelectStmt,
|
T_SelectStmt,
|
||||||
T_AlterTableStmt,
|
T_AlterTableStmt,
|
||||||
T_AlterTableCmd,
|
T_AlterTableCmd,
|
||||||
@ -660,8 +657,7 @@ typedef enum CmdType
|
|||||||
CMD_SELECT, /* select stmt */
|
CMD_SELECT, /* select stmt */
|
||||||
CMD_UPDATE, /* update stmt */
|
CMD_UPDATE, /* update stmt */
|
||||||
CMD_INSERT, /* insert stmt */
|
CMD_INSERT, /* insert stmt */
|
||||||
CMD_DELETE, /* delete stmt */
|
CMD_DELETE,
|
||||||
CMD_MERGE, /* merge stmt */
|
|
||||||
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
|
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
|
||||||
* etc. */
|
* etc. */
|
||||||
CMD_NOTHING /* dummy command for instead nothing rules
|
CMD_NOTHING /* dummy command for instead nothing rules
|
||||||
|
@ -38,7 +38,7 @@ typedef enum OverridingKind
|
|||||||
typedef enum QuerySource
|
typedef enum QuerySource
|
||||||
{
|
{
|
||||||
QSRC_ORIGINAL, /* original parsetree (explicit query) */
|
QSRC_ORIGINAL, /* original parsetree (explicit query) */
|
||||||
QSRC_PARSER, /* added by parse analysis in MERGE */
|
QSRC_PARSER, /* added by parse analysis (now unused) */
|
||||||
QSRC_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
|
QSRC_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
|
||||||
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
|
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
|
||||||
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
|
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
|
||||||
@ -107,7 +107,7 @@ typedef struct Query
|
|||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
|
|
||||||
CmdType commandType; /* select|insert|update|delete|merge|utility */
|
CmdType commandType; /* select|insert|update|delete|utility */
|
||||||
|
|
||||||
QuerySource querySource; /* where did I come from? */
|
QuerySource querySource; /* where did I come from? */
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ typedef struct Query
|
|||||||
Node *utilityStmt; /* non-null if commandType == CMD_UTILITY */
|
Node *utilityStmt; /* non-null if commandType == CMD_UTILITY */
|
||||||
|
|
||||||
int resultRelation; /* rtable index of target relation for
|
int resultRelation; /* rtable index of target relation for
|
||||||
* INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
|
* INSERT/UPDATE/DELETE; 0 for SELECT */
|
||||||
|
|
||||||
bool hasAggs; /* has aggregates in tlist or havingQual */
|
bool hasAggs; /* has aggregates in tlist or havingQual */
|
||||||
bool hasWindowFuncs; /* has window functions in tlist */
|
bool hasWindowFuncs; /* has window functions in tlist */
|
||||||
@ -169,9 +169,6 @@ typedef struct Query
|
|||||||
List *withCheckOptions; /* a list of WithCheckOption's, which are
|
List *withCheckOptions; /* a list of WithCheckOption's, which are
|
||||||
* only added during rewrite and therefore
|
* only added during rewrite and therefore
|
||||||
* are not written out as part of Query. */
|
* are not written out as part of Query. */
|
||||||
int mergeTarget_relation;
|
|
||||||
List *mergeSourceTargetList;
|
|
||||||
List *mergeActionList; /* list of actions for MERGE (only) */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The following two fields identify the portion of the source text string
|
* The following two fields identify the portion of the source text string
|
||||||
@ -1131,9 +1128,7 @@ typedef enum WCOKind
|
|||||||
WCO_VIEW_CHECK, /* WCO on an auto-updatable view */
|
WCO_VIEW_CHECK, /* WCO on an auto-updatable view */
|
||||||
WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */
|
WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */
|
||||||
WCO_RLS_UPDATE_CHECK, /* RLS UPDATE WITH CHECK policy */
|
WCO_RLS_UPDATE_CHECK, /* RLS UPDATE WITH CHECK policy */
|
||||||
WCO_RLS_CONFLICT_CHECK, /* RLS ON CONFLICT DO UPDATE USING policy */
|
WCO_RLS_CONFLICT_CHECK /* RLS ON CONFLICT DO UPDATE USING policy */
|
||||||
WCO_RLS_MERGE_UPDATE_CHECK, /* RLS MERGE UPDATE USING policy */
|
|
||||||
WCO_RLS_MERGE_DELETE_CHECK /* RLS MERGE DELETE USING policy */
|
|
||||||
} WCOKind;
|
} WCOKind;
|
||||||
|
|
||||||
typedef struct WithCheckOption
|
typedef struct WithCheckOption
|
||||||
@ -1508,30 +1503,6 @@ typedef struct UpdateStmt
|
|||||||
WithClause *withClause; /* WITH clause */
|
WithClause *withClause; /* WITH clause */
|
||||||
} UpdateStmt;
|
} UpdateStmt;
|
||||||
|
|
||||||
/* ----------------------
|
|
||||||
* Merge Statement
|
|
||||||
* ----------------------
|
|
||||||
*/
|
|
||||||
typedef struct MergeStmt
|
|
||||||
{
|
|
||||||
NodeTag type;
|
|
||||||
RangeVar *relation; /* target relation to merge into */
|
|
||||||
Node *source_relation; /* source relation */
|
|
||||||
Node *join_condition; /* join condition between source and target */
|
|
||||||
List *mergeActionList; /* list of MergeAction(s) */
|
|
||||||
} MergeStmt;
|
|
||||||
|
|
||||||
typedef struct MergeAction
|
|
||||||
{
|
|
||||||
NodeTag type;
|
|
||||||
bool matched; /* true=MATCHED, false=NOT MATCHED */
|
|
||||||
Node *condition; /* WHEN AND conditions (raw parser) */
|
|
||||||
Node *qual; /* transformed WHEN AND conditions */
|
|
||||||
CmdType commandType; /* INSERT/UPDATE/DELETE/DO NOTHING */
|
|
||||||
Node *stmt; /* T_UpdateStmt etc */
|
|
||||||
List *targetList; /* the target list (of ResTarget) */
|
|
||||||
} MergeAction;
|
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
* Select Statement
|
* Select Statement
|
||||||
*
|
*
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "nodes/bitmapset.h"
|
#include "nodes/bitmapset.h"
|
||||||
#include "nodes/lockoptions.h"
|
#include "nodes/lockoptions.h"
|
||||||
#include "nodes/parsenodes.h"
|
|
||||||
#include "nodes/primnodes.h"
|
#include "nodes/primnodes.h"
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ typedef struct PlannedStmt
|
|||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
|
|
||||||
CmdType commandType; /* select|insert|update|delete|merge|utility */
|
CmdType commandType; /* select|insert|update|delete|utility */
|
||||||
|
|
||||||
uint64 queryId; /* query identifier (copied from Query) */
|
uint64 queryId; /* query identifier (copied from Query) */
|
||||||
|
|
||||||
@ -217,14 +216,13 @@ typedef struct ProjectSet
|
|||||||
typedef struct ModifyTable
|
typedef struct ModifyTable
|
||||||
{
|
{
|
||||||
Plan plan;
|
Plan plan;
|
||||||
CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
|
CmdType operation; /* INSERT, UPDATE, or DELETE */
|
||||||
bool canSetTag; /* do we set the command tag/es_processed? */
|
bool canSetTag; /* do we set the command tag/es_processed? */
|
||||||
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
|
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
|
||||||
/* RT indexes of non-leaf tables in a partition tree */
|
/* RT indexes of non-leaf tables in a partition tree */
|
||||||
List *partitioned_rels;
|
List *partitioned_rels;
|
||||||
bool partColsUpdated; /* some part key in hierarchy updated */
|
bool partColsUpdated; /* some part key in hierarchy updated */
|
||||||
List *resultRelations; /* integer list of RT indexes */
|
List *resultRelations; /* integer list of RT indexes */
|
||||||
Index mergeTargetRelation; /* RT index of the merge target */
|
|
||||||
int resultRelIndex; /* index of first resultRel in plan's list */
|
int resultRelIndex; /* index of first resultRel in plan's list */
|
||||||
int rootResultRelIndex; /* index of the partitioned table root */
|
int rootResultRelIndex; /* index of the partitioned table root */
|
||||||
List *plans; /* plan(s) producing source data */
|
List *plans; /* plan(s) producing source data */
|
||||||
@ -240,8 +238,6 @@ typedef struct ModifyTable
|
|||||||
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
|
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
|
||||||
Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
|
Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
|
||||||
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
|
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
|
||||||
List *mergeSourceTargetList;
|
|
||||||
List *mergeActionList; /* actions for MERGE */
|
|
||||||
} ModifyTable;
|
} ModifyTable;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -1670,7 +1670,7 @@ typedef struct LockRowsPath
|
|||||||
} LockRowsPath;
|
} LockRowsPath;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ModifyTablePath represents performing INSERT/UPDATE/DELETE/MERGE
|
* ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
|
||||||
*
|
*
|
||||||
* We represent most things that will be in the ModifyTable plan node
|
* We represent most things that will be in the ModifyTable plan node
|
||||||
* literally, except we have child Path(s) not Plan(s). But analysis of the
|
* literally, except we have child Path(s) not Plan(s). But analysis of the
|
||||||
@ -1679,14 +1679,13 @@ typedef struct LockRowsPath
|
|||||||
typedef struct ModifyTablePath
|
typedef struct ModifyTablePath
|
||||||
{
|
{
|
||||||
Path path;
|
Path path;
|
||||||
CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
|
CmdType operation; /* INSERT, UPDATE, or DELETE */
|
||||||
bool canSetTag; /* do we set the command tag/es_processed? */
|
bool canSetTag; /* do we set the command tag/es_processed? */
|
||||||
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
|
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
|
||||||
/* RT indexes of non-leaf tables in a partition tree */
|
/* RT indexes of non-leaf tables in a partition tree */
|
||||||
List *partitioned_rels;
|
List *partitioned_rels;
|
||||||
bool partColsUpdated; /* some part key in hierarchy updated */
|
bool partColsUpdated; /* some part key in hierarchy updated */
|
||||||
List *resultRelations; /* integer list of RT indexes */
|
List *resultRelations; /* integer list of RT indexes */
|
||||||
Index mergeTargetRelation;/* RT index of merge target relation */
|
|
||||||
List *subpaths; /* Path(s) producing source data */
|
List *subpaths; /* Path(s) producing source data */
|
||||||
List *subroots; /* per-target-table PlannerInfos */
|
List *subroots; /* per-target-table PlannerInfos */
|
||||||
List *withCheckOptionLists; /* per-target-table WCO lists */
|
List *withCheckOptionLists; /* per-target-table WCO lists */
|
||||||
@ -1694,8 +1693,6 @@ typedef struct ModifyTablePath
|
|||||||
List *rowMarks; /* PlanRowMarks (non-locking only) */
|
List *rowMarks; /* PlanRowMarks (non-locking only) */
|
||||||
OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
|
OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
|
||||||
int epqParam; /* ID of Param for EvalPlanQual re-eval */
|
int epqParam; /* ID of Param for EvalPlanQual re-eval */
|
||||||
List *mergeSourceTargetList;
|
|
||||||
List *mergeActionList; /* actions for MERGE */
|
|
||||||
} ModifyTablePath;
|
} ModifyTablePath;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -241,14 +241,11 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
|
|||||||
CmdType operation, bool canSetTag,
|
CmdType operation, bool canSetTag,
|
||||||
Index nominalRelation, List *partitioned_rels,
|
Index nominalRelation, List *partitioned_rels,
|
||||||
bool partColsUpdated,
|
bool partColsUpdated,
|
||||||
List *resultRelations,
|
List *resultRelations, List *subpaths,
|
||||||
Index mergeTargetRelation,
|
|
||||||
List *subpaths,
|
|
||||||
List *subroots,
|
List *subroots,
|
||||||
List *withCheckOptionLists, List *returningLists,
|
List *withCheckOptionLists, List *returningLists,
|
||||||
List *rowMarks, OnConflictExpr *onconflict,
|
List *rowMarks, OnConflictExpr *onconflict,
|
||||||
List *mergeSourceTargetList,
|
int epqParam);
|
||||||
List *mergeActionList, int epqParam);
|
|
||||||
extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
|
extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
Path *subpath,
|
Path *subpath,
|
||||||
Node *limitOffset, Node *limitCount,
|
Node *limitOffset, Node *limitCount,
|
||||||
|
@ -32,11 +32,6 @@ extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
|
|||||||
bool locked_from_parent,
|
bool locked_from_parent,
|
||||||
bool resolve_unknowns);
|
bool resolve_unknowns);
|
||||||
|
|
||||||
extern List *transformInsertRow(ParseState *pstate, List *exprlist,
|
|
||||||
List *stmtcols, List *icolumns, List *attrnos,
|
|
||||||
bool strip_indirection);
|
|
||||||
extern List *transformUpdateTargetList(ParseState *pstate,
|
|
||||||
List *targetList);
|
|
||||||
extern Query *transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree);
|
extern Query *transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree);
|
||||||
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
|
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
|
||||||
|
|
||||||
|
@ -244,10 +244,8 @@ PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD)
|
|||||||
PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
|
PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
|
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
|
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
|
|
||||||
PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
|
PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
|
|
||||||
PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
|
PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
|
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
|
||||||
|
@ -20,10 +20,7 @@ extern void transformFromClause(ParseState *pstate, List *frmList);
|
|||||||
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
|
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
|
||||||
bool inh, bool alsoSource, AclMode requiredPerms);
|
bool inh, bool alsoSource, AclMode requiredPerms);
|
||||||
extern bool interpretOidsOption(List *defList, bool allowOids);
|
extern bool interpretOidsOption(List *defList, bool allowOids);
|
||||||
extern Node *transformFromClauseItem(ParseState *pstate, Node *n,
|
|
||||||
RangeTblEntry **top_rte, int *top_rti,
|
|
||||||
RangeTblEntry **right_rte, int *right_rti,
|
|
||||||
List **namespace);
|
|
||||||
extern Node *transformWhereClause(ParseState *pstate, Node *clause,
|
extern Node *transformWhereClause(ParseState *pstate, Node *clause,
|
||||||
ParseExprKind exprKind, const char *constructName);
|
ParseExprKind exprKind, const char *constructName);
|
||||||
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
|
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
|
||||||
|
@ -50,7 +50,6 @@ typedef enum ParseExprKind
|
|||||||
EXPR_KIND_INSERT_TARGET, /* INSERT target list item */
|
EXPR_KIND_INSERT_TARGET, /* INSERT target list item */
|
||||||
EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */
|
EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */
|
||||||
EXPR_KIND_UPDATE_TARGET, /* UPDATE assignment target item */
|
EXPR_KIND_UPDATE_TARGET, /* UPDATE assignment target item */
|
||||||
EXPR_KIND_MERGE_WHEN_AND, /* MERGE WHEN ... AND condition */
|
|
||||||
EXPR_KIND_GROUP_BY, /* GROUP BY */
|
EXPR_KIND_GROUP_BY, /* GROUP BY */
|
||||||
EXPR_KIND_ORDER_BY, /* ORDER BY */
|
EXPR_KIND_ORDER_BY, /* ORDER BY */
|
||||||
EXPR_KIND_DISTINCT_ON, /* DISTINCT ON */
|
EXPR_KIND_DISTINCT_ON, /* DISTINCT ON */
|
||||||
@ -128,7 +127,7 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
|
|||||||
* p_parent_cte: CommonTableExpr that immediately contains the current query,
|
* p_parent_cte: CommonTableExpr that immediately contains the current query,
|
||||||
* if any.
|
* if any.
|
||||||
*
|
*
|
||||||
* p_target_relation: target relation, if query is INSERT/UPDATE/DELETE/MERGE
|
* p_target_relation: target relation, if query is INSERT, UPDATE, or DELETE.
|
||||||
*
|
*
|
||||||
* p_target_rangetblentry: target relation's entry in the rtable list.
|
* p_target_rangetblentry: target relation's entry in the rtable list.
|
||||||
*
|
*
|
||||||
@ -182,7 +181,7 @@ struct ParseState
|
|||||||
List *p_ctenamespace; /* current namespace for common table exprs */
|
List *p_ctenamespace; /* current namespace for common table exprs */
|
||||||
List *p_future_ctes; /* common table exprs not yet in namespace */
|
List *p_future_ctes; /* common table exprs not yet in namespace */
|
||||||
CommonTableExpr *p_parent_cte; /* this query's containing CTE */
|
CommonTableExpr *p_parent_cte; /* this query's containing CTE */
|
||||||
Relation p_target_relation; /* INSERT/UPDATE/DELETE/MERGE target rel */
|
Relation p_target_relation; /* INSERT/UPDATE/DELETE target rel */
|
||||||
RangeTblEntry *p_target_rangetblentry; /* target rel's RTE */
|
RangeTblEntry *p_target_rangetblentry; /* target rel's RTE */
|
||||||
bool p_is_insert; /* process assignment like INSERT not UPDATE */
|
bool p_is_insert; /* process assignment like INSERT not UPDATE */
|
||||||
List *p_windowdefs; /* raw representations of window clauses */
|
List *p_windowdefs; /* raw representations of window clauses */
|
||||||
|
@ -25,7 +25,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
|
|||||||
extern Node *build_column_default(Relation rel, int attrno);
|
extern Node *build_column_default(Relation rel, int attrno);
|
||||||
extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
|
extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
|
||||||
Relation target_relation);
|
Relation target_relation);
|
||||||
extern void rewriteTargetListMerge(Query *parsetree, Relation target_relation);
|
|
||||||
|
|
||||||
extern Query *get_view_query(Relation view);
|
extern Query *get_view_query(Relation view);
|
||||||
extern const char *view_query_is_auto_updatable(Query *viewquery,
|
extern const char *view_query_is_auto_updatable(Query *viewquery,
|
||||||
|
@ -3055,9 +3055,9 @@ PQoidValue(const PGresult *res)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* PQcmdTuples -
|
* PQcmdTuples -
|
||||||
* If the last command was INSERT/UPDATE/DELETE/MERGE/MOVE/FETCH/COPY,
|
* If the last command was INSERT/UPDATE/DELETE/MOVE/FETCH/COPY, return
|
||||||
* return a string containing the number of inserted/affected tuples.
|
* a string containing the number of inserted/affected tuples. If not,
|
||||||
* If not, return "".
|
* return "".
|
||||||
*
|
*
|
||||||
* XXX: this should probably return an int
|
* XXX: this should probably return an int
|
||||||
*/
|
*/
|
||||||
@ -3084,8 +3084,7 @@ PQcmdTuples(PGresult *res)
|
|||||||
strncmp(res->cmdStatus, "DELETE ", 7) == 0 ||
|
strncmp(res->cmdStatus, "DELETE ", 7) == 0 ||
|
||||||
strncmp(res->cmdStatus, "UPDATE ", 7) == 0)
|
strncmp(res->cmdStatus, "UPDATE ", 7) == 0)
|
||||||
p = res->cmdStatus + 7;
|
p = res->cmdStatus + 7;
|
||||||
else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0 ||
|
else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0)
|
||||||
strncmp(res->cmdStatus, "MERGE ", 6) == 0)
|
|
||||||
p = res->cmdStatus + 6;
|
p = res->cmdStatus + 6;
|
||||||
else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0 ||
|
else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0 ||
|
||||||
strncmp(res->cmdStatus, "COPY ", 5) == 0)
|
strncmp(res->cmdStatus, "COPY ", 5) == 0)
|
||||||
|
@ -3967,7 +3967,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* On the first call for this statement generate the plan, and detect
|
* On the first call for this statement generate the plan, and detect
|
||||||
* whether the statement is INSERT/UPDATE/DELETE/MERGE
|
* whether the statement is INSERT/UPDATE/DELETE
|
||||||
*/
|
*/
|
||||||
if (expr->plan == NULL)
|
if (expr->plan == NULL)
|
||||||
{
|
{
|
||||||
@ -3988,7 +3988,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
{
|
{
|
||||||
if (q->commandType == CMD_INSERT ||
|
if (q->commandType == CMD_INSERT ||
|
||||||
q->commandType == CMD_UPDATE ||
|
q->commandType == CMD_UPDATE ||
|
||||||
q->commandType == CMD_MERGE ||
|
|
||||||
q->commandType == CMD_DELETE)
|
q->commandType == CMD_DELETE)
|
||||||
stmt->mod_stmt = true;
|
stmt->mod_stmt = true;
|
||||||
}
|
}
|
||||||
@ -4046,7 +4045,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
case SPI_OK_INSERT_RETURNING:
|
case SPI_OK_INSERT_RETURNING:
|
||||||
case SPI_OK_UPDATE_RETURNING:
|
case SPI_OK_UPDATE_RETURNING:
|
||||||
case SPI_OK_DELETE_RETURNING:
|
case SPI_OK_DELETE_RETURNING:
|
||||||
case SPI_OK_MERGE:
|
|
||||||
Assert(stmt->mod_stmt);
|
Assert(stmt->mod_stmt);
|
||||||
exec_set_found(estate, (SPI_processed != 0));
|
exec_set_found(estate, (SPI_processed != 0));
|
||||||
break;
|
break;
|
||||||
@ -4224,7 +4222,6 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
|
|||||||
case SPI_OK_INSERT_RETURNING:
|
case SPI_OK_INSERT_RETURNING:
|
||||||
case SPI_OK_UPDATE_RETURNING:
|
case SPI_OK_UPDATE_RETURNING:
|
||||||
case SPI_OK_DELETE_RETURNING:
|
case SPI_OK_DELETE_RETURNING:
|
||||||
case SPI_OK_MERGE:
|
|
||||||
case SPI_OK_UTILITY:
|
case SPI_OK_UTILITY:
|
||||||
case SPI_OK_REWRITTEN:
|
case SPI_OK_REWRITTEN:
|
||||||
break;
|
break;
|
||||||
|
@ -304,7 +304,6 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
|
|||||||
%token <keyword> K_LAST
|
%token <keyword> K_LAST
|
||||||
%token <keyword> K_LOG
|
%token <keyword> K_LOG
|
||||||
%token <keyword> K_LOOP
|
%token <keyword> K_LOOP
|
||||||
%token <keyword> K_MERGE
|
|
||||||
%token <keyword> K_MESSAGE
|
%token <keyword> K_MESSAGE
|
||||||
%token <keyword> K_MESSAGE_TEXT
|
%token <keyword> K_MESSAGE_TEXT
|
||||||
%token <keyword> K_MOVE
|
%token <keyword> K_MOVE
|
||||||
@ -1948,10 +1947,6 @@ stmt_execsql : K_IMPORT
|
|||||||
{
|
{
|
||||||
$$ = make_execsql_stmt(K_INSERT, @1);
|
$$ = make_execsql_stmt(K_INSERT, @1);
|
||||||
}
|
}
|
||||||
| K_MERGE
|
|
||||||
{
|
|
||||||
$$ = make_execsql_stmt(K_MERGE, @1);
|
|
||||||
}
|
|
||||||
| T_WORD
|
| T_WORD
|
||||||
{
|
{
|
||||||
int tok;
|
int tok;
|
||||||
@ -2474,7 +2469,6 @@ unreserved_keyword :
|
|||||||
| K_IS
|
| K_IS
|
||||||
| K_LAST
|
| K_LAST
|
||||||
| K_LOG
|
| K_LOG
|
||||||
| K_MERGE
|
|
||||||
| K_MESSAGE
|
| K_MESSAGE
|
||||||
| K_MESSAGE_TEXT
|
| K_MESSAGE_TEXT
|
||||||
| K_MOVE
|
| K_MOVE
|
||||||
@ -2936,8 +2930,6 @@ make_execsql_stmt(int firsttoken, int location)
|
|||||||
{
|
{
|
||||||
if (prev_tok == K_INSERT)
|
if (prev_tok == K_INSERT)
|
||||||
continue; /* INSERT INTO is not an INTO-target */
|
continue; /* INSERT INTO is not an INTO-target */
|
||||||
if (prev_tok == K_MERGE)
|
|
||||||
continue; /* MERGE INTO is not an INTO-target */
|
|
||||||
if (firsttoken == K_IMPORT)
|
if (firsttoken == K_IMPORT)
|
||||||
continue; /* IMPORT ... INTO is not an INTO-target */
|
continue; /* IMPORT ... INTO is not an INTO-target */
|
||||||
if (have_into)
|
if (have_into)
|
||||||
|
@ -138,7 +138,6 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
|
PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
|
PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
|
PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("merge", K_MERGE, UNRESERVED_KEYWORD)
|
|
||||||
PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("move", K_MOVE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("move", K_MOVE, UNRESERVED_KEYWORD)
|
||||||
|
@ -846,8 +846,8 @@ typedef struct PLpgSQL_stmt_execsql
|
|||||||
PLpgSQL_stmt_type cmd_type;
|
PLpgSQL_stmt_type cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
PLpgSQL_expr *sqlstmt;
|
PLpgSQL_expr *sqlstmt;
|
||||||
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE/MERGE?
|
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? Note:
|
||||||
* Note mod_stmt is set when we plan the query */
|
* mod_stmt is set when we plan the query */
|
||||||
bool into; /* INTO supplied? */
|
bool into; /* INTO supplied? */
|
||||||
bool strict; /* INTO STRICT flag */
|
bool strict; /* INTO STRICT flag */
|
||||||
PLpgSQL_variable *target; /* INTO target (record or row) */
|
PLpgSQL_variable *target; /* INTO target (record or row) */
|
||||||
|
@ -33,10 +33,6 @@ test: insert-conflict-do-update
|
|||||||
test: insert-conflict-do-update-2
|
test: insert-conflict-do-update-2
|
||||||
test: insert-conflict-do-update-3
|
test: insert-conflict-do-update-3
|
||||||
test: insert-conflict-toast
|
test: insert-conflict-toast
|
||||||
test: merge-insert-update
|
|
||||||
test: merge-delete
|
|
||||||
test: merge-update
|
|
||||||
test: merge-match-recheck
|
|
||||||
test: delete-abort-savept
|
test: delete-abort-savept
|
||||||
test: delete-abort-savept-2
|
test: delete-abort-savept-2
|
||||||
test: aborted-keyrevoke
|
test: aborted-keyrevoke
|
||||||
|
@ -386,58 +386,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
|
|||||||
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
||||||
ERROR: identity columns are not supported on partitions
|
ERROR: identity columns are not supported on partitions
|
||||||
DROP TABLE itest_parent;
|
DROP TABLE itest_parent;
|
||||||
-- MERGE tests
|
|
||||||
CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
|
|
||||||
CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
|
||||||
ERROR: cannot insert into column "a"
|
|
||||||
DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
|
|
||||||
HINT: Use OVERRIDING SYSTEM VALUE to override.
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
ERROR: cannot insert into column "a"
|
|
||||||
DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
|
|
||||||
HINT: Use OVERRIDING SYSTEM VALUE to override.
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
SELECT * FROM itest14;
|
|
||||||
a | b
|
|
||||||
----+-------------------
|
|
||||||
30 | inserted by merge
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT * FROM itest15;
|
|
||||||
a | b
|
|
||||||
----+-------------------
|
|
||||||
10 | inserted by merge
|
|
||||||
1 | inserted by merge
|
|
||||||
30 | inserted by merge
|
|
||||||
(3 rows)
|
|
||||||
|
|
||||||
DROP TABLE itest14;
|
|
||||||
DROP TABLE itest15;
|
|
||||||
|
@ -517,104 +517,6 @@ SELECT atest6 FROM atest6; -- ok
|
|||||||
(0 rows)
|
(0 rows)
|
||||||
|
|
||||||
COPY atest6 TO stdout; -- ok
|
COPY atest6 TO stdout; -- ok
|
||||||
-- test column privileges with MERGE
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
CREATE TABLE mtarget (a int, b text);
|
|
||||||
CREATE TABLE msource (a int, b text);
|
|
||||||
INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
|
|
||||||
INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
|
|
||||||
GRANT SELECT (a) ON msource TO regress_priv_user4;
|
|
||||||
GRANT SELECT (a) ON mtarget TO regress_priv_user4;
|
|
||||||
GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
|
|
||||||
GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
|
||||||
--
|
|
||||||
-- test source privileges
|
|
||||||
--
|
|
||||||
-- fail (no SELECT priv on s.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
ERROR: permission denied for table msource
|
|
||||||
-- fail (s.b used in the INSERTed values)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = 'x'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
ERROR: permission denied for table msource
|
|
||||||
-- fail (s.b used in the WHEN quals)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND s.b = 'x' THEN
|
|
||||||
UPDATE SET b = 'x'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
ERROR: permission denied for table msource
|
|
||||||
-- this should be ok since only s.a is accessed
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = 'ok'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
ROLLBACK;
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
GRANT SELECT (b) ON msource TO regress_priv_user4;
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
|
||||||
-- should now be ok
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
ROLLBACK;
|
|
||||||
--
|
|
||||||
-- test target privileges
|
|
||||||
--
|
|
||||||
-- fail (no SELECT priv on t.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = t.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
ERROR: permission denied for table mtarget
|
|
||||||
-- fail (no UPDATE on t.a)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b, a = t.a + 1
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
ERROR: permission denied for table mtarget
|
|
||||||
-- fail (no SELECT on t.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
ERROR: permission denied for table mtarget
|
|
||||||
-- ok
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b;
|
|
||||||
ROLLBACK;
|
|
||||||
-- fail (no DELETE)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
DELETE;
|
|
||||||
ERROR: permission denied for table mtarget
|
|
||||||
-- grant delete privileges
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
GRANT DELETE ON mtarget TO regress_priv_user4;
|
|
||||||
-- should be ok now
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
DELETE;
|
|
||||||
ROLLBACK;
|
|
||||||
-- check error reporting with column privs
|
-- check error reporting with column privs
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||||
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
||||||
|
@ -2138,188 +2138,6 @@ ERROR: new row violates row-level security policy (USING expression) for table
|
|||||||
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
||||||
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
||||||
ERROR: new row violates row-level security policy for table "document"
|
ERROR: new row violates row-level security policy for table "document"
|
||||||
--
|
|
||||||
-- MERGE
|
|
||||||
--
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
DROP POLICY p3_with_all ON document;
|
|
||||||
ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
|
|
||||||
-- all documents are readable
|
|
||||||
CREATE POLICY p1 ON document FOR SELECT USING (true);
|
|
||||||
-- one may insert documents only authored by them
|
|
||||||
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
|
|
||||||
-- one may only update documents in 'novel' category
|
|
||||||
CREATE POLICY p3 ON document FOR UPDATE
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
|
|
||||||
WITH CHECK (dauthor = current_user);
|
|
||||||
-- one may only delete documents in 'manga' category
|
|
||||||
CREATE POLICY p4 ON document FOR DELETE
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
|
||||||
SELECT * FROM document;
|
|
||||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
||||||
-----+-----+--------+-------------------+----------------------------------+--------
|
|
||||||
1 | 11 | 1 | regress_rls_bob | my first novel |
|
|
||||||
3 | 22 | 2 | regress_rls_bob | my science fiction |
|
|
||||||
4 | 44 | 1 | regress_rls_bob | my first manga |
|
|
||||||
5 | 44 | 2 | regress_rls_bob | my second manga |
|
|
||||||
6 | 22 | 1 | regress_rls_carol | great science fiction |
|
|
||||||
7 | 33 | 2 | regress_rls_carol | great technology book |
|
|
||||||
8 | 44 | 1 | regress_rls_carol | great manga |
|
|
||||||
9 | 22 | 1 | regress_rls_dave | awesome science fiction |
|
|
||||||
10 | 33 | 2 | regress_rls_dave | awesome technology book |
|
|
||||||
11 | 33 | 1 | regress_rls_carol | hoge |
|
|
||||||
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
|
||||||
2 | 11 | 2 | regress_rls_bob | my first novel |
|
|
||||||
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
|
||||||
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
|
||||||
(14 rows)
|
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
-- Fails, since update violates WITH CHECK qual on dauthor
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
|
|
||||||
ERROR: new row violates row-level security policy for table "document"
|
|
||||||
-- Should be OK since USING and WITH CHECK quals pass
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
|
|
||||||
-- Even when dauthor is updated explicitly, but to the existing value
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
|
|
||||||
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
|
|
||||||
-- updating an item in category 'science fiction'
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 3 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge ';
|
|
||||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
||||||
-- The same thing with DELETE action, but fails again because no permissions
|
|
||||||
-- to delete items in 'science fiction' category that did 3 belongs to.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 3 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
||||||
-- Document with did 4 belongs to 'manga' category which is allowed for
|
|
||||||
-- deletion. But this fails because the UPDATE action is matched first and
|
|
||||||
-- UPDATE policy does not allow updation in the category.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes = '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
||||||
-- UPDATE action is not matched this time because of the WHEN AND qual.
|
|
||||||
-- DELETE still fails because role regress_rls_bob does not have SELECT
|
|
||||||
-- privileges on 'manga' category row in the category table.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes <> '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
||||||
SELECT * FROM document WHERE did = 4;
|
|
||||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
||||||
-----+-----+--------+-----------------+----------------+--------
|
|
||||||
4 | 44 | 1 | regress_rls_bob | my first manga |
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
|
|
||||||
-- this time
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes <> '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
-- Switch back to regress_rls_bob role
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
-- Try INSERT action. This fails because we are trying to insert
|
|
||||||
-- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
|
|
||||||
-- that
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 12 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
|
|
||||||
ERROR: new row violates row-level security policy for table "document"
|
|
||||||
-- This should be fine
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 12 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
-- ok
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge4 '
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
-- drop and create a new SELECT policy which prevents us from reading
|
|
||||||
-- any document except with category 'magna'
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
DROP POLICY p1 ON document;
|
|
||||||
CREATE POLICY p1 ON document FOR SELECT
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
-- MERGE can no longer see the matching row and hence attempts the
|
|
||||||
-- NOT MATCHED action, which results in unique key violation
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
ERROR: duplicate key value violates unique constraint "document_pkey"
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
-- drop the restrictive SELECT policy so that we can look at the
|
|
||||||
-- final state of the table
|
|
||||||
DROP POLICY p1 ON document;
|
|
||||||
-- Just check everything went per plan
|
|
||||||
SELECT * FROM document;
|
|
||||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
||||||
-----+-----+--------+-------------------+----------------------------------+-----------------------------------------------------------------------
|
|
||||||
3 | 22 | 2 | regress_rls_bob | my science fiction |
|
|
||||||
5 | 44 | 2 | regress_rls_bob | my second manga |
|
|
||||||
6 | 22 | 1 | regress_rls_carol | great science fiction |
|
|
||||||
7 | 33 | 2 | regress_rls_carol | great technology book |
|
|
||||||
8 | 44 | 1 | regress_rls_carol | great manga |
|
|
||||||
9 | 22 | 1 | regress_rls_dave | awesome science fiction |
|
|
||||||
10 | 33 | 2 | regress_rls_dave | awesome technology book |
|
|
||||||
11 | 33 | 1 | regress_rls_carol | hoge |
|
|
||||||
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
|
||||||
2 | 11 | 2 | regress_rls_bob | my first novel |
|
|
||||||
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
|
||||||
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
|
||||||
12 | 11 | 1 | regress_rls_bob | another novel |
|
|
||||||
1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4
|
|
||||||
(14 rows)
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- ROLE/GROUP
|
-- ROLE/GROUP
|
||||||
--
|
--
|
||||||
|
@ -3265,37 +3265,6 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
|
|||||||
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
||||||
DROP TABLE rules_parted_table;
|
DROP TABLE rules_parted_table;
|
||||||
--
|
--
|
||||||
-- test MERGE
|
|
||||||
--
|
|
||||||
CREATE TABLE rule_merge1 (a int, b text);
|
|
||||||
CREATE TABLE rule_merge2 (a int, b text);
|
|
||||||
CREATE RULE rule1 AS ON INSERT TO rule_merge1
|
|
||||||
DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
|
|
||||||
CREATE RULE rule2 AS ON UPDATE TO rule_merge1
|
|
||||||
DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
|
|
||||||
WHERE a = OLD.a;
|
|
||||||
CREATE RULE rule3 AS ON DELETE TO rule_merge1
|
|
||||||
DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
|
|
||||||
-- MERGE not supported for table with rules
|
|
||||||
MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
|
|
||||||
ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.a < 2 THEN
|
|
||||||
UPDATE SET b = b || ' updated by merge'
|
|
||||||
WHEN MATCHED AND t.a > 2 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.a, '');
|
|
||||||
ERROR: MERGE is not supported for relations with rules
|
|
||||||
-- should be ok with the other table though
|
|
||||||
MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
|
|
||||||
ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.a < 2 THEN
|
|
||||||
UPDATE SET b = b || ' updated by merge'
|
|
||||||
WHEN MATCHED AND t.a > 2 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.a, '');
|
|
||||||
--
|
|
||||||
-- Test enabling/disabling
|
-- Test enabling/disabling
|
||||||
--
|
--
|
||||||
CREATE TABLE ruletest1 (a int);
|
CREATE TABLE ruletest1 (a int);
|
||||||
|
@ -2761,54 +2761,6 @@ delete from self_ref where a = 1;
|
|||||||
NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
|
NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
|
||||||
NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
|
NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
|
||||||
drop table self_ref;
|
drop table self_ref;
|
||||||
--
|
|
||||||
-- test transition tables with MERGE
|
|
||||||
--
|
|
||||||
create table merge_target_table (a int primary key, b text);
|
|
||||||
create trigger merge_target_table_insert_trig
|
|
||||||
after insert on merge_target_table referencing new table as new_table
|
|
||||||
for each statement execute procedure dump_insert();
|
|
||||||
create trigger merge_target_table_update_trig
|
|
||||||
after update on merge_target_table referencing old table as old_table new table as new_table
|
|
||||||
for each statement execute procedure dump_update();
|
|
||||||
create trigger merge_target_table_delete_trig
|
|
||||||
after delete on merge_target_table referencing old table as old_table
|
|
||||||
for each statement execute procedure dump_delete();
|
|
||||||
create table merge_source_table (a int, b text);
|
|
||||||
insert into merge_source_table
|
|
||||||
values (1, 'initial1'), (2, 'initial2'),
|
|
||||||
(3, 'initial3'), (4, 'initial4');
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
NOTICE: trigger = merge_target_table_insert_trig, new table = (1,initial1), (2,initial2), (3,initial3), (4,initial4)
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when matched and s.a <= 2 then
|
|
||||||
update set b = t.b || ' updated by merge'
|
|
||||||
when matched and s.a > 2 then
|
|
||||||
delete
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
NOTICE: trigger = merge_target_table_delete_trig, old table = (3,initial3), (4,initial4)
|
|
||||||
NOTICE: trigger = merge_target_table_update_trig, old table = (1,initial1), (2,initial2), new table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge")
|
|
||||||
NOTICE: trigger = merge_target_table_insert_trig, new table = <NULL>
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when matched and s.a <= 2 then
|
|
||||||
update set b = t.b || ' updated again by merge'
|
|
||||||
when matched and s.a > 2 then
|
|
||||||
delete
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
NOTICE: trigger = merge_target_table_delete_trig, old table = <NULL>
|
|
||||||
NOTICE: trigger = merge_target_table_update_trig, old table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge"), new table = (1,"initial1 updated by merge updated again by merge"), (2,"initial2 updated by merge updated again by merge")
|
|
||||||
NOTICE: trigger = merge_target_table_insert_trig, new table = (3,initial3), (4,initial4)
|
|
||||||
drop table merge_source_table, merge_target_table;
|
|
||||||
-- cleanup
|
-- cleanup
|
||||||
drop function dump_insert();
|
drop function dump_insert();
|
||||||
drop function dump_update();
|
drop function dump_update();
|
||||||
|
@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
|
|||||||
# ----------
|
# ----------
|
||||||
# Another group of parallel tests
|
# Another group of parallel tests
|
||||||
# ----------
|
# ----------
|
||||||
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index merge
|
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
|
||||||
|
|
||||||
# ----------
|
# ----------
|
||||||
# Another group of parallel tests
|
# Another group of parallel tests
|
||||||
|
@ -123,7 +123,6 @@ test: tablesample
|
|||||||
test: groupingsets
|
test: groupingsets
|
||||||
test: drop_operator
|
test: drop_operator
|
||||||
test: password
|
test: password
|
||||||
test: merge
|
|
||||||
test: alter_generic
|
test: alter_generic
|
||||||
test: alter_operator
|
test: alter_operator
|
||||||
test: misc
|
test: misc
|
||||||
|
@ -246,48 +246,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
|
|||||||
f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY
|
f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY
|
||||||
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
||||||
DROP TABLE itest_parent;
|
DROP TABLE itest_parent;
|
||||||
|
|
||||||
-- MERGE tests
|
|
||||||
CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
|
|
||||||
CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
|
|
||||||
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
MERGE INTO itest14 t
|
|
||||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
MERGE INTO itest15 t
|
|
||||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
|
||||||
ON t.a = s.s_a
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
|
||||||
|
|
||||||
SELECT * FROM itest14;
|
|
||||||
SELECT * FROM itest15;
|
|
||||||
DROP TABLE itest14;
|
|
||||||
DROP TABLE itest15;
|
|
||||||
|
@ -349,114 +349,6 @@ UPDATE atest5 SET one = 1; -- fail
|
|||||||
SELECT atest6 FROM atest6; -- ok
|
SELECT atest6 FROM atest6; -- ok
|
||||||
COPY atest6 TO stdout; -- ok
|
COPY atest6 TO stdout; -- ok
|
||||||
|
|
||||||
-- test column privileges with MERGE
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
CREATE TABLE mtarget (a int, b text);
|
|
||||||
CREATE TABLE msource (a int, b text);
|
|
||||||
INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
|
|
||||||
INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
|
|
||||||
|
|
||||||
GRANT SELECT (a) ON msource TO regress_priv_user4;
|
|
||||||
GRANT SELECT (a) ON mtarget TO regress_priv_user4;
|
|
||||||
GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
|
|
||||||
GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
|
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- test source privileges
|
|
||||||
--
|
|
||||||
|
|
||||||
-- fail (no SELECT priv on s.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
|
|
||||||
-- fail (s.b used in the INSERTed values)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = 'x'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
|
|
||||||
-- fail (s.b used in the WHEN quals)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND s.b = 'x' THEN
|
|
||||||
UPDATE SET b = 'x'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
|
|
||||||
-- this should be ok since only s.a is accessed
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = 'ok'
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
ROLLBACK;
|
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
GRANT SELECT (b) ON msource TO regress_priv_user4;
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
|
||||||
|
|
||||||
-- should now be ok
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
ROLLBACK;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- test target privileges
|
|
||||||
--
|
|
||||||
|
|
||||||
-- fail (no SELECT priv on t.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = t.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, NULL);
|
|
||||||
|
|
||||||
-- fail (no UPDATE on t.a)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b, a = t.a + 1
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
|
|
||||||
-- fail (no SELECT on t.b)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
UPDATE SET b = s.b
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (a, b);
|
|
||||||
|
|
||||||
-- ok
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET b = s.b;
|
|
||||||
ROLLBACK;
|
|
||||||
|
|
||||||
-- fail (no DELETE)
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
DELETE;
|
|
||||||
|
|
||||||
-- grant delete privileges
|
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
|
||||||
GRANT DELETE ON mtarget TO regress_priv_user4;
|
|
||||||
-- should be ok now
|
|
||||||
BEGIN;
|
|
||||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
|
||||||
DELETE;
|
|
||||||
ROLLBACK;
|
|
||||||
|
|
||||||
-- check error reporting with column privs
|
-- check error reporting with column privs
|
||||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||||
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
||||||
|
@ -812,162 +812,6 @@ INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel')
|
|||||||
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
||||||
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
||||||
|
|
||||||
--
|
|
||||||
-- MERGE
|
|
||||||
--
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
DROP POLICY p3_with_all ON document;
|
|
||||||
|
|
||||||
ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
|
|
||||||
-- all documents are readable
|
|
||||||
CREATE POLICY p1 ON document FOR SELECT USING (true);
|
|
||||||
-- one may insert documents only authored by them
|
|
||||||
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
|
|
||||||
-- one may only update documents in 'novel' category
|
|
||||||
CREATE POLICY p3 ON document FOR UPDATE
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
|
|
||||||
WITH CHECK (dauthor = current_user);
|
|
||||||
-- one may only delete documents in 'manga' category
|
|
||||||
CREATE POLICY p4 ON document FOR DELETE
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
|
||||||
|
|
||||||
SELECT * FROM document;
|
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
|
|
||||||
-- Fails, since update violates WITH CHECK qual on dauthor
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
|
|
||||||
|
|
||||||
-- Should be OK since USING and WITH CHECK quals pass
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
|
|
||||||
|
|
||||||
-- Even when dauthor is updated explicitly, but to the existing value
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
|
|
||||||
|
|
||||||
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
|
|
||||||
-- updating an item in category 'science fiction'
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 3 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge ';
|
|
||||||
|
|
||||||
-- The same thing with DELETE action, but fails again because no permissions
|
|
||||||
-- to delete items in 'science fiction' category that did 3 belongs to.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 3 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
|
|
||||||
-- Document with did 4 belongs to 'manga' category which is allowed for
|
|
||||||
-- deletion. But this fails because the UPDATE action is matched first and
|
|
||||||
-- UPDATE policy does not allow updation in the category.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes = '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
|
|
||||||
-- UPDATE action is not matched this time because of the WHEN AND qual.
|
|
||||||
-- DELETE still fails because role regress_rls_bob does not have SELECT
|
|
||||||
-- privileges on 'manga' category row in the category table.
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes <> '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
|
|
||||||
SELECT * FROM document WHERE did = 4;
|
|
||||||
|
|
||||||
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
|
|
||||||
-- this time
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
||||||
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 4 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED AND dnotes <> '' THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE;
|
|
||||||
|
|
||||||
-- Switch back to regress_rls_bob role
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
|
|
||||||
-- Try INSERT action. This fails because we are trying to insert
|
|
||||||
-- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
|
|
||||||
-- that
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 12 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
|
|
||||||
|
|
||||||
-- This should be fine
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 12 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
|
|
||||||
-- ok
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge4 '
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
|
|
||||||
-- drop and create a new SELECT policy which prevents us from reading
|
|
||||||
-- any document except with category 'magna'
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
DROP POLICY p1 ON document;
|
|
||||||
CREATE POLICY p1 ON document FOR SELECT
|
|
||||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
||||||
|
|
||||||
-- MERGE can no longer see the matching row and hence attempts the
|
|
||||||
-- NOT MATCHED action, which results in unique key violation
|
|
||||||
MERGE INTO document d
|
|
||||||
USING (SELECT 1 as sdid) s
|
|
||||||
ON did = s.sdid
|
|
||||||
WHEN MATCHED THEN
|
|
||||||
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
||||||
|
|
||||||
RESET SESSION AUTHORIZATION;
|
|
||||||
-- drop the restrictive SELECT policy so that we can look at the
|
|
||||||
-- final state of the table
|
|
||||||
DROP POLICY p1 ON document;
|
|
||||||
-- Just check everything went per plan
|
|
||||||
SELECT * FROM document;
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- ROLE/GROUP
|
-- ROLE/GROUP
|
||||||
--
|
--
|
||||||
|
@ -1191,39 +1191,6 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
|
|||||||
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
||||||
DROP TABLE rules_parted_table;
|
DROP TABLE rules_parted_table;
|
||||||
|
|
||||||
--
|
|
||||||
-- test MERGE
|
|
||||||
--
|
|
||||||
CREATE TABLE rule_merge1 (a int, b text);
|
|
||||||
CREATE TABLE rule_merge2 (a int, b text);
|
|
||||||
CREATE RULE rule1 AS ON INSERT TO rule_merge1
|
|
||||||
DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
|
|
||||||
CREATE RULE rule2 AS ON UPDATE TO rule_merge1
|
|
||||||
DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
|
|
||||||
WHERE a = OLD.a;
|
|
||||||
CREATE RULE rule3 AS ON DELETE TO rule_merge1
|
|
||||||
DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
|
|
||||||
|
|
||||||
-- MERGE not supported for table with rules
|
|
||||||
MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
|
|
||||||
ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.a < 2 THEN
|
|
||||||
UPDATE SET b = b || ' updated by merge'
|
|
||||||
WHEN MATCHED AND t.a > 2 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.a, '');
|
|
||||||
|
|
||||||
-- should be ok with the other table though
|
|
||||||
MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
|
|
||||||
ON t.a = s.a
|
|
||||||
WHEN MATCHED AND t.a < 2 THEN
|
|
||||||
UPDATE SET b = b || ' updated by merge'
|
|
||||||
WHEN MATCHED AND t.a > 2 THEN
|
|
||||||
DELETE
|
|
||||||
WHEN NOT MATCHED THEN
|
|
||||||
INSERT VALUES (s.a, '');
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Test enabling/disabling
|
-- Test enabling/disabling
|
||||||
--
|
--
|
||||||
|
@ -2110,53 +2110,6 @@ delete from self_ref where a = 1;
|
|||||||
|
|
||||||
drop table self_ref;
|
drop table self_ref;
|
||||||
|
|
||||||
--
|
|
||||||
-- test transition tables with MERGE
|
|
||||||
--
|
|
||||||
create table merge_target_table (a int primary key, b text);
|
|
||||||
create trigger merge_target_table_insert_trig
|
|
||||||
after insert on merge_target_table referencing new table as new_table
|
|
||||||
for each statement execute procedure dump_insert();
|
|
||||||
create trigger merge_target_table_update_trig
|
|
||||||
after update on merge_target_table referencing old table as old_table new table as new_table
|
|
||||||
for each statement execute procedure dump_update();
|
|
||||||
create trigger merge_target_table_delete_trig
|
|
||||||
after delete on merge_target_table referencing old table as old_table
|
|
||||||
for each statement execute procedure dump_delete();
|
|
||||||
|
|
||||||
create table merge_source_table (a int, b text);
|
|
||||||
insert into merge_source_table
|
|
||||||
values (1, 'initial1'), (2, 'initial2'),
|
|
||||||
(3, 'initial3'), (4, 'initial4');
|
|
||||||
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when matched and s.a <= 2 then
|
|
||||||
update set b = t.b || ' updated by merge'
|
|
||||||
when matched and s.a > 2 then
|
|
||||||
delete
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
|
|
||||||
merge into merge_target_table t
|
|
||||||
using merge_source_table s
|
|
||||||
on t.a = s.a
|
|
||||||
when matched and s.a <= 2 then
|
|
||||||
update set b = t.b || ' updated again by merge'
|
|
||||||
when matched and s.a > 2 then
|
|
||||||
delete
|
|
||||||
when not matched then
|
|
||||||
insert values (a, b);
|
|
||||||
|
|
||||||
drop table merge_source_table, merge_target_table;
|
|
||||||
|
|
||||||
-- cleanup
|
-- cleanup
|
||||||
drop function dump_insert();
|
drop function dump_insert();
|
||||||
drop function dump_update();
|
drop function dump_update();
|
||||||
|
@ -1228,8 +1228,6 @@ MemoryContextCallbackFunction
|
|||||||
MemoryContextCounters
|
MemoryContextCounters
|
||||||
MemoryContextData
|
MemoryContextData
|
||||||
MemoryContextMethods
|
MemoryContextMethods
|
||||||
MergeAction
|
|
||||||
MergeActionState
|
|
||||||
MergeAppend
|
MergeAppend
|
||||||
MergeAppendPath
|
MergeAppendPath
|
||||||
MergeAppendState
|
MergeAppendState
|
||||||
@ -1237,7 +1235,6 @@ MergeJoin
|
|||||||
MergeJoinClause
|
MergeJoinClause
|
||||||
MergeJoinState
|
MergeJoinState
|
||||||
MergePath
|
MergePath
|
||||||
MergeStmt
|
|
||||||
MergeScanSelCache
|
MergeScanSelCache
|
||||||
MetaCommand
|
MetaCommand
|
||||||
MinMaxAggInfo
|
MinMaxAggInfo
|
||||||
|
Loading…
x
Reference in New Issue
Block a user