MERGE Specification
The following two files specify the behaviour of the MERGE statement and
how it will work in the world of PostgreSQL. In places, this supercedes
my recent commentary on MERGE, particularly with regard to triggers.
Neither of these files is intended for CVS.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.
The test file shows how I expect a successful test run to look when a
regression test is executed with a working version of final MERGE patch
applied. It has behavioural comments in it also, to make it slightly
more readable.
If anybody has any questions, ask them now please, before I begin
detailed implementation.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
Hello Simon,
would you support RETURNING clause?
Regards
Pavel Stehule
Show quoted text
On 21/04/2008, Simon Riggs <simon@2ndquadrant.com> wrote:
The following two files specify the behaviour of the MERGE statement and
how it will work in the world of PostgreSQL. In places, this supercedes
my recent commentary on MERGE, particularly with regard to triggers.Neither of these files is intended for CVS.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.The test file shows how I expect a successful test run to look when a
regression test is executed with a working version of final MERGE patch
applied. It has behavioural comments in it also, to make it slightly
more readable.If anybody has any questions, ask them now please, before I begin
detailed implementation.--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Apr 21, 2008, at 4:08 PM, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement
and
how it will work in the world of PostgreSQL. In places, this
supercedes
my recent commentary on MERGE, particularly with regard to triggers.Neither of these files is intended for CVS.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.The test file shows how I expect a successful test run to look when a
regression test is executed with a working version of final MERGE
patch
applied. It has behavioural comments in it also, to make it slightly
more readable.If anybody has any questions, ask them now please, before I begin
detailed implementation.
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?
Cheers,
M
From books at ejurka.com Mon Apr 21 20:38:15 2008
From: books at ejurka.com (Kris Jurka)
Date: Mon, 21 Apr 2008 16:38:15 -0400 (EDT)
Subject: [Pljava-dev] stack depth limit exceeded - patch possible?
In-Reply-To: <480CBE23.8050006@par.univie.ac.at>
References: <48010499.30000@par.univie.ac.at>
<Pine.BSO.4.64.0804131023000.9928@leary.csoft.net>
<58782.88.116.137.78.1208177811.squirrel@www.par.univie.ac.at>
<Pine.BSO.4.64.0804141112330.5378@leary.csoft.net>
<48060EE6.9040009@par.univie.ac.at>
<4806264F.1060800@ejurka.com> <48074F27.9050606@par.univie.ac.at>
<Pine.BSO.4.64.0804171204310.553@leary.csoft.net>
<480CBE23.8050006@par.univie.ac.at>
Message-ID: <Pine.BSO.4.64.0804211636140.14425@leary.csoft.net>
On Mon, 21 Apr 2008, Alexander W?hrer wrote:
Dear Kris,
thank you very much for building a patched version for me -
unfortunatelly I ran into the following problem after replacing the
pljava.jar and pljava.dll with the patched ones from your archive:java.lang.NoSuchMethodError: _fetch
There's an explicit binding between the C and Java parts of pljava that I
forgot to update when I changed the function signature for move and fetch
to include the thread id. I've done that and put up a new test release:
http://ejurka.com/pgsql/pljava/wohrer
Kris Jurka
On Mon, 2008-04-21 at 22:18 +0200, Pavel Stehule wrote:
would you support RETURNING clause?
I wouldn't rule it out completely, but not in the first implementation
because
- its more work
- its not in the SQL Standard
- neither Oracle nor DB2 support it either, so its only going to provide
incompatibility
- there are some wrinkles with MERGE that means I don't want to
over-complicate it because it looks to me like it will change in future
versions of the standard
- not sure what the use case for that will be
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?
Yes, that's right. Just like COPY. That seems fine to me because you're
likely to be doing a MERGE immediately after a COPY anyway, so the
restriction just continues.
Rules for Insert, Update and Delete are almost identical to the way
MERGE works anyway, so there's no particular loss of functionality. That
was why I co-opted the ability to DO NOTHING in a WHEN clause from the
way PostgreSQL Rules work.
I'm not taking any explicit decisions to exclude them permanently. I do
think its possible that we could support them and possibly very cheaply,
but I wouldn't make any promises initially.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
Simon Riggs <simon@2ndquadrant.com> writes:
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?
Yes, that's right. Just like COPY.
I find this to be pretty ugly. COPY is a special case because
(a) it is a utility statement not a plannable one, and (b) its only
reason to exist is to transfer data as fast as possible, so bypassing
rules isn't an unreasonable restriction. MERGE has neither of those
properties, and thus arguing that it can or should be like COPY is an
entirely unconvincing proposition. (In fact, I don't even want to think
about what kind of crock you're going to need in order to get it through
the planner without also going through the rewriter.)
Please think a bit harder.
regards, tom lane
On Mon, 2008-04-21 at 20:28 -0400, Tom Lane wrote:
In fact, I don't even want to think
about what kind of crock you're going to need in order to get it through
the planner without also going through the rewriter.
Hmmm, I hadn't thought I might be adding work rather than avoiding it.
I'll give it a go.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Mon, 2008-04-21 at 20:28 -0400, Tom Lane wrote:
Simon Riggs <simon@2ndquadrant.com> writes:
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?Yes, that's right. Just like COPY.
I find this to be pretty ugly. COPY is a special case because
(a) it is a utility statement not a plannable one, and (b) its only
reason to exist is to transfer data as fast as possible, so bypassing
rules isn't an unreasonable restriction. MERGE has neither of those
properties, and thus arguing that it can or should be like COPY is an
entirely unconvincing proposition.
Unrelated to rule processing, you did read the bit about MERGE and race
conditions? ISTM that MERGE as it stands isn't very useful for anything
other than large data loads since its going to cause problems if used
concurrently.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
Simon Riggs wrote:
Unrelated to rule processing, you did read the bit about MERGE and race
conditions? ISTM that MERGE as it stands isn't very useful for anything
other than large data loads since its going to cause problems if used
concurrently.
But that's how the committee designed it, yes?
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
"Simon Riggs" <simon@2ndquadrant.com> writes:
Unrelated to rule processing, you did read the bit about MERGE and race
conditions? ISTM that MERGE as it stands isn't very useful for anything
other than large data loads since its going to cause problems if used
concurrently.
If there are race conditions what advantage does it offer over writing plpgsql
or client code to do it?
I thought the whole advantage of having a built-in command is that it could do
the kind of locking our unique constraints do to avoid race conditions.
--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com
Ask me about EnterpriseDB's On-Demand Production Tuning
On Mon, 2008-04-21 at 21:57 -0400, Alvaro Herrera wrote:
Simon Riggs wrote:
Unrelated to rule processing, you did read the bit about MERGE and race
conditions? ISTM that MERGE as it stands isn't very useful for anything
other than large data loads since its going to cause problems if used
concurrently.But that's how the committee designed it, yes?
Yes. Not sure if I see your point there, but yes, that's how its been
designed.
Both DB2 and Oracle have additional items to get around the shortcomings
of the command.
The way MERGE works we first test to see if it matches or not, then if
not matched we would activate the NOT MATCHED action, which standard
says must be an insert. The gap between the two actions allows a race
condition to exist.
We could close the gap by taking a lock on the row when we perform the
is-matched test, but that would be expensive for bulk operations. ISTM
the lock should be optional. Not sure what the default should be. Input
welcome.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Tue, Apr 22, 2008 at 08:24:58AM +0100, Simon Riggs wrote:
The way MERGE works we first test to see if it matches or not, then if
not matched we would activate the NOT MATCHED action, which standard
says must be an insert. The gap between the two actions allows a race
condition to exist.We could close the gap by taking a lock on the row when we perform the
is-matched test, but that would be expensive for bulk operations. ISTM
the lock should be optional. Not sure what the default should be. Input
welcome.
ISTM that if the original select does a SELECT FOR UPDATE then it
should work fine for UPDATEs since any update with overwrite the xmax
field anyway.
What you can't do is prevent multiple inserts. Though if its a unique
index you should be able to do the same trick as normal inserts: create
the row, try to insert into the index and if that fails fall back to
doing an update.
What you really need for this though is a non-fatal _bt_check_unique so
you can recover without having a savepoint for every row.
Have a nice day,
--
Martijn van Oosterhout <kleptog@svana.org> http://svana.org/kleptog/
Show quoted text
Please line up in a tree and maintain the heap invariant while
boarding. Thank you for flying nlogn airlines.
On Tue, 2008-04-22 at 10:02 +0200, Martijn van Oosterhout wrote:
On Tue, Apr 22, 2008 at 08:24:58AM +0100, Simon Riggs wrote:
The way MERGE works we first test to see if it matches or not, then if
not matched we would activate the NOT MATCHED action, which standard
says must be an insert. The gap between the two actions allows a race
condition to exist.We could close the gap by taking a lock on the row when we perform the
is-matched test, but that would be expensive for bulk operations. ISTM
the lock should be optional. Not sure what the default should be. Input
welcome.ISTM that if the original select does a SELECT FOR UPDATE then it
should work fine for UPDATEs since any update with overwrite the xmax
field anyway.
Yes, agreed, that's what I meant by the lock on the row.
Incidentally, this is essentially the same problem that occurs with
SERIALIZABLE updates.
It should be easy enough to put an optional "LOCK MATCHED ROW" clause
into the MERGE statement, as an extension. The Standard doesn't specify
the lock timing.
What you can't do is prevent multiple inserts. Though if its a unique
index you should be able to do the same trick as normal inserts: create
the row, try to insert into the index and if that fails fall back to
doing an update.
The Standard doesn't really allow that. It's either matched or its not.
MERGE is specifically
1. Match
2. Update or Insert as per step (1), following complex logic
rather than
1. Update
2. if not matched Insert
which is exactly what the MySQL and Teradata upsert statements do, but
only for single row operations, unlike MERGE.
For MERGE, there is no "lets try one of these and if not, I'll switch".
You decide which it is going to be and then do it. Which can fail...
I guess we could just spin through, re-testing the match each time and
re-initiating an action, but I see problems there also, not least of
which is it violates the standard. That may not be that clever, but
there may be reasons we can't see yet, or reasons that would affect
other implementors. Guidance, please, if anybody sees clearly?
What you really need for this though is a non-fatal _bt_check_unique so
you can recover without having a savepoint for every row.
Oracle simply fails in the event of a uniqueness violation, even though
it logs other errors. DB2 fails unconditionally if there is even a
single error. The MySQL and Teradata syntax don't seem to offer any
protection from concurrent inserts either. Teradata and DB2 both use
locking, so they would lock the value prior to the update anyway, so the
update, insert issue would not happen for them at least.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Mon, 2008-04-21 at 22:27 -0400, Gregory Stark wrote:
"Simon Riggs" <simon@2ndquadrant.com> writes:
Unrelated to rule processing, you did read the bit about MERGE and race
conditions? ISTM that MERGE as it stands isn't very useful for anything
other than large data loads since its going to cause problems if used
concurrently.If there are race conditions what advantage does it offer over writing plpgsql
or client code to do it?
That's an excellent question. I'm not trying to sell you anything here.
MERGE is a SQL Standard command, supported by Oracle, DB2, SQLServer
etc, so there is reason enough to implement it.
There may be also reasons to implement other syntaxes, other behaviours,
which would be non-standard. If people want the latter first/second/not
at all then please speak, its not an either-or situation.
I would expect MERGE to be slightly faster than a well coded PL/pgSQL
function, but there won't be too much in it. It will allow the statement
to be more easily parallelised in the form it currently takes, I would
note.
I thought the whole advantage of having a built-in command is that it could do
the kind of locking our unique constraints do to avoid race conditions.
As I've said elsewhere, we could have it lock each row, its just more
overhead if we do and not necessary at all for bulk data merging.
I'll presume we want locking as an option, unless people say otherwise.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
"Simon Riggs" <simon@2ndquadrant.com> writes:
As I've said elsewhere, we could have it lock each row, its just more
overhead if we do and not necessary at all for bulk data merging.I'll presume we want locking as an option, unless people say otherwise.
It's not so simple. If you look for a row to merge into and don't find one
there's no row to lock. What unique constraints do is hold the lock on the
index page where the entry would have to be added.
That's the trick that plpgsql cannot implement. That's why users are forced to
loop and retry until they manage to do an update successfully or insert
successfully.
--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com
Ask me about EnterpriseDB's Slony Replication support!
On Apr 22, 2008, at 1:47 PM, Simon Riggs wrote:
On Mon, 2008-04-21 at 22:27 -0400, Gregory Stark wrote:
"Simon Riggs" <simon@2ndquadrant.com> writes:
Unrelated to rule processing, you did read the bit about MERGE and
race
conditions? ISTM that MERGE as it stands isn't very useful for
anything
other than large data loads since its going to cause problems if
used
concurrently.If there are race conditions what advantage does it offer over
writing plpgsql
or client code to do it?That's an excellent question. I'm not trying to sell you anything
here.
MERGE is a SQL Standard command, supported by Oracle, DB2, SQLServer
etc, so there is reason enough to implement it.There may be also reasons to implement other syntaxes, other
behaviours,
which would be non-standard. If people want the latter first/second/
not
at all then please speak, its not an either-or situation.I would expect MERGE to be slightly faster than a well coded PL/pgSQL
function, but there won't be too much in it. It will allow the
statement
to be more easily parallelised in the form it currently takes, I would
note.I thought the whole advantage of having a built-in command is that
it could do
the kind of locking our unique constraints do to avoid race
conditions.As I've said elsewhere, we could have it lock each row, its just more
overhead if we do and not necessary at all for bulk data merging.
I was hoping to use MERGE alongside the other standard DML. Its
purpose is to set the "truth" regardless of past states.
Why should it be relegated to the bulk-loading basement alongside COPY?
Cheers,
M
On Apr 22, 2008, at 1:17 PM, Gregory Stark wrote:
"Simon Riggs" <simon@2ndquadrant.com> writes:
As I've said elsewhere, we could have it lock each row, its just more
overhead if we do and not necessary at all for bulk data merging.I'll presume we want locking as an option, unless people say
otherwise.It's not so simple. If you look for a row to merge into and don't
find one
there's no row to lock. What unique constraints do is hold the lock
on the
index page where the entry would have to be added.That's the trick that plpgsql cannot implement. That's why users
are forced to
loop and retry until they manage to do an update successfully or
insert
successfully.
Yeah, hopefully there's a better way to do this other than row locks.
But no matter how this is done, I think we need to handle the race
conditions, and handle them by default. If people *really* know what
they're doing, they can disable the row locking (perhaps one way to
do this would be to grab an explicit lock on the table and have merge
check for that...).
On a different note, if you intend for the SGML to become the doc
page for MERGE, I'd really like to see some more complex examples
showing both delete and more than 2 WHEN cases. Something like the
"multiple actions on single target row" test case would work.
--
Decibel!, aka Jim C. Nasby, Database Architect decibel@decibel.org
Give your computer some brain candy! www.distributed.net Team #1828
Attachments:
On Tue, Apr 22, 2008 at 02:19:24PM -0500, Decibel! wrote:
But no matter how this is done, I think we need to handle the race
conditions, and handle them by default. If people *really* know what
they're doing, they can disable the row locking (perhaps one way to
do this would be to grab an explicit lock on the table and have merge
check for that...).
I disagree. The spec doesn't require it and MERGE is useful without it.
For a first cut I would say implement as the spec says, race conditions
and all. Later we can think on whether it's worth handling them
directly.
Have a nice day,
--
Martijn van Oosterhout <kleptog@svana.org> http://svana.org/kleptog/
Show quoted text
Please line up in a tree and maintain the heap invariant while
boarding. Thank you for flying nlogn airlines.
On Apr 22, 2008, at 3:20 PM, Martijn van Oosterhout wrote:
On Tue, Apr 22, 2008 at 02:19:24PM -0500, Decibel! wrote:
But no matter how this is done, I think we need to handle the race
conditions, and handle them by default. If people *really* know what
they're doing, they can disable the row locking (perhaps one way to
do this would be to grab an explicit lock on the table and have merge
check for that...).I disagree. The spec doesn't require it and MERGE is useful without
it.
For a first cut I would say implement as the spec says, race
conditions
and all. Later we can think on whether it's worth handling them
directly.
That really strikes me as taking the "MySQL route". If push comes to
shove, I'll take a MERGE with race conditions over no merge at all,
but I think it's very important that it does the right thing. Just
because the spec doesn't say anything about it doesn't mean it's ok.
--
Decibel!, aka Jim C. Nasby, Database Architect decibel@decibel.org
Give your computer some brain candy! www.distributed.net Team #1828
Attachments:
Decibel! <decibel@decibel.org> writes:
That really strikes me as taking the "MySQL route". If push comes to
shove, I'll take a MERGE with race conditions over no merge at all,
but I think it's very important that it does the right thing. Just
because the spec doesn't say anything about it doesn't mean it's ok.
Agreed. It seems to me that in the last set of discussions, we rejected
implementing MERGE precisely because it failed to provide a solution to
the race-condition problem. I'm not satisfied with a version that
doesn't handle that, because I think that is *exactly* what most people
will try to use it for. The non-concurrent bulk update case that Simon
is arguing for is the uncommon usage.
regards, tom lane
decibel@decibel.org (Decibel!) writes:
On Apr 22, 2008, at 1:17 PM, Gregory Stark wrote:
"Simon Riggs" <simon@2ndquadrant.com> writes:
As I've said elsewhere, we could have it lock each row, its just more
overhead if we do and not necessary at all for bulk data merging.I'll presume we want locking as an option, unless people say
otherwise.It's not so simple. If you look for a row to merge into and don't
find one
there's no row to lock. What unique constraints do is hold the lock
on the
index page where the entry would have to be added.That's the trick that plpgsql cannot implement. That's why users
are forced to
loop and retry until they manage to do an update successfully or
insert
successfully.Yeah, hopefully there's a better way to do this other than row locks.
But no matter how this is done, I think we need to handle the race
conditions, and handle them by default. If people *really* know what
they're doing, they can disable the row locking (perhaps one way to
do this would be to grab an explicit lock on the table and have merge
check for that...).
I agree that handling the race conditions by default is preferable.
Consider: An excellent reason to prefer MERGE is if it handles race
conditions that would otherwise require application code be more
carefully and cleverly written to avoid the race conditions.
If MERGE "solves it automatically," and eliminates hand-written code,
that's TWO benefits, that quite likely outweigh any performance costs.
I know we've had cases where race conditions [that a well-done MERGE
would probably solve easily] bit us badly, requiring considerable
development effort, and the addition of extra table columns and such.
There are some possibilities of "worst case" MERGE being "pretty
slow;" it's likely to be faster than the alternatives we used in its
absence ;-).
--
(reverse (concatenate 'string "ofni.secnanifxunil" "@" "enworbbc"))
http://www3.sympatico.ca/cbbrowne/wp.html
"I once went to a shrink. He told me to speak freely. I did. The
damn fool tried to charge me $90 an hour."
-- jimjr@qis.net (Jim Moore Jr)
On Thu, 2008-04-24 at 12:19 -0400, Tom Lane wrote:
Decibel! <decibel@decibel.org> writes:
That really strikes me as taking the "MySQL route". If push comes to
shove, I'll take a MERGE with race conditions over no merge at all,
but I think it's very important that it does the right thing. Just
because the spec doesn't say anything about it doesn't mean it's ok.Agreed. It seems to me that in the last set of discussions, we rejected
implementing MERGE precisely because it failed to provide a solution to
the race-condition problem. I'm not satisfied with a version that
doesn't handle that, because I think that is *exactly* what most people
will try to use it for. The non-concurrent bulk update case that Simon
is arguing for is the uncommon usage.
If y'all think that, then I will do it that way.
The only protection from the race condition is to do the Insert first.
With MERGE, we would need to do it like this:
1. If there are any WHEN NOT MATCHED clauses that trigger INSERTs, then
attempt to apply them first, no matter what order they were in with
respect to the WHEN MATCHED clauses. Start loop at step (3) every time.
If there aren't any, start loop straight at step (5). Note that we would
need to check to see that INSERTs had not been removed by Rules.
2. For each row retrieved by outer join, goto either step 3 or 5 as
established before the loop starts.
3. Try to apply the WHEN NOT MATCHED clauses. The ordering of the
clauses with respect to each other will remain exactly as stated. If one
of the clauses activates an INSERT, we start an internal subtransaction
and perform the INSERT action. If it succeeds, we commit the
subtransaction and continue.
4. If the INSERT fails with a uniqueness violation, we shrug. The ERROR
has caused the subtransaction to abort.
5. Process WHEN MATCHED clauses and continue.
Technically, this is a standards violation because of the potentially
out-of-order execution of the when clauses. Though the end result is not
distinguishable from standards compliant behaviour, AFAICS.
Note that in step 3 we *must* use subtransactions if there is more than
1 unique index on a table, otherwise we might succeed with the first
index and fail with the second. Using a subtransaction per row pretty
much rules out an efficient bulk load.
Note also that this results in a version optimised for INSERT. If we end
up doing an UPDATE there will be two dead rows, probably in two separate
blocks. We hope that doesn't matter because of HOT.
There's probably a reasonable argument for having an optional keyword to
make MERGE behave differently for bulk loads, but I'll save that now for
another day. Focus now is on a command that works well for OLTP cases.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Thursday 24 April 2008 12:19, Tom Lane wrote:
Decibel! <decibel@decibel.org> writes:
That really strikes me as taking the "MySQL route". If push comes to
shove, I'll take a MERGE with race conditions over no merge at all,
but I think it's very important that it does the right thing. Just
because the spec doesn't say anything about it doesn't mean it's ok.Agreed. It seems to me that in the last set of discussions, we rejected
implementing MERGE precisely because it failed to provide a solution to
the race-condition problem. I'm not satisfied with a version that
doesn't handle that, because I think that is *exactly* what most people
will try to use it for. The non-concurrent bulk update case that Simon
is arguing for is the uncommon usage.
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you keep
the spec behavior for the spec syntax, and have a clearly non-spec command
for non-spec behavior.
--
Robert Treat
Build A Brighter LAMP :: Linux Apache {middleware} PostgreSQL
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you keep
the spec behavior for the spec syntax, and have a clearly non-spec command
for non-spec behavior.
In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)
I recently came across the expression "YAGNI", and think it's probably
pretty relevant to this discussion:
http://en.wikipedia.org/wiki/You_Ain't_Gonna_Need_It
regards, tom lane
On Tue, 2008-04-22 at 00:24 +0100, Simon Riggs wrote:
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?Yes, that's right. Just like COPY. That seems fine to me because you're
likely to be doing a MERGE immediately after a COPY anyway, so the
restriction just continues.
May be the bulk data merging variant of MERGE to be used after initial
COPY should be a variant of COPY with special keyword(s) instead of
MERGE ?
------------
Hannu
On Thu, 2008-04-24 at 23:40 -0400, Tom Lane wrote:
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you keep
the spec behavior for the spec syntax, and have a clearly non-spec command
for non-spec behavior.In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)
A Freudian slip? Hopefully, you meant "apart from Simon's request." ;-)
I recently came across the expression "YAGNI", and think it's probably
pretty relevant to this discussion:
http://en.wikipedia.org/wiki/You_Ain't_Gonna_Need_It
In matters of technical implementation, I follow you almost without
question, and very happily so.
I think all of us should be careful when expressing views on what other
people need or don't need. We sleep soundly after having given such an
opinion, but that doesn't make those opinions valid. I'm not sure if
there is a pithy acronym for that thought.
In this case, I had already agreed to do it the safe way, for OLTP. I
believe there is a need for other behaviour as well, but that isn't the
use case that the majority are expressing at this time.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Thu, Apr 24, 2008 at 11:40:22PM -0400, Tom Lane wrote:
In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)
I could have used something like this a few years ago. I don't think it
would get mentioned on the lists, because frankly it's not something I
would've expected a DBMS to handle internally. Certainly I'd never
heard of the MERGE command until recently. I just wrote a program to do
it (and no, race conditions wern't an issue).
Making a race condition free version is fine, just as long as merging
on a condition without a unique index is also supported.
Have a nice day,
--
Martijn van Oosterhout <kleptog@svana.org> http://svana.org/kleptog/
Show quoted text
Please line up in a tree and maintain the heap invariant while
boarding. Thank you for flying nlogn airlines.
Tom Lane wrote:
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you keep
the spec behavior for the spec syntax, and have a clearly non-spec command
for non-spec behavior.In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)
While I agree that there is zero demand for anything else then "UPSERT"
version of MERGE INTO, I don't think that doing the *exact opposite* of
what SQL standard says without any change of syntax to clearly
differentiate the behavior is best thing to do.
Another thing is, the table on which we do SELECT (the one in USING) can
be different from target table and we can use columns from that table in
INSERT/UPDATE statement (probably one the reasons why spec says the
"SELECT" query has to be executed before any changes). How you want to
use the "INSERT first" implementation in this scenario ? IMHO you still
need to have both implementations in the end. So we probably need to
implement the standard one first and then implement our version and put
some restrictions of what can be in USING or INSERT part when using it.
Regards
Petr Jelinek
--
Regards
Petr Jelinek (PJMODOS)
On Thu, 2008-04-24 at 20:28 +0100, Simon Riggs wrote:
With MERGE, we would need to do it like this:
1. If there are any WHEN NOT MATCHED clauses that trigger INSERTs, then
attempt to apply them first, no matter what order they were in with
respect to the WHEN MATCHED clauses. Start loop at step (3) every time.
If there aren't any, start loop straight at step (5). Note that we would
need to check to see that INSERTs had not been removed by Rules.
This would only be needed when there is at least one unique index.
MERGE will work, whether or not a unique index exists. If there is no
unique index then MERGE will silently ignore duplicate rows produced by
concurrent INSERTs, though that is no different from what can occur now.
No special action will be required to handle DELETEs, since attempting
to delete a row twice doesn't produce an error.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Fri, 2008-04-25 at 11:07 +0200, Petr Jelinek wrote:
Another thing is, the table on which we do SELECT (the one in USING) can
be different from target table and we can use columns from that table in
INSERT/UPDATE statement (probably one the reasons why spec says the
"SELECT" query has to be executed before any changes). How you want to
use the "INSERT first" implementation in this scenario ? IMHO you still
need to have both implementations in the end. So we probably need to
implement the standard one first and then implement our version and put
some restrictions of what can be in USING or INSERT part when using it.
We do this:
1. Run using clause,
then for each row
2. NOT MATCHED rules
3. MATCHED
I'm now happy that we can get a spec-compliant end result by always
forcing NOT MATCHED rules to occur before MATCHED rules, when we have at
least one unique index.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Apr 25, 2008, at 3:28 AM, Simon Riggs wrote:
I recently came across the expression "YAGNI", and think it's
probably
pretty relevant to this discussion:
http://en.wikipedia.org/wiki/You_Ain't_Gonna_Need_ItIn matters of technical implementation, I follow you almost without
question, and very happily so.I think all of us should be careful when expressing views on what
other
people need or don't need. We sleep soundly after having given such an
opinion, but that doesn't make those opinions valid. I'm not sure if
there is a pithy acronym for that thought.
Agreed. Many people on hackers don't actually deal with production
systems, so it's easy to look at things from an academic standpoint
and not a practical one. This is generally a Good Thing (I think
MySQL is an example of what happens when you don't do that), but it
does need to be balanced by real-world needs. And not all of those
needs are always well represented on the lists.
In this case, I have bulk-load code that could certainly use MERGE.
It's not that hard to write code that will handle this in a way
that's not safe from race conditions, so it's unlikely that we'll see
that many requests, but that doesn't mean a fast MERGE wouldn't be
useful. It certainly would have saved me some effort, and it would
probably out-perform the current code.
--
Decibel!, aka Jim C. Nasby, Database Architect decibel@decibel.org
Give your computer some brain candy! www.distributed.net Team #1828
Attachments:
On Fri, 2008-04-25 at 10:03 +0300, Hannu Krosing wrote:
On Tue, 2008-04-22 at 00:24 +0100, Simon Riggs wrote:
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?Yes, that's right. Just like COPY. That seems fine to me because you're
likely to be doing a MERGE immediately after a COPY anyway, so the
restriction just continues.May be the bulk data merging variant of MERGE to be used after initial
COPY should be a variant of COPY with special keyword(s) instead of
MERGE ?
That does sound like a good way of differentiating between the OLTP and
bulk loading cases.
I'll bear that in mind as we develop.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
Robert Treat wrote:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you keep
the spec behavior for the spec syntax, and have a clearly non-spec command
for non-spec behavior.
MySQL's "REPLACE INTO" is *NOT* semantically equivalent to any flavor of
"insert or update". It is "delete plus insert". They do have "INSERT ...
ON DUPLICATE KEY UPDATE ..."
Presumably, if we implement MERGE with transaction-safe semantics, which
Simon has agreed to do, we would not need to consider anything like the
latter, but we might still want to consider REPLACE INTO (in the MySQL
sense).
cheers
andrew
Simon Riggs wrote:
I'm now happy that we can get a spec-compliant end result by always
forcing NOT MATCHED rules to occur before MATCHED rules, when we have at
least one unique index.
... and raise an ERROR when there is no unique index?
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On Thursday 24 April 2008 23:40, Tom Lane wrote:
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you
keep the spec behavior for the spec syntax, and have a clearly non-spec
command for non-spec behavior.In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)
AIUI the current implementation is designed to avoid race conditions partially
at the cost of being insert friendly and somewhat update unfriendly. My guess
is that most of the people wanting this for OLTP use want an update friendly
implementation (that's certainly been the majority of cases I've needed
myself, and that I have seen others use).
--
Robert Treat
Build A Brighter LAMP :: Linux Apache {middleware} PostgreSQL
Simon Riggs wrote:
On Fri, 2008-04-25 at 10:03 +0300, Hannu Krosing wrote:
On Tue, 2008-04-22 at 00:24 +0100, Simon Riggs wrote:
On Mon, 2008-04-21 at 16:38 -0400, A.M. wrote:
"MERGE will not invoke Rules." Does this imply that MERGE cannot be
used on views or that the resulting INSERTs or UPDATEs do not work on
views?Yes, that's right. Just like COPY. That seems fine to me because you're
likely to be doing a MERGE immediately after a COPY anyway, so the
restriction just continues.May be the bulk data merging variant of MERGE to be used after initial
COPY should be a variant of COPY with special keyword(s) instead of
MERGE ?That does sound like a good way of differentiating between the OLTP and
bulk loading cases.I'll bear that in mind as we develop.
To me, a simple user, it'd be important that MERGE implementation does
not place any unpredictable restrictions. For example in Oracle you can
break any MERGE statement by placing a full text index on the table. So
I'd really expect a MERGE in PostgreSQL to be fine with views, rules,
tsearch, etc.
Just my 2 cent.
--
Best regards,
Hannes Dorbath
On Fri, 2008-04-25 at 09:10 -0400, Alvaro Herrera wrote:
Simon Riggs wrote:
I'm now happy that we can get a spec-compliant end result by always
forcing NOT MATCHED rules to occur before MATCHED rules, when we have at
least one unique index.... and raise an ERROR when there is no unique index?
No, I think an ERROR is not required, nor desirable.
In the absence of a unique index we allow exactly duplicate rows to
exist in a table. This is effectively user defined behaviour, albeit the
default setting.
We have two choices of behaviour:
1. If a MERGE statement runs and sees a row in the target table is NOT
MATCHED then it will insert a row. It is possible that a concurrent
MERGE statement could also see the row in the target table as NOT
MATCHED and then insert a duplicate row.
2. In the absence of a Unique Index, throw an ERROR because a concurrent
MERGE *might* result in duplicate Inserts. (i.e. prevent the above).
(1) is a situation possible with concurrent INSERTs into a table without
a unique index, so I see no reason to make MERGE follow (2) when INSERTs
do not.
Also, it is possible for a MERGE to generate duplicate rows in a table
if the INSERT clause contained constants for example. In the absence of
an applicable rule the MERGE will generate INSERT DEFAULT VALUES, i.e.
an all-constant insert will take place. So the MERGE spec allows the
inserting of duplicate rows without error.
We could include additional options to control this behaviour, if anyone
thinks it worthwhile, but ISTM more restrictive than protective.
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On 4/25/08, Robert Treat <xzilla@users.sourceforge.net> wrote:
On Thursday 24 April 2008 23:40, Tom Lane wrote:
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you
keep the spec behavior for the spec syntax, and have a clearly non-spec
command for non-spec behavior.In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)AIUI the current implementation is designed to avoid race conditions partially
at the cost of being insert friendly and somewhat update unfriendly. My guess
is that most of the people wanting this for OLTP use want an update friendly
implementation (that's certainly been the majority of cases I've needed
myself, and that I have seen others use).
This seems to hint that there should be 2 variants of merge/upsert
- insert-friendly and update-friendly... It seems unlikely one implementation
can be both. And especially bad would be implementation that is neither.
--
marko
On Mon, 2008-04-28 at 11:57 +0300, Marko Kreen wrote:
On 4/25/08, Robert Treat <xzilla@users.sourceforge.net> wrote:
On Thursday 24 April 2008 23:40, Tom Lane wrote:
Robert Treat <xzilla@users.sourceforge.net> writes:
Perhaps a better option would be to implement Merge per spec, and then
implement a "replace into" command for the oltp scenario. This way you
keep the spec behavior for the spec syntax, and have a clearly non-spec
command for non-spec behavior.In that case, it's a fair question to ask just who will use the "spec"
syntax. As far as I can tell from years of watching the mailing lists,
there is plenty of demand for a concurrent-safe insert-or-update
behavior, and *exactly zero* demand for the other. I challenge you to
find even one request for the "spec" behavior in the mailing list
archives. (Simon doesn't count.)AIUI the current implementation is designed to avoid race conditions partially
at the cost of being insert friendly and somewhat update unfriendly. My guess
is that most of the people wanting this for OLTP use want an update friendly
implementation (that's certainly been the majority of cases I've needed
myself, and that I have seen others use).This seems to hint that there should be 2 variants of merge/upsert
- insert-friendly and update-friendly... It seems unlikely one implementation
can be both. And especially bad would be implementation that is neither.
Not sure what an option that was "neither" would look like ...
I would summarise the two MERGE behaviour proposals as
1. Correctly protects against concurrent inserts. Uses one
sub-transaction per row and leaves 2 dead rows per update. Requires us
to perform tasks in different order than required by SQL spec, but the
end result seems identical to me (now).
Has been noted as suitable for OLTP, and poor for bulk data maintenance.
Has been described as "insert-friendly" and "non-spec".
2. Does not protect against concurrent inserts. Leaves 1 dead row per
update. Much more efficient for updates, not sure about any efficiency
gain for inserts.
Has been noted as being unsuitable for OLTP, though likely to offer more
acceptable performance for bulk operations.
Has been described as "update-friendly".
By consensus, I'm doing (1).
It looks likely that doing (2) should be fairly small change and so can
be offered as an option. For example, we can have an additional
action-order clause with two options (the first of which is default)
[INSERT BEFORE UPDATE ACTION ORDER | DEFAULT ACTION ORDER]
So the default is to force inserts to occur before updates, as required
by (1). The other option "DEFAULT ACTION ORDER" tests the WHEN clauses
in the order specified in the statement, allowing the user to choose
whether they want to test for updates or inserts first.
Overall, the difference between these behaviours is small in comparison
with making MERGE work in the first place...
--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com
On Mon, 2008-04-21 at 21:08 +0100, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement and
how it will work in the world of PostgreSQL.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.
Enclose merge.sgml docs for forthcoming MERGE command, as originally
written.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
On 05/08/10 10:46, Simon Riggs wrote:
On Mon, 2008-04-21 at 21:08 +0100, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement and
how it will work in the world of PostgreSQL.The HTML file was generated from SGML source, though the latter is not
included here for clarity.Enclose merge.sgml docs for forthcoming MERGE command, as originally
written.
Oh, cool, I wasn't aware you had written that already. Boxuan, please
include this in your patch, after reviewing and removing/editing
anything that doesn't apply to your patch.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Thu, 2010-08-05 at 12:29 +0300, Heikki Linnakangas wrote:
On 05/08/10 10:46, Simon Riggs wrote:
On Mon, 2008-04-21 at 21:08 +0100, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement and
how it will work in the world of PostgreSQL.The HTML file was generated from SGML source, though the latter is not
included here for clarity.Enclose merge.sgml docs for forthcoming MERGE command, as originally
written.Oh, cool, I wasn't aware you had written that already. Boxuan, please
include this in your patch, after reviewing and removing/editing
anything that doesn't apply to your patch.
Also had these fragments as well, if they're still useful. Probably just
useful as pointers as to what else to change to include the docs.
The tests and docs were written from SQL standard, so any deviations
would need to be flagged. The idea of writing the tests first was that
they provide an objective test of whether the implementation works
according to spec.
I'd quite like a commentary on anything that needs changing. Not saying
I will necessarily object to differences, but knowing the differences
sounds important for us.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
merge_doc_ref.patchtext/x-patch; charset=UTF-8; name=merge_doc_ref.patchDownload
Index: doc/src/sgml/reference.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/reference.sgml,v
retrieving revision 1.66
diff -c -r1.66 reference.sgml
*** doc/src/sgml/reference.sgml 27 Mar 2008 17:24:16 -0000 1.66
--- doc/src/sgml/reference.sgml 18 Apr 2008 17:50:31 -0000
***************
*** 135,140 ****
--- 135,141 ----
&listen;
&load;
&lock;
+ &merge;
&move;
¬ify;
&prepare;
Index: doc/src/sgml/ref/allfiles.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/ref/allfiles.sgml,v
retrieving revision 1.73
diff -c -r1.73 allfiles.sgml
*** doc/src/sgml/ref/allfiles.sgml 27 Mar 2008 17:24:16 -0000 1.73
--- doc/src/sgml/ref/allfiles.sgml 18 Apr 2008 11:10:16 -0000
***************
*** 107,112 ****
--- 107,113 ----
<!entity listen system "listen.sgml">
<!entity load system "load.sgml">
<!entity lock system "lock.sgml">
+ <!entity merge system "merge.sgml">
<!entity move system "move.sgml">
<!entity notify system "notify.sgml">
<!entity prepare system "prepare.sgml">
Index: doc/src/sgml/ref/update.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/ref/update.sgml,v
retrieving revision 1.46
diff -c -r1.46 update.sgml
*** doc/src/sgml/ref/update.sgml 15 Feb 2008 22:17:06 -0000 1.46
--- doc/src/sgml/ref/update.sgml 21 Apr 2008 19:01:36 -0000
***************
*** 322,327 ****
--- 322,330 ----
-- continue with other operations, and eventually
COMMIT;
</programlisting>
+
+ This operation can be executed in a single statement using
+ <xref linkend="sql-merge" endterm="sql-merge-title">.
</para>
<para>
On Thu, Aug 5, 2010 at 7:25 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Thu, 2010-08-05 at 12:29 +0300, Heikki Linnakangas wrote:
On 05/08/10 10:46, Simon Riggs wrote:
On Mon, 2008-04-21 at 21:08 +0100, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement
and
how it will work in the world of PostgreSQL.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.Enclose merge.sgml docs for forthcoming MERGE command, as originally
written.Oh, cool, I wasn't aware you had written that already. Boxuan, please
include this in your patch, after reviewing and removing/editing
anything that doesn't apply to your patch.
Thanks a lot for the instruction file of MERGE command. I have read through
it carefully. It is really a great work. I have to admit that I am not
familiar with the sgml language, and I cannot write the instruction by
myself.
All features of MERGE demonstrated in this file are consistent with my
implementation, EXCEPT the DO NOTHING option. In current edition, we don't
have the DO NOTHING action type. That is, during the execution of MERGE
commands, if one tuple is not caught by any of the merge actions, it will be
ignored. In another word, DO NOTING (although cannot be specified explicitly
by user) is the DEFAULT action for tuples.
In the contrary, Simon's instruction says that the DEFAULT action for the
tuple caught by no actions is
WHEN NOT MATCHED THEN INSERT DEFAULT VALUES
From the user's point of view, these two kinds of MERGE command may have
not much differences. But, as the coder, I prefer current setting, because
we can save the implementation for a new type of MERGE actions (DO
NOTHING is a special merge action type). And, thus, no checks and special
process for it. (For example, we need to make sure that DO NOTHING is the
last WHEN clause, and it has no additional qual. And we have to generate a
INSERT DEFAULT VALUES action for the MERGE command if we don't find the DO
NOTHING action)
Well, if people want the DO NOTHING action, I will add it in the system.
Now, I have changed the RULE strategy of MERGE to the better logic. And I
am working on triggers for MERGE, which is also mentioned in the instruction
file. I will build a new patch with no long comment and blank line around
functions, and possibly contain the regress test file and this sgml
instructions in it.
I wish we can reach a agreement on the DO NOTHING thing before my next
submission, so I can make necessary modification on my code for it. (the new
patch may be finished in one or two days, I think)
Thanks!
PS: I have an embarrassing question: how to view the sgml instructions of
postgres in web page form, rather than read the source code of them?
Show quoted text
Also had these fragments as well, if they're still useful. Probably just
useful as pointers as to what else to change to include the docs.The tests and docs were written from SQL standard, so any deviations
would need to be flagged. The idea of writing the tests first was that
they provide an objective test of whether the implementation works
according to spec.I'd quite like a commentary on anything that needs changing. Not saying
I will necessarily object to differences, but knowing the differences
sounds important for us.--
Simon Riggs www.2ndQuadrant.com <http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training and Services
On Thu, Aug 05, 2010 at 09:55:29PM +0800, Boxuan Zhai wrote:
On Thu, Aug 5, 2010 at 7:25 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Thu, 2010-08-05 at 12:29 +0300, Heikki Linnakangas wrote:
On 05/08/10 10:46, Simon Riggs wrote:
On Mon, 2008-04-21 at 21:08 +0100, Simon Riggs wrote:
The following two files specify the behaviour of the MERGE statement
and
how it will work in the world of PostgreSQL.
The HTML file was generated from SGML source, though the latter is not
included here for clarity.Enclose merge.sgml docs for forthcoming MERGE command, as originally
written.Oh, cool, I wasn't aware you had written that already. Boxuan, please
include this in your patch, after reviewing and removing/editing
anything that doesn't apply to your patch.Thanks a lot for the instruction file of MERGE command. I have read through
it carefully. It is really a great work. I have to admit that I am not
familiar with the sgml language, and I cannot write the instruction by
myself.
It's really not super complicated. It's quite like (and ancestral to)
HTML.
All features of MERGE demonstrated in this file are consistent with my
implementation, EXCEPT the DO NOTHING option. In current edition, we don't
have the DO NOTHING action type. That is, during the execution of MERGE
commands, if one tuple is not caught by any of the merge actions, it will be
ignored. In another word, DO NOTING (although cannot be specified explicitly
by user) is the DEFAULT action for tuples.In the contrary, Simon's instruction says that the DEFAULT action for the
tuple caught by no actions is
WHEN NOT MATCHED THEN INSERT DEFAULT VALUES
I believe that the SQL standard specifies this behavior, and I don't
think we have a compelling reason to do something different from what
the SQL standard specifies.
Well, if people want the DO NOTHING action, I will add it in the system.
That'd be great :)
Now, I have changed the RULE strategy of MERGE to the better logic. And I
am working on triggers for MERGE, which is also mentioned in the instruction
file. I will build a new patch with no long comment and blank line around
functions, and possibly contain the regress test file and this sgml
instructions in it.I wish we can reach a agreement on the DO NOTHING thing before my next
submission, so I can make necessary modification on my code for it. (the new
patch may be finished in one or two days, I think)Thanks!
PS: I have an embarrassing question: how to view the sgml instructions of
postgres in web page form, rather than read the source code of them?
After you've built postgresql, do this:
cd doc/src/sgml
make
Then you can point a web browser at the doc/src/sgml/html/index.html
(and similar)
http://www.postgresql.org/docs/current/static/docguide.html
has information about the tools you will need for the above to work.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
On Thu, 2010-08-05 at 21:55 +0800, Boxuan Zhai wrote:
In the contrary, Simon's instruction says that the DEFAULT action for
the tuple caught by no actions is
WHEN NOT MATCHED THEN INSERT DEFAULT VALUESFrom the user's point of view, these two kinds of MERGE command may
have not much differences. But, as the coder, I prefer current
setting, because we can save the implementation for a new type
of MERGE actions (DO NOTHING is a special merge action type). And,
thus, no checks and special process for it. (For example, we need to
make sure that DO NOTHING is the last WHEN clause, and it has no
additional qual. And we have to generate a INSERT DEFAULT VALUES
action for the MERGE command if we don't find the DO NOTHING action)Well, if people want the DO NOTHING action, I will add it in the
system.
This is only important when using AND <search condition>, so its not
important for the common UPSERT case of unconditional UPDATE/INSERT.
Personally, I would prefer the default action to be RAISE ERROR or
similar. Otherwise its just too easy to get complex logic wrong and lose
a few rows without noticing. If that was the case then you would
definitely need DO NOTHING when you explicitly wanted to lose a few
rows.
You may think that's a bit strong, but consider that PostgreSQL uses
default => ERROR in vast majority of switch() statements. I think its a
safe coding practice and the annoyance of having run-time errors is much
better than losing rows.
The INSERT DEFAULT VALUES was behaviour taken from another DBMS, its not
part of the standard AFAICS.
Now, I have changed the RULE strategy of MERGE to the better logic.
And I am working on triggers for MERGE, which is also mentioned in the
instruction file. I will build a new patch with no long comment and
blank line around functions, and possibly contain the regress test
file and this sgml instructions in it.I wish we can reach a agreement on the DO NOTHING thing before my next
submission, so I can make necessary modification on my code for
it. (the new patch may be finished in one or two days, I think)Thanks!
PS: I have an embarrassing question: how to view the sgml instructions
of postgres in web page form, rather than read the source code of
them?
If you edit the files, as shown in the patches here, then you just need
to drop into the doc/sgml/src directory and type "make". The SGML will
then be compiled into HTML and you can view the resulting file directly
in your web browser.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On 05/08/10 17:22, Simon Riggs wrote:
On Thu, 2010-08-05 at 21:55 +0800, Boxuan Zhai wrote:
In the contrary, Simon's instruction says that the DEFAULT action for
the tuple caught by no actions is
WHEN NOT MATCHED THEN INSERT DEFAULT VALUESFrom the user's point of view, these two kinds of MERGE command may
have not much differences. But, as the coder, I prefer current
setting, because we can save the implementation for a new type
of MERGE actions (DO NOTHING is a special merge action type). And,
thus, no checks and special process for it. (For example, we need to
make sure that DO NOTHING is the last WHEN clause, and it has no
additional qual. And we have to generate a INSERT DEFAULT VALUES
action for the MERGE command if we don't find the DO NOTHING action)Well, if people want the DO NOTHING action, I will add it in the
system.This is only important when using AND<search condition>, so its not
important for the common UPSERT case of unconditional UPDATE/INSERT.
Assuming the default action if no other action matches is to do nothing,
then an explicit DO NOTHING is just a convenience. You can have the same
effect by having an "AND NOT <condition>" to all the actions following
the DO NOTHING action. I admit it's quite handy, but let's avoid
PostgreSQL extensions at this point.
Personally, I would prefer the default action to be RAISE ERROR or
similar. Otherwise its just too easy to get complex logic wrong and lose
a few rows without noticing. If that was the case then you would
definitely need DO NOTHING when you explicitly wanted to lose a few
rows.You may think that's a bit strong, but consider that PostgreSQL uses
default => ERROR in vast majority of switch() statements. I think its a
safe coding practice and the annoyance of having run-time errors is much
better than losing rows.The INSERT DEFAULT VALUES was behaviour taken from another DBMS, its not
part of the standard AFAICS.
What does the standard say about this? We should follow the standard, I
don't see enough reason to deviate here.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Thu, 2010-08-05 at 18:17 +0300, Heikki Linnakangas wrote:
On 05/08/10 17:22, Simon Riggs wrote:
On Thu, 2010-08-05 at 21:55 +0800, Boxuan Zhai wrote:
In the contrary, Simon's instruction says that the DEFAULT action for
the tuple caught by no actions is
WHEN NOT MATCHED THEN INSERT DEFAULT VALUESFrom the user's point of view, these two kinds of MERGE command may
have not much differences. But, as the coder, I prefer current
setting, because we can save the implementation for a new type
of MERGE actions (DO NOTHING is a special merge action type). And,
thus, no checks and special process for it. (For example, we need to
make sure that DO NOTHING is the last WHEN clause, and it has no
additional qual. And we have to generate a INSERT DEFAULT VALUES
action for the MERGE command if we don't find the DO NOTHING action)Well, if people want the DO NOTHING action, I will add it in the
system.This is only important when using AND<search condition>, so its not
important for the common UPSERT case of unconditional UPDATE/INSERT.Assuming the default action if no other action matches is to do nothing,
then an explicit DO NOTHING is just a convenience. You can have the same
effect by having an "AND NOT <condition>" to all the actions following
the DO NOTHING action. I admit it's quite handy, but let's avoid
PostgreSQL extensions at this point.
err...
* DELETE is an extension to the standard, though supported by Oracle,
DB2 and SQLServer and damn useful
* INSERT DEFAULT VALUES is an extension to the standard, though matches
options on the normal INSERT clause
* rule support is an extension to the standard
* It appears we would be in violation of the standard on
14.12 General Rule 6 a) i) 2) B), p.890
(Oh, I wish I was joking, there really is such a paragraph number)
which specifies that the join between source and target table must not
return multiple rows or must return "cardinality violation". That's
pretty difficult thing to check and not very useful if it does do that.
anyway, that list isn't an argument in favour of change. The argument in
favour of a fail-safe default is that it is a safe coding practice that
the PostgreSQL project already uses itself. The only way to write a safe
MERGE SQL statement is with an extension to the standard...
Principle of minimal extension would mean we only need to support RAISE
ERROR, to allow people to specify they actively want statement to fail
if the list of WHEN clauses does not produce a match.
Personally, I would prefer the default action to be RAISE ERROR or
similar. Otherwise its just too easy to get complex logic wrong and lose
a few rows without noticing. If that was the case then you would
definitely need DO NOTHING when you explicitly wanted to lose a few
rows.You may think that's a bit strong, but consider that PostgreSQL uses
default => ERROR in vast majority of switch() statements. I think its a
safe coding practice and the annoyance of having run-time errors is much
better than losing rows.The INSERT DEFAULT VALUES was behaviour taken from another DBMS, its not
part of the standard AFAICS.What does the standard say about this? We should follow the standard, I
don't see enough reason to deviate here.
I checked the standard before commenting previously and have done so
again here. I can't see anything that refers to this (in SQL:2008),
either way.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On Thu, Aug 5, 2010 at 11:35 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
* It appears we would be in violation of the standard on
14.12 General Rule 6 a) i) 2) B), p.890
(Oh, I wish I was joking, there really is such a paragraph number)
Just shoot me.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
On Thu, Aug 5, 2010 at 7:25 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
Also had these fragments as well, if they're still useful. Probably just
useful as pointers as to what else to change to include the docs.The tests and docs were written from SQL standard, so any deviations
would need to be flagged. The idea of writing the tests first was that
they provide an objective test of whether the implementation works
according to spec.I'd quite like a commentary on anything that needs changing. Not saying
I will necessarily object to differences, but knowing the differences
sounds important for us.
I think this is a wonderful feature. A couple of thoughts:
*) Would however very much like to see RETURNING support if it's not
there. Our other DML statements support it, and this one should too.
wCTE if/when we get it will make the lack of it especially glaring.
(OTOH, no issue if there is no rule support...they should be
deprecated)
*) The decision to stay on the standard and not do a 'race free'
version was IMO a good one. I am starting to come around to the point
of view that the *only* safe way to guarantee race free merge with the
current locking model is to take an appropriate table lock. BTW, our
pl/pgsql upsert example we've been encouraging people to use has a
horrible bug (see:
http://postgresql.1045698.n5.nabble.com/Danger-of-idiomatic-plpgsql-loop-for-merging-data-td2257700.html).
If we want to rework the locking model to support anticipatory locks
then fine (but that has nothing to do with MERGE specifically).
merlin
Dear All,
I have seen a lively discussion about the DO NOTING action in MERGE command.
And, I think most people want it. So it will be added to my next patch.
Before the implementation, I still have some questions to confirm:
1. If we have a DO NOTHING action specified, it should be the last WHEN
clause. It must be of the NOT MATCHED cases, and it CANNOT have any
additional action qualifications. Am I correct?
2. If no DO NOTHING specified, we will imply a INSERT DEFAULT VALUES action
as the end of MERGE.
My question is, is this action taken only for the NOT MATCHED tuples? If
this is the case, then what about the MATCHED tuples that match not previous
actions? Ignore them?
That means we are in fact going to add two implicit WHEN clause:
a) WHEN NOT MATCHED INSERT default values;
b) WHEN MATCHED THEN DO NOTHING.
OR, is the INSERT DEFAULT VALUES applied to ALL tuples not matter they are
MATCHED or not?
Besides, (I mean no offense, but) can this method really avoid losing row?
So far as I know, the DEFAULT values for table attributes are defined when
the table is created and no variables are allowed in the default value
expressions. That means, they are usually constants or simple serial
numbers.
Image that we have a MERGE command that has thousands of NOT MATCHED tuples
going to the implicit action. Then, the target table will inserted with
thousands of rows with DEAULT VALUES. These row will have similar (if not
exactly the same) simple content, which contains NO information from the
source table of MERGE. Is this really what we want? If it is not, then what
is the use of the INSERT DEFAULT VALUES action?
Regards
On 06/08/10 04:39, Boxuan Zhai wrote:
I have seen a lively discussion about the DO NOTING action in MERGE command.
And, I think most people want it. So it will be added to my next patch.Before the implementation, I still have some questions to confirm:
1. If we have a DO NOTHING action specified, it should be the last WHEN
clause. It must be of the NOT MATCHED cases, and it CANNOT have any
additional action qualifications. Am I correct?
It would be useful to specify it in WHEN MATCHED sometimes, and not
necessarily the last. For example:
MERGE INTO Stock S
USING DailySales DS ON S.Item = DS.Item
WHEN MATCHED AND (QtyOnHand ‐ QtySold = 0) THEN DELETE
WHEN MATCHED THEN UPDATE SET QtyOnHand = QtyOnHand ‐ QtySold
-- Don't add new inactive items to stock if not there already
WHEN MATCHED AND (itemtype = 'inactive') THEN DO NOTHING
WHEN NOT MATCHED THEN INSERT VALUES (Item, QtySold);
It shouldn't be difficult to support DO NOTHING in all cases, right?
2. If no DO NOTHING specified, we will imply a INSERT DEFAULT VALUES action
as the end of MERGE.
My question is, is this action taken only for the NOT MATCHED tuples? If
this is the case, then what about the MATCHED tuples that match not previous
actions? Ignore them?
That means we are in fact going to add two implicit WHEN clause:
a) WHEN NOT MATCHED INSERT default values;
b) WHEN MATCHED THEN DO NOTHING.
OR, is the INSERT DEFAULT VALUES applied to ALL tuples not matter they are
MATCHED or not?
We'll need to figure out what the SQL standard says about this. I tried
reading the spec but couldn't readily understand what the default action
should be. Does someone else know that? What do other DBMSs do?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, 2010-08-06 at 09:39 +0800, Boxuan Zhai wrote:
Besides, (I mean no offense, but) can this method really avoid losing
row?
Not as you just specified, no.
You need *both* actions of RAISE ERROR and DO NOTHING, or you may as
well have neither.
(1) Natural style allows missing rows if you are not careful - and also
allows missing rows in future when COL is allowed to take value 'C',
which may not have been originally considered when SQL first written
WHEN NOT MATCHED AND COL = 'A'
INSERT...
WHEN NOT MATCHED AND COL = 'B'
INSERT...
(2) Shows code style required to explicitly avoid missing rows
WHEN NOT MATCHED AND COL = 'A'
INSERT...
WHEN NOT MATCHED AND COL = 'B'
INSERT...
WHEN NOT MATCHED
RAISE ERROR
(3) More complex example, with explicit DO NOTHING, showing how it can
provide well structured code
WHEN NOT MATCHED AND COL = 'A'
DO NOTHING
WHEN NOT MATCHED AND COL = 'B'
INSERT...
WHEN NOT MATCHED
RAISE ERROR
So DO NOTHING is the default and implies silently ignoring rows. RAISE
ERROR is the opposite.
Coding for those seems very easy, its just a question of "should we do
it?". DB2 has it; SQL:2008 does not. But then SQL:2008 followed the DB2
introduction of AND clauses, and SQL:2011 has so far followed the DB2
introduction of DELETE action also.
Given that Peter is now attending SQL Standards meetings, I would
suggest we leave out my suggestion above, for now. We have time to raise
this at standards meetings and influence the outcome and then follow
later.
There is a workaround:
WHEN NOT MATCHED AND COL = 'A'
DO NOTHING
WHEN NOT MATCHED AND COL = 'B'
INSERT...
WHEN NOT MATCHED AND TRUE
INSERT INTO ERROR_TABLE (errortext);
where ERROR_TABLE has an INSERT trigger which throws an ERROR with given
text.
SQL:2011 makes no mention of how MERGE should react to statement level
triggers. MERGE is not a trigger action even. Given considerable
confusion in this area, IMHO we should just say the MERGE does not call
statement triggers at all, of any kind.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On 06/08/10 10:12, Simon Riggs wrote:
So DO NOTHING is the default and implies silently ignoring rows. RAISE
ERROR is the opposite.Coding for those seems very easy, its just a question of "should we do
it?". DB2 has it; SQL:2008 does not. But then SQL:2008 followed the DB2
introduction of AND clauses, and SQL:2011 has so far followed the DB2
introduction of DELETE action also.
I see neither DO NOTHING or RAISE ERROR in the documentation of DB2,
Oracle, or MSSQL server.
Given that Peter is now attending SQL Standards meetings, I would
suggest we leave out my suggestion above, for now. We have time to raise
this at standards meetings and influence the outcome and then follow
later.
Ok, fair enough.
SQL:2011 makes no mention of how MERGE should react to statement level
triggers. MERGE is not a trigger action even. Given considerable
confusion in this area, IMHO we should just say the MERGE does not call
statement triggers at all, of any kind.
IMO the UPDATE/DELETE/INSERT actions should fire the respective
statement level triggers, but the MERGE itself should not.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, 2010-08-06 at 10:28 +0300, Heikki Linnakangas wrote:
SQL:2011 makes no mention of how MERGE should react to statement level
triggers. MERGE is not a trigger action even. Given considerable
confusion in this area, IMHO we should just say the MERGE does not call
statement triggers at all, of any kind.IMO the UPDATE/DELETE/INSERT actions should fire the respective
statement level triggers, but the MERGE itself should not.
When, and how?
If an UPDATE is mentioned 5 times, do we call the trigger 5 times? What
happens if none of the UPDATEs are ever executed?
Best explain exactly what you mean.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, 2010-08-06 at 10:28 +0300, Heikki Linnakangas wrote:
On 06/08/10 10:12, Simon Riggs wrote:
So DO NOTHING is the default and implies silently ignoring rows. RAISE
ERROR is the opposite.Coding for those seems very easy, its just a question of "should we do
it?". DB2 has it; SQL:2008 does not. But then SQL:2008 followed the DB2
introduction of AND clauses, and SQL:2011 has so far followed the DB2
introduction of DELETE action also.I see neither DO NOTHING or RAISE ERROR in the documentation of DB2,
Oracle, or MSSQL server.
Agreed, Oracle and MSSQL server does not have these.
However, DB2 very clearly does have these features
* SIGNAL which raises an error and can be used in place of any action,
at any point in sequence of WHEN clauses. DB2 already supports SIGNAL as
part of SQL/PSM, which we do not, so RAISE ERROR was the nearest
equivalent command for PostgreSQL.
* ELSE IGNORE which does same thing as DO NOTHING, except it must always
be last statement in a sequence of WHEN clauses. DO NOTHING is already a
phrase with exactly this meaning in PostgreSQL, so I suggest that.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, Aug 6, 2010 at 3:41 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2010-08-06 at 10:28 +0300, Heikki Linnakangas wrote:
SQL:2011 makes no mention of how MERGE should react to statement level
triggers. MERGE is not a trigger action even. Given considerable
confusion in this area, IMHO we should just say the MERGE does not call
statement triggers at all, of any kind.IMO the UPDATE/DELETE/INSERT actions should fire the respective
statement level triggers, but the MERGE itself should not.When, and how?
If an UPDATE is mentioned 5 times, do we call the trigger 5 times?
My current process for BEFOR / AFTER STATEMENT trigger on MERGE is to fire
the triggers for all action types that appears in the command, unless it is
replaced by a INSTEAD rule. But the triggers for one action type will be
fired only once. That means you will get both UPDATE and INSERT triggers be
activated for only once if you are executing a MERGE command with 5 UPDATEs
and 10 INSERTs.
What happens if none of the UPDATEs are ever executed?
The triggers (I mean triggers for statement) will be fired anyway even the
UPDATE action matches no tuple. This is not for MERGE only. If you update a
table with the command
UPDATE foo SET ... WHERE false;
It will also fire the STATEMENT triggers of UPDATE type on foo (I think so).
And, even not been asked, I want to say that, in current implementation of
MERGE, the row level triggers are fired by the actions that take the
tuples. If one tuple is caught by an UPDATE action, then the UPDATE row
trigger will be fired on this tuple. If it is handled by INSERT action, then
the INSRET row triggers are on.
Hope you agree with my designs.
Show quoted text
Best explain exactly what you mean.
--
Simon Riggs www.2ndQuadrant.com <http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, Aug 6, 2010 at 3:53 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2010-08-06 at 10:28 +0300, Heikki Linnakangas wrote:
On 06/08/10 10:12, Simon Riggs wrote:
So DO NOTHING is the default and implies silently ignoring rows. RAISE
ERROR is the opposite.Coding for those seems very easy, its just a question of "should we do
it?". DB2 has it; SQL:2008 does not. But then SQL:2008 followed the DB2
introduction of AND clauses, and SQL:2011 has so far followed the DB2
introduction of DELETE action also.I see neither DO NOTHING or RAISE ERROR in the documentation of DB2,
Oracle, or MSSQL server.Agreed, Oracle and MSSQL server does not have these.
However, DB2 very clearly does have these features
* SIGNAL which raises an error and can be used in place of any action,
at any point in sequence of WHEN clauses. DB2 already supports SIGNAL as
part of SQL/PSM, which we do not, so RAISE ERROR was the nearest
equivalent command for PostgreSQL.* ELSE IGNORE which does same thing as DO NOTHING, except it must always
be last statement in a sequence of WHEN clauses. DO NOTHING is already a
phrase with exactly this meaning in PostgreSQL, so I suggest that.
So, we need to add both DO NOTHING and RAISE ERROR actions in the MERGE
command now !? What will RAISE ERROR do? To stop the whole MERGE command OR,
just throw an error notice for the row and move on.
Show quoted text
--
Simon Riggs www.2ndQuadrant.com <http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, 2010-08-06 at 16:26 +0800, Boxuan Zhai wrote:
So, we need to add both DO NOTHING and RAISE ERROR actions in the
MERGE command now !? What will RAISE ERROR do?
Let's get the rest of it working first. This would be a later extension,
though I think an important one for our developers.
To stop the whole MERGE command OR, just throw an error notice for the
row and move on.
As you say, it would be even better to be able to report errors in some
way and move onto next row. NOTICE is not the way though.
Maybe one of the actions would be to EXECUTE a procedure, so we can call
an error logging function.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On tor, 2010-08-05 at 16:35 +0100, Simon Riggs wrote:
* DELETE is an extension to the standard, though supported by Oracle,
DB2 and SQLServer and damn useful
-> SQL:2011
Dear All,
I have just finished a new patch, with the following feature:
1. The rule rewriter is changed to a better logic, which is the actions
replaced by INSTEAD rules will still catch tuples, but do nothing for them.
2. Triggers can work on MERGE. We don't have CREATE TRIGGER ON MERGE...
command. But the triggers (for each statement and for each row) of UPDATE,
DELETE and INSERT will be activated by the actions in MERGE.
3. We have DO NOTHING and RAISE ERROR actions now. They can used in both
MATCHED and NOT MATCHED situation, and can have additional quals. Currently
RAISE ERROR just elog a NOTICE message, since we are stil not sure what
should be done for these errors. I just build up the framework for
it, preparing for the further extension.
4. The default action of MERGE is RAISE ERROR.
I explain the usage of the new features in my pages
https://wiki.postgresql.org/wiki/MergeTestExamples
Please find the patch file in the attachment.
Thanks
Boxuan
Attachments:
merge_do_nothing.patchtext/plain; charset=US-ASCII; name=merge_do_nothing.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..1a9e39a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -73,6 +73,8 @@ static void show_sort_keys(SortState *sortstate, List *ancestors,
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
+static void ExplainMergeActions(ModifyTableState *mt_planstate,
+ List *ancestors, ExplainState *es);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
@@ -636,6 +638,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case CMD_DELETE:
pname = operation = "Delete";
break;
+ case CMD_MERGE:
+ pname = operation = "Merge";
+ break;
default:
pname = "???";
break;
@@ -1190,6 +1195,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
switch (nodeTag(plan))
{
case T_ModifyTable:
+ ExplainMergeActions((ModifyTableState *)planstate, ancestors, es);
+
ExplainMemberNodes(((ModifyTable *) plan)->plans,
((ModifyTableState *) planstate)->mt_plans,
ancestors, es);
@@ -1482,6 +1489,64 @@ explain_get_index_name(Oid indexId)
return result;
}
+static void
+ExplainMergeActions(ModifyTableState *mt_planstate, List *ancestors, ExplainState *es)
+{
+ ListCell *l;
+ StringInfo buf = makeStringInfo();
+
+ if(mt_planstate->operation != CMD_MERGE || mt_planstate->mergeActPstates == NIL)
+ return;
+
+ foreach(l,mt_planstate->mergeActPstates)
+ {
+ ModifyTableState *mt_state = (ModifyTableState *)lfirst(l);
+
+ MergeActionState *act_pstate = (MergeActionState *)mt_state->mt_plans[0];
+
+ MergeAction *act_plan = (MergeAction *)act_pstate->ps.plan;
+
+ resetStringInfo(buf);
+
+ /*prepare the string for printing*/
+ switch(act_pstate->operation)
+ {
+ case CMD_INSERT:
+ appendStringInfoString(buf, "INSERT WHEN ");
+ break;
+ case CMD_UPDATE:
+ appendStringInfoString(buf, "UPDATE WHEN ");
+ break;
+ case CMD_DELETE:
+ appendStringInfoString(buf, "DELETE WHEN ");
+ break;
+ case CMD_DONOTHING:
+ appendStringInfoString(buf, "DO NOTHING WHEN ");
+ break;
+ case CMD_RAISEERR:
+ appendStringInfoString(buf, "RAISE ERROR WHEN ");
+ break;
+ default:
+ elog(ERROR, "unknown merge action type when explain");
+ }
+
+ if(act_plan->matched)
+ appendStringInfoString(buf, "MATCHED ");
+ else
+ appendStringInfoString(buf, "NOT MATCHED ");
+
+ if(act_plan->flattenedqual)
+ appendStringInfoString(buf, "AND ");
+
+ /*print it*/
+ ExplainPropertyText("ACTION", buf->data, es);
+
+ show_qual(act_plan->flattenedqual, " qual", &act_pstate->ps, ancestors, true, es);
+
+ }
+
+}
+
/*
* Show the target of a Scan node
*/
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2cbc192..99cb221 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2315,6 +2315,107 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
false, NULL, NULL, NIL, NULL);
}
+void
+ExecBSMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTable *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecBSUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecBSInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecBSDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
+
+void
+ExecASMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTable *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecASUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecASInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecASDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
static HeapTuple
GetTupleForTrigger(EState *estate,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2f33fdb..56c13ee 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,6 +171,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
+ case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_MergeAction:
+ result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+ estate, eflags);
+ break;
+
case T_Append:
result = (PlanState *) ExecInitAppend((Append *) node,
estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..8ab81ae 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -582,6 +582,143 @@ lreplace:;
return NULL;
}
+static TupleTableSlot *
+MergeRaiseErr(void)
+{
+ elog(NOTICE, "one tuple is ERROR");
+ return NULL;
+}
+
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot,
+ ModifyTableState *node,
+ EState *estate)
+{
+
+ TupleTableSlot *actslot = NULL;
+ TupleTableSlot *res = NULL;
+ ListCell *each;
+
+ /*
+ * try the merge actions one by one
+ */
+ foreach(each, node->mergeActPstates)
+ {
+ ModifyTableState *mt_pstate;
+
+ MergeActionState *action_pstate;
+
+ ExprContext *econtext;
+
+ bool matched;
+
+
+ mt_pstate = (ModifyTableState *)lfirst(each);
+
+ /*
+ * mt_pstate is supposed to have only ONE mt_plans,
+ * which is a MergeActionState
+ */
+ Assert(mt_pstate->mt_nplans == 1);
+
+ action_pstate = (MergeActionState *)mt_pstate->mt_plans[0];
+
+ matched = ((MergeAction *)action_pstate->ps.plan)->matched;
+
+
+ /*
+ * If tupleid == NULL, it is a NOT MATCHED case,
+ * else, it is a MATCHED case,
+ */
+ if((tupleid == NULL && matched) || (tupleid != NULL && !matched))
+ {
+ continue;
+ }
+
+ /*Setup the expression context*/
+ econtext = action_pstate->ps.ps_ExprContext;
+
+ /*
+ If the action has an additional qual,
+ which is not satisfied, skip it
+ */
+ if(action_pstate->ps.qual)
+ {
+ ResetExprContext(econtext);
+
+ econtext->ecxt_scantuple = slot;
+ econtext->ecxt_outertuple = planSlot;
+
+ if(!ExecQual(action_pstate->ps.qual, econtext,false))
+ {
+ continue;
+ }
+ }
+
+
+ /*
+ * OK, the input tuple is caugth by current action.
+ * If this action is "replaced" by rules, we will skip it
+ * AND THE REMAINING ACTIONS.
+ */
+ Assert(IsA(action_pstate->ps.plan, MergeAction));
+ if(((MergeAction *)action_pstate->ps.plan)->replaced)
+ return NULL;
+
+
+ /*Now we start to exec this action.
+ We have 5 action types*/
+
+ /*1. do nothing for a DO NOTHING action*/
+ if(action_pstate->operation == CMD_DONOTHING)
+ return NULL;
+
+ /*2. throw an error for a RAISE ERROR action*/
+ if(action_pstate->operation == CMD_RAISEERR)
+ return MergeRaiseErr();
+
+ /*3. project the result tuple slot, for INSERT/UPDATE action*/
+ if(action_pstate->operation != CMD_DELETE)
+ actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo,
+ slot, planSlot);
+
+ switch (action_pstate->operation)
+ {
+ case CMD_INSERT:
+ res = ExecInsert(actslot, planSlot, estate);
+ return res;
+ break;
+ case CMD_UPDATE:
+ res = ExecUpdate(tupleid,
+ actslot,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ case CMD_DELETE:
+ res = ExecDelete(tupleid,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ default:
+ elog(ERROR, "unknown merge action type for excute");
+ break;
+ }
+
+ }
+
+ /*
+ * Here, no action is taken. Let's do the default thing,
+ * which is Raise Error in crrent edition
+ */
+ return MergeRaiseErr();
+
+}
/*
* Process BEFORE EACH STATEMENT triggers
@@ -603,6 +740,9 @@ fireBSTriggers(ModifyTableState *node)
ExecBSDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecBSMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -629,6 +769,9 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecASMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -708,20 +851,34 @@ ExecModifyTable(ModifyTableState *node)
/*
* extract the 'ctid' junk attribute.
*/
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
Datum datum;
bool isNull;
datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
&isNull);
- /* shouldn't ever get a null result... */
+
if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
- tupleid = &tuple_ctid;
+ {
+ /*
+ * shouldn't ever get a null result for update and delete.
+ * Merge command will get a null ctid in "NOT MATCHED" case
+ */
+ if(operation != CMD_MERGE)
+ elog(ERROR, "ctid is NULL");
+ else
+ tupleid = NULL;
+ }
+ else
+ {
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+
+ tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
+ tupleid = &tuple_ctid;
+
+ }
}
/*
@@ -744,6 +901,10 @@ ExecModifyTable(ModifyTableState *node)
slot = ExecDelete(tupleid, planSlot,
&node->mt_epqstate, estate);
break;
+ case CMD_MERGE:
+ slot = ExecMerge(tupleid, slot, planSlot,
+ node, estate);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -771,6 +932,74 @@ ExecModifyTable(ModifyTableState *node)
return NULL;
}
+/*
+* When init a merge plan, we also need init its action plans.
+* These action plans are "MergeAction" plans .
+*
+* This function mainly handles the tlist and qual in the plan.
+* The returning result is a "MergeActionState".
+*/
+MergeActionState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+ MergeActionState *result;
+
+ /*
+ * do nothing when we get to the end of a leaf on tree.
+ */
+ if (node == NULL)
+ return NULL;
+
+ /*
+ * create state structure
+ */
+ result = makeNode(MergeActionState);
+ result->operation = node->operation;
+ result->ps.plan = (Plan *)node;
+ result->ps.state = estate;
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &result->ps);
+
+ /*
+ * initialize tuple type
+ */
+ ExecAssignResultTypeFromTL(&result->ps);
+
+
+ /*
+ * create expression context for node
+ */
+
+ ExecAssignExprContext(estate, &result->ps);
+
+
+ /*
+ * initialize child expressions
+ */
+ result->ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->plan.targetlist, &result->ps);
+
+
+ result->ps.qual = (List *)
+ ExecInitExpr((Expr *) node->plan.qual, &result->ps);
+
+
+ /*
+ * init the projection information
+ */
+ ExecAssignProjectionInfo(&result->ps, NULL);
+
+ /*
+ do we need a check for the plan output here ?
+ (by calling the ExecCheckPlanOutput() function
+ */
+
+ return result;
+}
+
/* ----------------------------------------------------------------
* ExecInitModifyTable
* ----------------------------------------------------------------
@@ -786,6 +1015,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ bool isMergeAction = false;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -826,6 +1056,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
+
+ /*
+ * test if this subplan node is a MergeAction.
+ * We need this information for setting the junckfilter.
+ * juckfiler is necessary for an ordinary UPDATE/DELETE plan,
+ * but not for an UPDATE/DELETE merge action
+ */
+ if(IsA(subplan, MergeAction))
+ isMergeAction = true;
+
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
estate->es_result_relation_info++;
i++;
@@ -955,7 +1195,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
- junk_filter_needed = true;
+ case CMD_MERGE:
+ if(!isMergeAction)
+ junk_filter_needed = true;
+ break;
+ case CMD_DONOTHING:
+ case CMD_RAISEERR:
break;
default:
elog(ERROR, "unknown operation");
@@ -978,9 +1223,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
ExecInitExtraTupleSlot(estate));
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
- /* For UPDATE/DELETE, find the ctid junk attr now */
+ /* For UPDATE/DELETE/MERGE, find the ctid junk attr now */
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
@@ -1006,6 +1251,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (estate->es_trig_tuple_slot == NULL)
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ /*
+ * for the merge actions, we need to do similar things as above
+ */
+ foreach(l, node->mergeActPlan)
+ {
+ PlanState *actpstate = ExecInitNode((Plan *)lfirst(l), estate, 0);
+ /*
+ * put the pstates of each action into ModifyTableState
+ */
+ mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+
+ }
+
return mtstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 93dcef5..8036a27 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
+ COPY_NODE_FIELD(mergeActPlan);
return newnode;
}
@@ -2273,6 +2274,11 @@ _copyQuery(Query *from)
COPY_NODE_FIELD(rowMarks);
COPY_NODE_FIELD(setOperations);
+ COPY_SCALAR_FIELD(isMergeAction);
+ COPY_SCALAR_FIELD(replaced);
+ /*merge action list*/
+ COPY_NODE_FIELD(mergeActQry);
+
return newnode;
}
@@ -2343,6 +2349,59 @@ _copySelectStmt(SelectStmt *from)
return newnode;
}
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+ MergeStmt *newnode = makeNode(MergeStmt);
+
+ COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(source);
+ COPY_NODE_FIELD(matchCondition);
+ COPY_NODE_FIELD(actions);
+
+ return newnode;
+
+}
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+ MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+ COPY_SCALAR_FIELD(match);
+ COPY_NODE_FIELD(condition);
+ COPY_NODE_FIELD(action);
+
+ return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+ MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+ newNode->type = T_MergeUpdate;
+
+ return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+ MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+ newNode->type = T_MergeInsert;
+
+ return newNode;
+}
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+ MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+ newNode->type = T_MergeDelete;
+
+ return newNode;
+}
+
static SetOperationStmt *
_copySetOperationStmt(SetOperationStmt *from)
{
@@ -3903,6 +3962,21 @@ copyObject(void *from)
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
+ case T_MergeStmt:
+ retval = _copyMergeStmt(from);
+ break;
+ case T_MergeConditionAction:
+ retval = _copyMergeConditionAction(from);
+ break;
+ case T_MergeUpdate:
+ retval = _copyMergeUpdate(from);
+ break;
+ case T_MergeInsert:
+ retval = _copyMergeInsert(from);
+ break;
+ case T_MergeDelete:
+ retval = _copyMergeDelete(from);
+ break;
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5d83727..3e3589a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,6 +878,9 @@ _equalQuery(Query *a, Query *b)
COMPARE_NODE_FIELD(rowMarks);
COMPARE_NODE_FIELD(setOperations);
+ COMPARE_SCALAR_FIELD(isMergeAction);
+ COMPARE_SCALAR_FIELD(replaced);
+ COMPARE_NODE_FIELD(mergeActQry);
return true;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 79baf4f..f61bd84 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
+ WRITE_NODE_FIELD(mergeActPlan);
}
static void
@@ -2019,6 +2020,56 @@ _outQuery(StringInfo str, Query *node)
WRITE_NODE_FIELD(limitCount);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
+ WRITE_BOOL_FIELD(isMergeAction);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_NODE_FIELD(mergeActQry);
+}
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+ WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+ WRITE_BOOL_FIELD(match);
+
+ WRITE_NODE_FIELD(condition);
+ WRITE_NODE_FIELD(action);
+
+
+}
+
+static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+ WRITE_NODE_TYPE("MERGESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(source);
+ WRITE_NODE_FIELD(matchCondition);
+ WRITE_NODE_FIELD(actions);
+
+}
+
+static void
+_outMergeAction(StringInfo str, MergeAction*node)
+{
+ _outPlanInfo(str, (Plan *)node);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_ENUM_FIELD(operation, CmdType);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_NODE_FIELD(flattenedqual);
+}
+
+static void
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+ WRITE_NODE_TYPE("DELETESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(usingClause);
+ WRITE_NODE_FIELD(whereClause);
+ WRITE_NODE_FIELD(returningList);
}
static void
@@ -2904,6 +2955,18 @@ _outNode(StringInfo str, void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_MergeAction:
+ _outMergeAction(str, obj);
+ break;
+ case T_MergeStmt:
+ _outMergeStmt(str, obj);
+ break;
+ case T_MergeConditionAction:
+ _outMergeConditionAction(str,obj);
+ break;
+ case T_DeleteStmt:
+ _outDeleteStmt(str,obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bc6e2a6..9c137cc 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -218,7 +218,11 @@ _readQuery(void)
READ_NODE_FIELD(limitCount);
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
-
+ READ_BOOL_FIELD(isMergeAction);
+ READ_BOOL_FIELD(matched);
+ READ_BOOL_FIELD(replaced);
+ READ_NODE_FIELD(mergeActQry);
+
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..2b7fd13 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -102,7 +102,12 @@ static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
int *ordNumCols,
AttrNumber **ordColIdx,
Oid **ordOperators);
-
+static ModifyTable *merge_action_planner(PlannerGlobal *glob,
+ Query *parse,
+ Plan *top_plan);
+static void merge_action_list_planner(PlannerGlobal *glob,
+ Query *parse,
+ ModifyTable *mainplan);
/*****************************************************************************
*
@@ -565,6 +570,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
returningLists,
rowMarks,
SS_assign_special_param(root));
+
+ /*do a simple plan for each actions in the merge command.
+ *put them in mergeActPlan list;
+ */
+ merge_action_list_planner(glob, parse, (ModifyTable *)plan);
}
}
@@ -584,6 +594,137 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
return plan;
}
+static void
+merge_action_list_planner(PlannerGlobal *glob, Query *parse, ModifyTable *mainplan)
+{
+ ListCell *l;
+
+ /*this is a function for MERGE command only*/
+ if(parse->commandType != CMD_MERGE ||
+ mainplan->operation != CMD_MERGE)
+ return;
+
+ /*if the merge actions are already there, no need to do it again*/
+ if(mainplan->mergeActPlan != NIL)
+ return;
+
+ /*plan each action query*/
+ foreach(l, parse->mergeActQry)
+ {
+ Plan *actplan = (Plan *)merge_action_planner(glob,
+ (Query *)lfirst(l),
+ (Plan *)linitial(mainplan->plans)
+ );
+
+ mainplan->mergeActPlan = lappend(mainplan->mergeActPlan, actplan);
+ }
+
+ return;
+}
+
+/*create plan for a single merge action*/
+static ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+ Plan *top_plan)
+{
+ PlannerInfo *root;
+ MergeAction *actplan;
+ ModifyTable *result;
+
+ List *returningLists;
+ List *rowMarks;
+
+ /*
+ * no having clause in a merge action
+ */
+ Assert(parse->havingQual == NULL);
+
+
+ /* Create a PlannerInfo data structure for this subquery */
+ root = makeNode(PlannerInfo);
+ root->parse = parse;
+ root->glob = glob;
+ root->query_level = 1;
+ root->parent_root = NULL;
+ root->planner_cxt = CurrentMemoryContext;
+ root->init_plans = NIL;
+ root->cte_plan_ids = NIL;
+ root->eq_classes = NIL;
+ root->append_rel_list = NIL;
+ root->hasPseudoConstantQuals = false;
+ root->hasRecursion = false;
+ root->wt_param_id = -1;
+ root->non_recursive_plan = NULL;
+
+
+ /*
+ * Create the action plan node
+ */
+ actplan = makeNode(MergeAction);
+ actplan->operation = parse->commandType;
+ actplan->replaced = parse->replaced;
+ actplan->matched = parse->matched;
+
+ /*
+ * Do expression preprocessing on targetlist and quals.
+ */
+ parse->targetList = (List *)
+ preprocess_expression(root, (Node *) parse->targetList,
+ EXPRKIND_TARGET);
+
+ preprocess_qual_conditions(root, (Node *) parse->jointree);
+
+
+ /*
+ * we need a flat qual for explaining
+ */
+ actplan->flattenedqual = flatten_join_alias_vars(root, parse->jointree->quals);
+
+ /*copy the cost from the top_plan*/
+ actplan->plan.startup_cost = top_plan->startup_cost;
+ actplan->plan.total_cost = top_plan->total_cost;
+ actplan->plan.plan_rows = top_plan->plan_rows;
+ actplan->plan.plan_width = top_plan->plan_width;
+
+ /*
+ * prepare the result
+ */
+ if(parse->targetList)
+ actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+ actplan->plan.qual = (List *)parse->jointree->quals;
+ push_up_merge_action_vars(actplan, parse);
+
+ if (parse->returningList)
+ {
+ List *rlist;
+
+ Assert(parse->resultRelation);
+ rlist = set_returning_clause_references(root->glob,
+ parse->returningList,
+ &actplan->plan,
+ parse->resultRelation);
+ returningLists = list_make1(rlist);
+ }
+ else
+ returningLists = NIL;
+
+
+ if (parse->rowMarks)
+ rowMarks = NIL;
+ else
+ rowMarks = root->rowMarks;
+
+ result = make_modifytable(parse->commandType,
+ copyObject(root->resultRelations),
+ list_make1(actplan),
+ returningLists,
+ rowMarks,
+ SS_assign_special_param(root));
+
+ return result;
+}
+
/*
* preprocess_expression
* Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..b4514b8 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,23 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
result_relation, range_table);
/*
- * for "update" and "delete" queries, add ctid of the result relation into
- * the target list so that the ctid will propagate through execution and
- * ExecutePlan() will be able to identify the right tuple to replace or
- * delete. This extra field is marked "junk" so that it is not stored
+ * for "update" , "delete" and "merge" queries, add ctid of the result
+ * relation into the target list so that the ctid will propagate through
+ * execution and ExecutePlan() will be able to identify the right tuple
+ * to replace or delete.
+ * This extra field is marked "junk" so that it is not stored
* back into the tuple.
+ *
+ * BUT, if the query node is a merge action,
+ * we don't need to expend the ctid attribute in tlist.
+ * The tlist of the merge top level plan already contains
+ * a "ctid" junk attr of the target relation.
*/
- if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+
+ if(!parse->isMergeAction &&
+ (command_type == CMD_UPDATE ||
+ command_type == CMD_DELETE ||
+ command_type == CMD_MERGE))
{
TargetEntry *tle;
Var *var;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92c2208..1775554 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -67,6 +67,16 @@ typedef struct
bool inserted_sublink; /* have we inserted a SubLink? */
} flatten_join_alias_vars_context;
+typedef struct
+{
+ int varno_source;
+ int varno_target;
+ int varno_join;
+
+ int offset_source;
+ int offset_target;
+} push_up_merge_action_vars_context;
+
static bool pull_varnos_walker(Node *node,
pull_varnos_context *context);
static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
@@ -83,6 +93,8 @@ static bool pull_var_clause_walker(Node *node,
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static bool push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context);
/*
@@ -677,6 +689,91 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
(void *) context);
}
+/*
+* When prepare for the MERGE command, we have made a
+* left join between the Source table and target table as the
+* main plan.
+*
+* In this case, the range table contains ONLY THREE range table entries:
+* 1. the source table, which may be a subquery or a plain table
+* 2. the entry of the targe table, which is a plain table
+* 3. join expression with the sourse table and target table as its parameters.
+*
+* Each merge action of the command has its own query and
+* plan nodes as well. And, the vars in its target list and qual
+* expressions may refers to the attribute in any one of the above 3
+* range table entries.
+*
+* However, since the result tuple slots of merge actions are
+* projected from the returned tuple of the join, we need to
+* mapping the vars of source table and target table to their
+* corresponding attributes in the third range table entry.
+*
+* This function does the opposit of the flatten_join_alias_vars()
+* function. It walks through the target list and qual of a
+* MergeAction plan, changes the vars' varno and varattno to the
+* corresponding position in the upper level join RTE.
+*/
+void
+push_up_merge_action_vars(MergeAction *actplan, Query *actqry)
+{
+ push_up_merge_action_vars_context context;
+ RangeTblEntry *source_rte = rt_fetch(1,actqry->rtable);
+
+
+ /*
+ * We are supposed to do a more careful assingment
+ * of the values in context
+ * But lets take a shortcut for simple.
+ */
+ context.varno_source = 1;
+ context.varno_target = 2;
+ context.varno_join = 3;
+
+ context.offset_source = 0;
+
+
+ context.offset_target = list_length(source_rte->eref->colnames);
+
+ push_up_merge_action_vars_walker(actplan->plan.targetlist, &context);
+
+ push_up_merge_action_vars_walker(actplan->plan.qual, &context);
+
+}
+
+static bool
+push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *)node;
+
+ if(var->varno == context->varno_source)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_source;
+ return false;
+ }
+ else if(var->varno == context->varno_target)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_target;
+ return false;
+ }
+ else if(var->varno == context->varno_join)
+ return false;
+ else
+ elog(ERROR, "the vars in merge action tlist of qual should only belongs to the source table or targe table");
+
+
+ }
+
+ return expression_tree_walker(node, push_up_merge_action_vars_walker,
+ (void *) context);
+}
/*
* flatten_join_alias_vars
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..b23cadd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +165,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
* Optimizable statements
*/
case T_InsertStmt:
+ case T_MergeInsert:
result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
break;
case T_DeleteStmt:
+ case T_MergeDelete:
result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
break;
case T_UpdateStmt:
+ case T_MergeUpdate:
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
break;
+ case T_MergeStmt:
+ result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+ break;
+
case T_SelectStmt:
{
SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +290,28 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->commandType = CMD_DELETE;
- /* set up range table with just the result rel */
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_DELETE);
-
qry->distinctClause = NIL;
/*
- * The USING clause is non-standard SQL syntax, and is equivalent in
- * functionality to the FROM list that can be specified for UPDATE. The
- * USING keyword is used rather than FROM because FROM is already a
- * keyword in the DELETE syntax.
- */
- transformFromClause(pstate, stmt->usingClause);
-
+ * The input stmt could be a MergeDelete node.
+ * In this case, we don't need the process on range table.
+ */
+ if(IsA(stmt, DeleteStmt))
+ {
+ /* set up range table with just the result rel */
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_DELETE);
+ /*
+ * The USING clause is non-standard SQL syntax, and is equivalent in
+ * functionality to the FROM list that can be specified for UPDATE. The
+ * USING keyword is used rather than FROM because FROM is already a
+ * keyword in the DELETE syntax.
+ */
+ transformFromClause(pstate, stmt->usingClause);
+ }
+
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -347,6 +361,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* VALUES list, or general SELECT input. We special-case VALUES, both for
* efficiency and so we can handle DEFAULT specifications.
*/
+
+ /*a MergeInsert statment is always a VALUE clause*/
isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
/*
@@ -382,7 +398,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, ACL_INSERT);
/* Validate stmt->cols list, or build default list if no list given */
@@ -1730,16 +1747,19 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_UPDATE);
+ if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+ {
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_UPDATE);
- /*
- * the FROM clause is non-standard SQL syntax. We used to be able to do
- * this with REPLACE in POSTQUEL so we keep the feature.
- */
- transformFromClause(pstate, stmt->fromClause);
+ /*
+ * the FROM clause is non-standard SQL syntax. We used to be able to do
+ * this with REPLACE in POSTQUEL so we keep the feature.
+ */
+ transformFromClause(pstate, stmt->fromClause);
+ }
qry->targetList = transformTargetList(pstate, stmt->targetList);
@@ -2241,3 +2261,389 @@ applyLockingClause(Query *qry, Index rtindex,
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
+
+/*
+transform an action of merge command into a query.
+No change of the pstate range table is allowed in this function.
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+ Query *actqry;
+
+ /*
+ * firstly, we need to make sure that DELETE and UPDATE
+ * actions are only taken in MATCHED condition,
+ * and INSERTs are only takend when not MATCHED
+ */
+
+ switch(condact->action->type)
+ {
+ case T_MergeDelete:/*a delete action*/
+ {
+ MergeDelete *deleteact = (MergeDelete *)(condact->action);
+ Assert(IsA(deleteact,MergeDelete));
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+
+ /*put new right code to the result relaion.
+ This line chages the RTE in range table directly*/
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+
+ deleteact->relation = stmt->relation;
+ deleteact->usingClause = stmt->source;
+ deleteact->whereClause = condact->condition;;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)deleteact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_DELETE ||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper DELETE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeUpdate:/*an update action*/
+ {
+ MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+ Assert(IsA(updateact,MergeUpdate));
+
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+
+
+ /*the "targetlist" of the updateact is filled in the parser */
+ updateact->relation = stmt->relation;
+ updateact->fromClause = stmt->source;
+ updateact->whereClause = condact->condition;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)updateact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_UPDATE||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper UPDATE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeInsert:/*an insert action*/
+ {
+ MergeInsert *insertact = (MergeInsert *)(condact->action);
+ Assert(IsA(insertact,MergeInsert));
+
+ if(condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;
+
+ /*the "cols" and "selectStmt" of the insertact is filled in the parser */
+ insertact->relation = stmt->relation;
+
+ /*
+ the merge insert action has a strange feature.
+ In an ordinary INSERT, the VALUES list can only
+ contains constants and DEFAULT. (am I right??)
+ But in the INSERT action of MERGE command,
+ the VALUES list can have expressions with
+ variables(attributes of the targe and source tables).
+ Besides, in the ordinary INSERT, a VALUES list can
+ never be followed by a WHERE clause.
+ But in MERGE INSERT action, there are matching conditions.
+
+ Thus, the output qry of this function is an INSERT
+ query in the style of "INSERT...VALUES...",
+ except that we have other range tables and a WHERE clause.
+ Note that it is also different from the "INSERT ... SELECT..."
+ query, in which the whole SELECT is a subquery.
+ (We don't have subquery here).
+
+ We construct this novel query structure in order
+ to keep consitency with other merge action types
+ (DELETE, UPDATE). In this way, all the merge action
+ queries are in fact share the very same Range Table,
+ They only differs in their target lists and join trees
+ */
+
+
+ /*parse the action query, this will call
+ transformInsertStmt() which analyzes the VALUES list.*/
+ actqry = transformStmt(pstate, (Node *)insertact);
+
+ /*do the WHERE clause here, Since the
+ transformInsertStmt() function only analyzes
+ the VALUES list but not the WHERE clause*/
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_INSERT||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper INSERT action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeDoNothing:
+ {
+ MergeDoNothing *nothingact = (MergeDoNothing *)(condact->action);
+
+ Assert(IsA(nothingact,MergeDoNothing));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_DONOTHING;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeError:
+ {
+ MergeError *erract = (MergeError *)(condact->action);
+ Assert(IsA(erract,MergeError));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_RAISEERR;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ default:
+ elog(ERROR, "unknown MERGE action type %d", condact->action->type);
+ break;
+
+ }
+
+ /*never comes here*/
+ return NULL;
+}
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+ Query *qry;
+
+ ColumnRef *starRef;
+ ResTarget *starResTarget;
+ ListCell *act;
+ ListCell *l;
+ JoinExpr *joinexp;
+ int rtindex;
+ MergeConditionAction *lastaction;
+
+ /*The source list has only one element*/
+ if(list_length(stmt->source) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("now we only accept merge command with only ONE source table")));
+
+ /*now, do the real tranformation of the merge command. */
+ qry = makeNode(Query);
+ qry->commandType = CMD_MERGE;
+
+ /*
+ What we are doing here is to create a query like
+ "SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+
+ Note:
+ 1. we set the "match condition" as the join qualification.
+ The left join will scan both the matched and non-matched tuples.
+
+ 2. a normal SELECT query has no "target relation".
+ But here we need to set the targe relation in query,
+ like the UPDATE/DELETE/INSERT queries.
+ So this is a left join SELECT with a "target table" in its range table.
+
+ 3. We don't have a specific ACL level for Merge, here we just use
+ ACL_SELECT.
+ But we will add other ACL levels when handle each merge actions.
+ */
+
+
+ /*before analyze the FROM clause, we need to set the target table.
+ We cannot call setTargetTable() function directly.
+ We only need the lock target relation, without adding it to Range table.
+ */
+ setTargetTableLock(pstate, stmt->relation);
+
+ /*create the FROM clause. Make the join expression first*/
+ joinexp = makeNode(JoinExpr);
+ joinexp->jointype = JOIN_LEFT;
+ joinexp->isNatural = FALSE;
+ /*source list has only one element*/
+ joinexp->larg = linitial(stmt->source);
+ joinexp->rarg = (Node *)stmt->relation;
+ /*match condtion*/
+ joinexp->quals = stmt->matchCondition;
+
+ /*transform the FROM clause. The target relation and
+ source relation will be add to Rtable here. */
+ transformFromClause(pstate, list_make1(joinexp));
+
+ /*the targetList of the main query is "*" */
+ starRef = makeNode(ColumnRef);
+ starRef->fields = list_make1(makeNode(A_Star));
+ starRef->location = 1;
+
+ starResTarget = makeNode(ResTarget);
+ starResTarget->name = NULL;
+ starResTarget->indirection = NIL;
+ starResTarget->val = (Node *)starRef;
+ starResTarget->location = 1;
+
+ qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+ /*we don't need the WHERE clause here. Set it null. */
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ /*now , we find out the RTE for the target relation,
+ and do some unfinished jobs*/
+ rtindex = 1;
+ foreach(l, pstate->p_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+ if(rte->relid == pstate->p_target_relation->rd_id)
+ {
+ /*find the RTE*/
+ pstate->p_target_rangetblentry = rte;
+ rte->requiredPerms = ACL_SELECT;
+ qry->resultRelation = rtindex;
+ break;
+ }
+ rtindex++;
+ }
+
+ if(pstate->p_target_rangetblentry == NULL)
+ elog(ERROR, "cannot find the RTE for target table");
+
+
+ qry->rtable = pstate->p_rtable;
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ /*
+ * Top-level aggregates are simply disallowed in MERGE
+ */
+ if (pstate->p_hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in top level of MERGE"),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in MERGE"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
+
+#if 0
+ /*
+ the main query is done. Ready for tansform actions.
+
+ Firstly, we check the last action of the action list.
+ If it is not a DO NOTING action, we need to generate
+ an INSERT DEFAULT VALUES action and append it to action list.
+ */
+ lastaction = (MergeConditionAction *)llast(stmt->actions);
+
+ if(lastaction->action == NULL)
+ {
+ /*
+ we have a do nothing action here,
+ What we need to do is just delete it from action list
+ */
+ stmt->actions = list_truncate(stmt->actions,
+ list_length(stmt->actions) - 1);
+ }
+ else
+ {
+ /*
+ The last action is no the DO NOTHING action,
+ we need to generate an INSERT action.
+ */
+ lastaction = makeNode(MergeConditionAction);
+
+ lastaction->condition = NULL;
+ lastaction->match = NOT;
+ lastaction->action = makeNode(MergeInsert);
+
+ /*nothing need to be filled into the node*/
+
+ stmt->actions = lappend(stmt->actions, lastaction);
+ }
+#endif
+
+ /*
+ For each actions ,we transform it to a seperate query.
+ the action queries shares the exactly same
+ range table with the main query.
+
+ In other words, in the extra condtions of the sub actions,
+ we don't allow involvement of new tables
+ */
+ qry->mergeActQry = NIL;
+
+ foreach(act,stmt->actions)
+ {
+ MergeConditionAction *mca = (MergeConditionAction *)lfirst(act);
+ Query *actqry;
+
+ /*transform the act (and its condition) as a single query. */
+ actqry = transformMergeActions(pstate, stmt, mca);
+
+ /*since we don't invoke setTargetTable() in transformMergeActions(),
+ we need to set actqry->resultRelation here
+ */
+ actqry->resultRelation = qry->resultRelation;
+
+ /*put it into the list*/
+ qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+ }
+
+ return qry;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..06b9c81 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,10 @@ static TypeName *TableFuncTypeName(List *columns);
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+%type <node> MergeStmt opt_and_condition merge_condition_action merge_action
+%type <boolean> opt_not
+%type <list> merge_condition_action_list
+
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -504,6 +508,8 @@ static TypeName *TableFuncTypeName(List *columns);
MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MATCHED MERGE RAISE ERROR_P
+
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -725,6 +731,7 @@ stmt :
| ListenStmt
| LoadStmt
| LockStmt
+ | MergeStmt
| NotifyStmt
| PrepareStmt
| ReassignOwnedStmt
@@ -6952,6 +6959,7 @@ ExplainableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt
+ | MergeStmt
| DeclareCursorStmt
| CreateAsStmt
| ExecuteStmt /* by default all are $$=$1 */
@@ -7297,6 +7305,114 @@ set_target_list:
/*****************************************************************************
*
* QUERY:
+ * MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt:
+ MERGE INTO relation_expr_opt_alias
+ USING table_ref
+ ON a_expr
+ merge_condition_action_list
+ {
+ MergeStmt *m = makeNode(MergeStmt);
+
+ m->relation = $3;
+
+ /*although we have only one USING table,
+ we still make it a list, maybe in future
+ we will allow mutliple USING tables.*/
+ m->matchCondition = $7;
+ m->source = list_make1($5);
+ m->actions = $8;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+merge_condition_action_list:
+ merge_condition_action
+ { $$ = list_make1($1); }
+ | merge_condition_action_list merge_condition_action
+ { $$ = lappend($1,$2); }
+ ;
+
+merge_condition_action:
+ WHEN opt_not MATCHED opt_and_condition THEN merge_action
+ {
+ MergeConditionAction *m = makeNode(MergeConditionAction);
+
+ m->match = $2;
+ m->condition = $4;
+ m->action = $6;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+
+opt_and_condition:
+ AND a_expr {$$ = $2;}
+ | /*EMPTY*/ {$$ = NULL;}
+ ;
+
+opt_not:
+ NOT {$$ = false;}
+ | /*EMPTY*/ {$$ = true;}
+ ;
+
+merge_action:
+ DELETE_P
+ {
+ $$ = (Node *)makeNode(MergeDelete);
+ }
+ | UPDATE SET set_clause_list
+ {
+ UpdateStmt *n = makeNode(MergeUpdate);
+ n->targetList = $3;
+ $$ = (Node *)n;
+ }
+ | INSERT values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = $2;
+
+ $$ = (Node *)n;
+ }
+
+ | INSERT '(' insert_column_list ')' values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = $3;
+ n->selectStmt = $5;
+
+ $$ = (Node *)n;
+ }
+ | INSERT DEFAULT VALUES
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = NULL;
+
+ $$ = (Node *)n;
+ }
+ | DO NOTHING
+ {
+ $$ = makeNode(MergeDoNothing);
+ }
+ | RAISE ERROR_P
+ {
+ $$ = makeNode(MergeError);
+ }
+ ;
+
+
+
+/*****************************************************************************
+ *
+ * QUERY:
* CURSOR STATEMENTS
*
*****************************************************************************/
@@ -10882,6 +10998,7 @@ unreserved_keyword:
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EXCLUDE
| EXCLUDING
@@ -10935,7 +11052,9 @@ unreserved_keyword:
| LOGIN_P
| MAPPING
| MATCH
+ | MATCHED
| MAXVALUE
+ | MERGE
| MINUTE_P
| MINVALUE
| MODE
@@ -10977,6 +11096,7 @@ unreserved_keyword:
| PROCEDURAL
| PROCEDURE
| QUOTE
+ | RAISE
| RANGE
| READ
| REASSIGN
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index f30132a..8876f73 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -213,6 +213,25 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
return rtindex;
}
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+
+ /* Close old target; this could only happen for multi-action rules */
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, NoLock);
+
+ /*
+ * Open target rel and grab suitable lock (which we will hold till end of
+ * transaction).
+ *
+ * free_parsestate() will eventually do the corresponding heap_close(),
+ * but *not* release the lock.
+ */
+ pstate->p_target_relation = parserOpenTable(pstate, relation,
+ RowExclusiveLock);
+}
+
/*
* Simplify InhOption (yes/no/default) into boolean yes/no.
*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..3ee0428 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1836,6 +1836,41 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
return rewritten;
}
+/*if the merge action type has already been processed by rewriter*/
+#define insert_rewrite (1<<0)
+#define delete_rewrite (1<<1)
+#define update_rewrite (1<<2)
+
+/*if the merge action type is fully replace by rules.*/
+#define insert_instead (1<<3)
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+#define merge_action_already_rewrite(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+ (acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+ (acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+#define set_action_rewrite(acttype, flag) \
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_rewrite;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_rewrite;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_rewrite;}
+
+#define merge_action_instead(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_instead)) || \
+ (acttype == CMD_UPDATE && (flag & update_instead)) || \
+ (acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_instead;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_instead;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_instead;}
/*
* QueryRewrite -
@@ -1861,7 +1896,151 @@ QueryRewrite(Query *parsetree)
*
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = RewriteQuery(parsetree, NIL);
+ if(parsetree->commandType == CMD_MERGE)
+ {
+ /*
+ *for merge query, we have a set of action queries (not subquery).
+ *each of these action queries should be applied to RewriteQuery().
+ */
+ ListCell *l;
+
+ int flag = 0;
+
+ List *pre_qry = NIL;
+ List *post_qry = NIL;
+
+
+ querylist = NIL;
+
+
+ /*rewrite the merge action queries one by one.*/
+ foreach(l, parsetree->mergeActQry)
+ {
+ List *queryList4action = NIL;
+ Query *actionqry;
+ Query *q;
+
+
+ actionqry = lfirst(l);
+
+ /*
+ * no rewriting for DO NOTHING or ERROR
+ */
+ if(actionqry->commandType == CMD_DONOTHING ||
+ actionqry->commandType == CMD_RAISEERR)
+ continue;
+
+
+ /*
+ *if this kind of actions are fully replaced by rules,
+ *we mark it as "replaced"
+ */
+ if(merge_action_instead(actionqry->commandType, flag))
+ {
+ /*
+ *Still need to call RewriteQuery(),
+ *since we need the process on target list and so on.
+ *BUT, the returned list is discarded
+ */
+ RewriteQuery(actionqry, NIL);
+ actionqry->replaced = true;
+ continue;
+ }
+
+
+ /*if this kind of actions are already processed by rewriter, skip it.*/
+ if(merge_action_already_rewrite(actionqry->commandType, flag))
+ {
+ RewriteQuery(actionqry, NIL);
+ continue;
+ }
+
+ /*ok this action has not been processed before, let's do it now.*/
+ queryList4action = RewriteQuery(actionqry, NIL);
+
+ /*this kind of actions has been processed, set the flag*/
+ set_action_rewrite(actionqry->commandType,flag);
+
+ /*if the returning list is nil, this merge action
+ is replaced by a do-nothing rule*/
+ if(queryList4action == NIL)
+ {
+ /*set the flag for other merge actions of the same type*/
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ continue;
+ }
+
+ /*
+ * if the rewriter return a non-NIL list, the merge action query
+ *could be one element in it.
+ *if so, it must be the head (for INSERT acton)
+ *or tail (for UPDATE/DELETE action).
+ */
+
+ /*test the list head*/
+ q = (Query *)linitial(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*
+ *the merge action is the head, the remaining part
+ *of the list are the queries generated by rules
+ *we put them in the post_qry list.
+ */
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+
+ queryList4action = list_delete_first(queryList4action);
+ post_qry = list_concat(post_qry,queryList4action);
+
+ continue;
+
+ }
+
+ /*test the list tail*/
+ q = (Query *)llast(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*the merge action is the tail.
+ Put the rule queries in pre_qry list*/
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+ queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+ pre_qry = list_concat(pre_qry,queryList4action);
+ continue;
+
+ }
+
+ /*here, the merge action query is not in the rewriten query list,
+ *which means the action is replaced by INSTEAD rule(s).
+ *We need to mark it as "replaced".
+
+ For a INSERT action, we put the rule queries in the post list
+ otherwise, in the pre list
+ */
+ if(actionqry->commandType == CMD_INSERT)
+ post_qry = list_concat(post_qry,queryList4action);
+ else
+ pre_qry = list_concat(pre_qry,queryList4action);
+
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ }
+
+ /*finally, put the 3 lists into one.
+ *If all the merge actions are replaced by rules,
+ *the original merge query
+ *will not be involved in the querylist.
+ */
+ querylist = list_concat(pre_qry,querylist);
+ querylist = list_concat(querylist, post_qry);
+
+ }
+ else
+ querylist = RewriteQuery(parsetree, NIL);
/*
* Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"DELETE %u", queryDesc->estate->es_processed);
break;
+ case CMD_MERGE:
+ snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+ "MERGE %u", queryDesc->estate->es_processed);
+ break;
default:
strcpy(completionTag, "???");
break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8960246..2733e5d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
+ case CMD_MERGE:
return false;
default:
elog(WARNING, "unrecognized commandType: %d",
@@ -1398,6 +1399,10 @@ CreateCommandTag(Node *parsetree)
tag = "SELECT";
break;
+ case T_MergeStmt:
+ tag = "MERGE";
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -2235,6 +2240,7 @@ GetCommandLogLevel(Node *parsetree)
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
+ case T_MergeStmt:
lev = LOGSTMT_MOD;
break;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 98bc0c6..be4ad6e 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -163,6 +163,8 @@ extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
+extern void ExecBSMergeTriggers(ModifyTableState *mt_state);
+extern void ExecASMergeTriggers(ModifyTableState *mt_state);
extern void AfterTriggerBeginXact(void);
extern void AfterTriggerBeginQuery(void);
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..422e3ce 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+extern MergeActionState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 09fdb5d..3013f13 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1031,9 +1031,22 @@ typedef struct ModifyTableState
int mt_whichplan; /* which one is being executed (0..n-1) */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
+ List *mergeActPstates; /*the list of the planstate of meger command actions.
+ NIL if this is not a merge command.
+ The elements if it are still ModifyTableState nodes*/
} ModifyTableState;
/* ----------------
+ * MergeActionState information
+ * ----------------
+ */
+typedef struct MergeActionState
+{
+ PlanState ps; /* its first field is NodeTag */
+ CmdType operation;
+} MergeActionState;
+
+/* ----------------
* AppendState information
*
* nplans how many plans are in the array
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..a840349 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
T_Plan = 100,
T_Result,
T_ModifyTable,
+ T_MergeAction,
T_Append,
T_RecursiveUnion,
T_BitmapAnd,
@@ -86,6 +87,7 @@ typedef enum NodeTag
T_PlanState = 200,
T_ResultState,
T_ModifyTableState,
+ T_MergeActionState,
T_AppendState,
T_RecursiveUnionState,
T_BitmapAndState,
@@ -347,6 +349,13 @@ typedef enum NodeTag
T_AlterUserMappingStmt,
T_DropUserMappingStmt,
T_AlterTableSpaceOptionsStmt,
+ T_MergeStmt,
+ T_MergeConditionAction,
+ T_MergeUpdate,
+ T_MergeDelete,
+ T_MergeInsert,
+ T_MergeDoNothing,
+ T_MergeError,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -511,6 +520,9 @@ typedef enum CmdType
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
CMD_DELETE,
+ CMD_MERGE, /*merge stmt*/
+ CMD_DONOTHING,
+ CMD_RAISEERR,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
CMD_NOTHING /* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b591073..5f329ef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -146,6 +146,12 @@ typedef struct Query
Node *setOperations; /* set-operation tree if this is top level of
* a UNION/INTERSECT/EXCEPT query */
+ /*the fileds for merge actions*/
+ bool isMergeAction; /*if this query is a merge action. */
+ bool matched; /*this is a MATCHED action or NOT*/
+ bool replaced; /*is this merge action replaced by rules*/
+ List *mergeActQry; /* the list of all the merge actions.
+ * used only for merge query statment*/
} Query;
@@ -990,6 +996,58 @@ typedef struct SelectStmt
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;
+/*The structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+ NodeTag type;
+ RangeVar *relation; /*targe relation for merge */
+
+ /* source relations for the merge.
+ *Currently, we only allwo single-source merge,
+ *so the length of this list should always be 1
+ */
+ List *source;
+ Node *matchCondition; /* qualifications of the merge*/
+
+ /*list of MergeConditionAction structure.
+ *It stores all the matched / not-matched
+ *conditions and the corresponding actions
+ *The elments of this list are MergeConditionAction
+ *nodes
+ */
+ List *actions;
+
+}MergeStmt;
+
+/* the structure for the actions of MERGE command.
+* Holds info of the clauses like
+* "WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+ NodeTag type;
+ bool match; /*match or not match*/
+ Node *condition;/*the AND condition for this action*/
+ Node *action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+/*
+* The merge action nodes are in fact the
+* ordinary nodes of UPDATE,DELETE and INSERT
+*/
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
+
+typedef struct MergeDoNothing
+{
+ NodeTag type;
+}MergeDoNothing;
+
+typedef struct MergeError
+{
+ NodeTag type;
+}MergeError;
/* ----------------------
* Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..a020051 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,9 +169,25 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *mergeActPlan; /*the plans for merge actions,
+ which are also ModifyTable nodes*/
} ModifyTable;
/* ----------------
+ * MergeAction node -
+ * The plan node for the actions of MERGE command
+ * ----------------
+ */
+typedef struct MergeAction
+{
+ Plan plan;
+ bool replaced; /*if this action is replaced by INSTEAD rules*/
+ CmdType operation;/* INSERT, UPDATE, or DELETE */
+ bool matched;
+ List *flattenedqual; /*the flattened qual expression of action*/
+}MergeAction;
+
+/* ----------------
* Append node -
* Generate the concatenation of the results of sub-plans.
* ----------------
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index b0e04a0..4d6c9e8 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -15,6 +15,7 @@
#define VAR_H
#include "nodes/relation.h"
+#include "nodes/plannodes.h"
typedef enum
{
@@ -32,5 +33,5 @@ extern int locate_var_of_relation(Node *node, int relid, int levelsup);
extern int find_minimum_var_level(Node *node);
extern List *pull_var_clause(Node *node, PVCPlaceHolderBehavior behavior);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
-
+extern void push_up_merge_action_vars(MergeAction * actplan,Query * actqry);
#endif /* VAR_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..91ddcb6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -141,6 +141,7 @@ PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD)
PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
@@ -229,7 +230,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
@@ -295,6 +298,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
+PG_KEYWORD("raise", RAISE, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..b54f530 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,7 @@
extern void transformFromClause(ParseState *pstate, List *frmList);
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
extern bool interpretInhOption(InhOption inhOpt);
extern bool interpretOidsOption(List *defList);
On 07/08/10 10:58, Boxuan Zhai wrote:
I have just finished a new patch, with the following feature:
Please include the regression tests in the patch too. Also, I note that
there's a few merge conflicts when applied over CVS HEAD from today, can
you please fix the bitrot?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On 09/08/10 14:47, Heikki Linnakangas wrote:
On 07/08/10 10:58, Boxuan Zhai wrote:
I have just finished a new patch, with the following feature:
Please include the regression tests in the patch too....
And the docs changes too.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Mon, Aug 9, 2010 at 8:30 PM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:
On 09/08/10 14:47, Heikki Linnakangas wrote:
On 07/08/10 10:58, Boxuan Zhai wrote:
I have just finished a new patch, with the following feature:
Please include the regression tests in the patch too....
And the docs changes too.
I have put everything in one patch, against the latest git repository. The
program is tested on my machine.
Thanks
Show quoted text
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
final_merge.tarapplication/x-tar; name=final_merge.tarDownload
final_merge.patch 0000644 0000764 0001040 00000301110 11430140142 015377 0 ustar bxzhai Administrators diff --git a/doc/src/sgml/merge.sgml b/doc/src/sgml/merge.sgml
new file mode 100644
index 0000000..b30c0ff
--- /dev/null
+++ b/doc/src/sgml/merge.sgml
@@ -0,0 +1,409 @@
+<!--
+$PostgreSQL$
+-->
+
+<refentry id="SQL-MERGE">
+ <refmeta>
+ <refentrytitle id="SQL-MERGE-TITLE">MERGE</refentrytitle>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>MERGE</refname>
+ <refpurpose>update, insert or delete rows of a table based upon source data</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-merge">
+ <primary>MERGE</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+MERGE INTO <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
+USING <replaceable class="PARAMETER">source-query</replaceable>
+ON <replaceable class="PARAMETER">join_condition</replaceable>
+[<replaceable class="PARAMETER">when_clause</replaceable> [...]]
+
+where <replaceable class="PARAMETER">when_clause</replaceable> is
+
+{ WHEN MATCHED [ AND <replaceable class="PARAMETER">condition</replaceable> ] THEN { <replaceable class="PARAMETER">merge_update</replaceable> | DELETE | DO NOTHING | RAISE ERROR}
+ WHEN NOT MATCHED [ AND <replaceable class="PARAMETER">condition</replaceable> ] THEN { <replaceable class="PARAMETER">merge_insert</replaceable> | DO NOTHING | RAISE ERROR} }
+
+where <replaceable class="PARAMETER">merge_update</replaceable> is
+
+UPDATE SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
+
+and <replaceable class="PARAMETER">merge_insert</replaceable> is
+
+INSERT [( <replaceable class="PARAMETER">column</replaceable> [, ...] )] { VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) | DEFAULT VALUES }
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>MERGE</command> performs at most one action on each row from
+ the target table, driven by the rows from the source query. This
+ provides a way to specify a single SQL statement that can conditionally
+ <command>UPDATE</command> or <command>INSERT</command> rows, a task
+ that would otherwise require multiple procedural language statements.
+ </para>
+
+ <para>
+ First, the <command>MERGE</command> command performs a left outer join
+ from source query to target table, producing zero or more merged rows. For
+ each merged row, <literal>WHEN</> clauses are evaluated in the
+ specified order until one of them is activated. The corresponding action
+ is then applied and processing continues for the next row.
+ </para>
+
+ <para>
+ <command>MERGE</command> actions have the same effect as
+ regular <command>UPDATE</command>, <command>INSERT</command>, or
+ <command>DELETE</command> commands of the same names, though the syntax
+ is slightly different.
+ </para>
+
+ <para>
+ If no <literal>WHEN</> clause activates then an implicit action of
+ <literal>RAISE ERROR</> is performed for that row. If that
+ implicit action is not desirable an explicit action of
+ <literal>DO NOTHING</> may be specified instead.
+ </para>
+
+ <para>
+ <command>MERGE</command> will only affect rows only in the specified table.
+ </para>
+
+ <para>
+ There is no <literal>RETURNING</> clause with <command>MERGE</command>.
+ </para>
+
+ <para>
+ There is no MERGE privilege.
+ You must have the <literal>UPDATE</literal> privilege on the table
+ if you specify an update action, the <literal>INSERT</literal> privilege if
+ you specify an insert action and/or the <literal>DELETE</literal> privilege
+ if you wish to delete. You will also require the
+ <literal>SELECT</literal> privilege to any table whose values are read
+ in the <replaceable class="parameter">expressions</replaceable> or
+ <replaceable class="parameter">condition</replaceable>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the table to merge into.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">alias</replaceable></term>
+ <listitem>
+ <para>
+ A substitute name for the target table. When an alias is
+ provided, it completely hides the actual name of the table. For
+ example, given <literal>MERGE foo AS f</>, the remainder of the
+ <command>MERGE</command> statement must refer to this table as
+ <literal>f</> not <literal>foo</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">source-query</replaceable></term>
+ <listitem>
+ <para>
+ A query (<command>SELECT</command> statement or <command>VALUES</command>
+ statement) that supplies the rows to be merged into the target table.
+ Refer to the <xref linkend="sql-select" endterm="sql-select-title">
+ statement or <xref linkend="sql-values" endterm="sql-values-title">
+ statement for a description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">join_condition</replaceable></term>
+ <listitem>
+ <para>
+ <replaceable class="parameter">join_condition</replaceable> is
+ an expression resulting in a value of type
+ <type>boolean</type> (similar to a <literal>WHERE</literal>
+ clause) that specifies which rows in the join are considered to
+ match. You should ensure that the join produces at most one output
+ row for each row to be modified. An attempt to modify any row of the
+ target table more than once will result in an error. This behaviour
+ requires the user to take greater care in using <command>MERGE</command>,
+ though is required explicitly by the SQL Standard.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">condition</replaceable></term>
+ <listitem>
+ <para>
+ An expression that returns a value of type <type>boolean</type>.
+ If this expression returns <literal>true</> then the <literal>WHEN</>
+ clause will be activated and the corresponding action will occur for
+ that row.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">merge_update</replaceable></term>
+ <listitem>
+ <para>
+ The specification of an <literal>UPDATE</> action. Do not include
+ the table name, as you would normally do with an
+ <xref linkend="sql-update" endterm="sql-update-title"> command.
+ For example, <literal>UPDATE tab SET col = 1</> is invalid. Also,
+ do not include a <literal>WHERE</> clause, since only the current
+ can be updated. For example,
+ <literal>UPDATE SET col = 1 WHERE key = 57</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">merge_insert</replaceable></term>
+ <listitem>
+ <para>
+ The specification of an <literal>INSERT</> action. Do not include
+ the table name, as you would normally do with an
+ <xref linkend="sql-insert" endterm="sql-insert-title"> command.
+ For example, <literal>INSERT INTO tab VALUES (1, 50)</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">column</replaceable></term>
+ <listitem>
+ <para>
+ The name of a column in <replaceable
+ class="PARAMETER">table</replaceable>.
+ The column name can be qualified with a subfield name or array
+ subscript, if needed. Do not include the table's name in the
+ specification of a target column — for example,
+ <literal>UPDATE SET tab.col = 1</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">expression</replaceable></term>
+ <listitem>
+ <para>
+ An expression to assign to the column. The expression can use the
+ old values of this and other columns in the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DEFAULT</literal></term>
+ <listitem>
+ <para>
+ Set the column to its default value (which will be NULL if no
+ specific default expression has been assigned to it).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Outputs</title>
+
+ <para>
+ On successful completion, a <command>MERGE</> command returns a command
+ tag of the form
+<screen>
+MERGE <replaceable class="parameter">total-count</replaceable>
+</screen>
+ The <replaceable class="parameter">total-count</replaceable> is the number
+ of rows changed (either updated, inserted or deleted).
+ If <replaceable class="parameter">total-count</replaceable> is 0, no rows
+ were changed (this is not considered an error).
+ </para>
+
+ <para>
+ The number of rows updated, inserted or deleted is not available as part
+ of the command tag. An optional NOTIFY message can be generated to
+ present this information, if desired.
+<screen>
+NOTIFY: 34 rows processed: 11 updated, 5 deleted, 15 inserted, 3 default inserts, 0 no action
+</screen>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ What essentially happens is that the target table is left outer-joined to
+ the tables mentioned in the <replaceable>source-query</replaceable>, and
+ each output row of the join may then activate at most one when-clause.
+ The row will be matched only once per statement, so the status of
+ <literal>MATCHED</> or <literal>NOT MATCHED</> cannot change once testing
+ of <literal>WHEN</> clauses has begun. <command>MERGE</command> will not
+ invoke Rules.
+ </para>
+
+ <para>
+ The following steps take place during the execution of
+ <command>MERGE</command>.
+ <orderedlist>
+ <listitem>
+ <para>
+ Perform any BEFORE STATEMENT triggers for actions specified, whether or
+ not they actually occur.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform left outer join from source to target table. Then for each row:
+ <orderedlist>
+ <listitem>
+ <para>
+ Evaluate whether each row is MATCHED or NOT MATCHED.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Test each WHEN condition in the order specified until one activates.
+ Identify the action and its event type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any BEFORE ROW triggers that fire for the action's event type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Apply the action specified.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any AFTER ROW triggers that fire for the action's event type.
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any AFTER STATEMENT triggers for actions specified, whether or
+ not they actually occur.
+ </para>
+ </listitem>
+ </orderedlist>
+ In summary, statement triggers for an event type (say, INSERT) will
+ be fired whenever we <emphasis>specify</> an action of that kind. Row-level
+ triggers will fire only for event type <emphasis>activated</>.
+ So a <command>MERGE</command> might fire statement triggers for both
+ <literal>UPDATE</> and <literal>INSERT</>, even though only
+ <literal>UPDATE</> row triggers were fired.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Attempt to insert a new stock item along with the quantity of stock. If
+ the item already exists, instead update the stock count of the existing
+ item.
+<programlisting>
+MERGE INTO wines w
+USING (VALUES('Chateau Lafite 2003', '24')) v
+ON v.column1 = w.winename
+WHEN NOT MATCHED THEN
+ INSERT VALUES(v.column1, v.column2)
+WHEN MATCHED THEN
+ UPDATE SET stock = stock + v.column2;
+</programlisting>
+ </para>
+
+ <para>
+ Perform maintenance on CustomerAccounts based upon new Transactions.
+ The following statement will fail if any accounts have had more than
+ one transaction
+
+<programlisting>
+MERGE CustomerAccount CA
+
+USING (SELECT CustomerId, TransactionValue,
+ FROM Transactions
+ WHERE TransactionId > 35345678) AS T
+
+ON T.CustomerId = CA.CustomerId
+
+WHEN MATCHED THEN
+ UPDATE SET Balance = Balance - TransactionValue
+
+WHEN NOT MATCHED THEN
+ INSERT (CustomerId, Balance)
+ VALUES (T.CustomerId, T.TransactionValue)
+;
+</programlisting>
+
+ so the right way to do this is to pre-aggregate the data
+
+<programlisting>
+MERGE CustomerAccount CA
+
+USING (SELECT CustomerId, Sum(TransactionValue) As TransactionSum
+ FROM Transactions
+ WHERE TransactionId > 35345678
+ GROUP BY CustomerId) AS T
+
+ON T.CustomerId = CA.CustomerId
+
+WHEN MATCHED THEN
+ UPDATE SET Balance = Balance - TransactionSum
+
+WHEN NOT MATCHED THEN
+ INSERT (CustomerId, Balance)
+ VALUES (T.CustomerId, T.TransactionSum)
+;
+</programlisting>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ This command conforms to the <acronym>SQL</acronym> standard, except
+ that the <literal>DELETE</literal> and <literal>DO NOTHING</> actions
+ are <productname>PostgreSQL</productname> extensions.
+ </para>
+
+ <para>
+ According to the standard, the column-list syntax for an <literal>UPDATE</>
+ action should allow a list of columns to be assigned from a single
+ row-valued expression.
+ This is not currently implemented — the source must be a list
+ of independent expressions.
+ </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 7518c84..3aa9aab 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -119,6 +119,7 @@ Complete list of usable sgml source files in this directory.
<!entity listen system "listen.sgml">
<!entity load system "load.sgml">
<!entity lock system "lock.sgml">
+<!entity merge system "merge.sgml">
<!entity move system "move.sgml">
<!entity notify system "notify.sgml">
<!entity prepare system "prepare.sgml">
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 5268794..677390c 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -327,6 +327,9 @@ UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- continue with other operations, and eventually
COMMIT;
</programlisting>
+
+ This operation can be executed in a single statement using
+ <xref linkend="sql-merge" endterm="sql-merge-title">.
</para>
<para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index c33d883..5068235 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -147,6 +147,7 @@
&listen;
&load;
&lock;
+ &merge;
&move;
¬ify;
&prepare;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..1a9e39a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -73,6 +73,8 @@ static void show_sort_keys(SortState *sortstate, List *ancestors,
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
+static void ExplainMergeActions(ModifyTableState *mt_planstate,
+ List *ancestors, ExplainState *es);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
@@ -636,6 +638,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case CMD_DELETE:
pname = operation = "Delete";
break;
+ case CMD_MERGE:
+ pname = operation = "Merge";
+ break;
default:
pname = "???";
break;
@@ -1190,6 +1195,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
switch (nodeTag(plan))
{
case T_ModifyTable:
+ ExplainMergeActions((ModifyTableState *)planstate, ancestors, es);
+
ExplainMemberNodes(((ModifyTable *) plan)->plans,
((ModifyTableState *) planstate)->mt_plans,
ancestors, es);
@@ -1482,6 +1489,64 @@ explain_get_index_name(Oid indexId)
return result;
}
+static void
+ExplainMergeActions(ModifyTableState *mt_planstate, List *ancestors, ExplainState *es)
+{
+ ListCell *l;
+ StringInfo buf = makeStringInfo();
+
+ if(mt_planstate->operation != CMD_MERGE || mt_planstate->mergeActPstates == NIL)
+ return;
+
+ foreach(l,mt_planstate->mergeActPstates)
+ {
+ ModifyTableState *mt_state = (ModifyTableState *)lfirst(l);
+
+ MergeActionState *act_pstate = (MergeActionState *)mt_state->mt_plans[0];
+
+ MergeAction *act_plan = (MergeAction *)act_pstate->ps.plan;
+
+ resetStringInfo(buf);
+
+ /*prepare the string for printing*/
+ switch(act_pstate->operation)
+ {
+ case CMD_INSERT:
+ appendStringInfoString(buf, "INSERT WHEN ");
+ break;
+ case CMD_UPDATE:
+ appendStringInfoString(buf, "UPDATE WHEN ");
+ break;
+ case CMD_DELETE:
+ appendStringInfoString(buf, "DELETE WHEN ");
+ break;
+ case CMD_DONOTHING:
+ appendStringInfoString(buf, "DO NOTHING WHEN ");
+ break;
+ case CMD_RAISEERR:
+ appendStringInfoString(buf, "RAISE ERROR WHEN ");
+ break;
+ default:
+ elog(ERROR, "unknown merge action type when explain");
+ }
+
+ if(act_plan->matched)
+ appendStringInfoString(buf, "MATCHED ");
+ else
+ appendStringInfoString(buf, "NOT MATCHED ");
+
+ if(act_plan->flattenedqual)
+ appendStringInfoString(buf, "AND ");
+
+ /*print it*/
+ ExplainPropertyText("ACTION", buf->data, es);
+
+ show_qual(act_plan->flattenedqual, " qual", &act_pstate->ps, ancestors, true, es);
+
+ }
+
+}
+
/*
* Show the target of a Scan node
*/
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8b017ae..e35da58 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2342,6 +2342,107 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
false, NULL, NULL, NIL, NULL);
}
+void
+ExecBSMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTable *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecBSUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecBSInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecBSDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
+
+void
+ExecASMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTable *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecASUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecASInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecASDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
static HeapTuple
GetTupleForTrigger(EState *estate,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b34a154..3a943a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -170,6 +170,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
+ case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_MergeAction:
+ result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+ estate, eflags);
+ break;
+
case T_Append:
result = (PlanState *) ExecInitAppend((Append *) node,
estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..8ab81ae 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -582,6 +582,143 @@ lreplace:;
return NULL;
}
+static TupleTableSlot *
+MergeRaiseErr(void)
+{
+ elog(NOTICE, "one tuple is ERROR");
+ return NULL;
+}
+
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot,
+ ModifyTableState *node,
+ EState *estate)
+{
+
+ TupleTableSlot *actslot = NULL;
+ TupleTableSlot *res = NULL;
+ ListCell *each;
+
+ /*
+ * try the merge actions one by one
+ */
+ foreach(each, node->mergeActPstates)
+ {
+ ModifyTableState *mt_pstate;
+
+ MergeActionState *action_pstate;
+
+ ExprContext *econtext;
+
+ bool matched;
+
+
+ mt_pstate = (ModifyTableState *)lfirst(each);
+
+ /*
+ * mt_pstate is supposed to have only ONE mt_plans,
+ * which is a MergeActionState
+ */
+ Assert(mt_pstate->mt_nplans == 1);
+
+ action_pstate = (MergeActionState *)mt_pstate->mt_plans[0];
+
+ matched = ((MergeAction *)action_pstate->ps.plan)->matched;
+
+
+ /*
+ * If tupleid == NULL, it is a NOT MATCHED case,
+ * else, it is a MATCHED case,
+ */
+ if((tupleid == NULL && matched) || (tupleid != NULL && !matched))
+ {
+ continue;
+ }
+
+ /*Setup the expression context*/
+ econtext = action_pstate->ps.ps_ExprContext;
+
+ /*
+ If the action has an additional qual,
+ which is not satisfied, skip it
+ */
+ if(action_pstate->ps.qual)
+ {
+ ResetExprContext(econtext);
+
+ econtext->ecxt_scantuple = slot;
+ econtext->ecxt_outertuple = planSlot;
+
+ if(!ExecQual(action_pstate->ps.qual, econtext,false))
+ {
+ continue;
+ }
+ }
+
+
+ /*
+ * OK, the input tuple is caugth by current action.
+ * If this action is "replaced" by rules, we will skip it
+ * AND THE REMAINING ACTIONS.
+ */
+ Assert(IsA(action_pstate->ps.plan, MergeAction));
+ if(((MergeAction *)action_pstate->ps.plan)->replaced)
+ return NULL;
+
+
+ /*Now we start to exec this action.
+ We have 5 action types*/
+
+ /*1. do nothing for a DO NOTHING action*/
+ if(action_pstate->operation == CMD_DONOTHING)
+ return NULL;
+
+ /*2. throw an error for a RAISE ERROR action*/
+ if(action_pstate->operation == CMD_RAISEERR)
+ return MergeRaiseErr();
+
+ /*3. project the result tuple slot, for INSERT/UPDATE action*/
+ if(action_pstate->operation != CMD_DELETE)
+ actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo,
+ slot, planSlot);
+
+ switch (action_pstate->operation)
+ {
+ case CMD_INSERT:
+ res = ExecInsert(actslot, planSlot, estate);
+ return res;
+ break;
+ case CMD_UPDATE:
+ res = ExecUpdate(tupleid,
+ actslot,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ case CMD_DELETE:
+ res = ExecDelete(tupleid,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ default:
+ elog(ERROR, "unknown merge action type for excute");
+ break;
+ }
+
+ }
+
+ /*
+ * Here, no action is taken. Let's do the default thing,
+ * which is Raise Error in crrent edition
+ */
+ return MergeRaiseErr();
+
+}
/*
* Process BEFORE EACH STATEMENT triggers
@@ -603,6 +740,9 @@ fireBSTriggers(ModifyTableState *node)
ExecBSDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecBSMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -629,6 +769,9 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecASMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -708,20 +851,34 @@ ExecModifyTable(ModifyTableState *node)
/*
* extract the 'ctid' junk attribute.
*/
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
Datum datum;
bool isNull;
datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
&isNull);
- /* shouldn't ever get a null result... */
+
if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
- tupleid = &tuple_ctid;
+ {
+ /*
+ * shouldn't ever get a null result for update and delete.
+ * Merge command will get a null ctid in "NOT MATCHED" case
+ */
+ if(operation != CMD_MERGE)
+ elog(ERROR, "ctid is NULL");
+ else
+ tupleid = NULL;
+ }
+ else
+ {
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+
+ tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
+ tupleid = &tuple_ctid;
+
+ }
}
/*
@@ -744,6 +901,10 @@ ExecModifyTable(ModifyTableState *node)
slot = ExecDelete(tupleid, planSlot,
&node->mt_epqstate, estate);
break;
+ case CMD_MERGE:
+ slot = ExecMerge(tupleid, slot, planSlot,
+ node, estate);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -771,6 +932,74 @@ ExecModifyTable(ModifyTableState *node)
return NULL;
}
+/*
+* When init a merge plan, we also need init its action plans.
+* These action plans are "MergeAction" plans .
+*
+* This function mainly handles the tlist and qual in the plan.
+* The returning result is a "MergeActionState".
+*/
+MergeActionState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+ MergeActionState *result;
+
+ /*
+ * do nothing when we get to the end of a leaf on tree.
+ */
+ if (node == NULL)
+ return NULL;
+
+ /*
+ * create state structure
+ */
+ result = makeNode(MergeActionState);
+ result->operation = node->operation;
+ result->ps.plan = (Plan *)node;
+ result->ps.state = estate;
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &result->ps);
+
+ /*
+ * initialize tuple type
+ */
+ ExecAssignResultTypeFromTL(&result->ps);
+
+
+ /*
+ * create expression context for node
+ */
+
+ ExecAssignExprContext(estate, &result->ps);
+
+
+ /*
+ * initialize child expressions
+ */
+ result->ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->plan.targetlist, &result->ps);
+
+
+ result->ps.qual = (List *)
+ ExecInitExpr((Expr *) node->plan.qual, &result->ps);
+
+
+ /*
+ * init the projection information
+ */
+ ExecAssignProjectionInfo(&result->ps, NULL);
+
+ /*
+ do we need a check for the plan output here ?
+ (by calling the ExecCheckPlanOutput() function
+ */
+
+ return result;
+}
+
/* ----------------------------------------------------------------
* ExecInitModifyTable
* ----------------------------------------------------------------
@@ -786,6 +1015,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ bool isMergeAction = false;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -826,6 +1056,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
+
+ /*
+ * test if this subplan node is a MergeAction.
+ * We need this information for setting the junckfilter.
+ * juckfiler is necessary for an ordinary UPDATE/DELETE plan,
+ * but not for an UPDATE/DELETE merge action
+ */
+ if(IsA(subplan, MergeAction))
+ isMergeAction = true;
+
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
estate->es_result_relation_info++;
i++;
@@ -955,7 +1195,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
- junk_filter_needed = true;
+ case CMD_MERGE:
+ if(!isMergeAction)
+ junk_filter_needed = true;
+ break;
+ case CMD_DONOTHING:
+ case CMD_RAISEERR:
break;
default:
elog(ERROR, "unknown operation");
@@ -978,9 +1223,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
ExecInitExtraTupleSlot(estate));
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
- /* For UPDATE/DELETE, find the ctid junk attr now */
+ /* For UPDATE/DELETE/MERGE, find the ctid junk attr now */
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
@@ -1006,6 +1251,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (estate->es_trig_tuple_slot == NULL)
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ /*
+ * for the merge actions, we need to do similar things as above
+ */
+ foreach(l, node->mergeActPlan)
+ {
+ PlanState *actpstate = ExecInitNode((Plan *)lfirst(l), estate, 0);
+ /*
+ * put the pstates of each action into ModifyTableState
+ */
+ mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+
+ }
+
return mtstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 69262d6..1379686 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
+ COPY_NODE_FIELD(mergeActPlan);
return newnode;
}
@@ -2274,6 +2275,11 @@ _copyQuery(Query *from)
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
+ COPY_SCALAR_FIELD(isMergeAction);
+ COPY_SCALAR_FIELD(replaced);
+ /*merge action list*/
+ COPY_NODE_FIELD(mergeActQry);
+
return newnode;
}
@@ -2344,6 +2350,59 @@ _copySelectStmt(SelectStmt *from)
return newnode;
}
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+ MergeStmt *newnode = makeNode(MergeStmt);
+
+ COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(source);
+ COPY_NODE_FIELD(matchCondition);
+ COPY_NODE_FIELD(actions);
+
+ return newnode;
+
+}
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+ MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+ COPY_SCALAR_FIELD(match);
+ COPY_NODE_FIELD(condition);
+ COPY_NODE_FIELD(action);
+
+ return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+ MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+ newNode->type = T_MergeUpdate;
+
+ return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+ MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+ newNode->type = T_MergeInsert;
+
+ return newNode;
+}
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+ MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+ newNode->type = T_MergeDelete;
+
+ return newNode;
+}
+
static SetOperationStmt *
_copySetOperationStmt(SetOperationStmt *from)
{
@@ -3905,6 +3964,21 @@ copyObject(void *from)
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
+ case T_MergeStmt:
+ retval = _copyMergeStmt(from);
+ break;
+ case T_MergeConditionAction:
+ retval = _copyMergeConditionAction(from);
+ break;
+ case T_MergeUpdate:
+ retval = _copyMergeUpdate(from);
+ break;
+ case T_MergeInsert:
+ retval = _copyMergeInsert(from);
+ break;
+ case T_MergeDelete:
+ retval = _copyMergeDelete(from);
+ break;
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 667057b..f726a48 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -879,6 +879,9 @@ _equalQuery(Query *a, Query *b)
COMPARE_NODE_FIELD(setOperations);
COMPARE_NODE_FIELD(constraintDeps);
+ COMPARE_SCALAR_FIELD(isMergeAction);
+ COMPARE_SCALAR_FIELD(replaced);
+ COMPARE_NODE_FIELD(mergeActQry);
return true;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 04a6647..c88d422 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
+ WRITE_NODE_FIELD(mergeActPlan);
}
static void
@@ -2021,6 +2022,53 @@ _outQuery(StringInfo str, Query *node)
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
+ WRITE_BOOL_FIELD(isMergeAction);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_NODE_FIELD(mergeActQry);
+}
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+ WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+ WRITE_BOOL_FIELD(match);
+
+ WRITE_NODE_FIELD(condition);
+ WRITE_NODE_FIELD(action);
+}
+
+static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+ WRITE_NODE_TYPE("MERGESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(source);
+ WRITE_NODE_FIELD(matchCondition);
+ WRITE_NODE_FIELD(actions);
+}
+
+static void
+_outMergeAction(StringInfo str, MergeAction*node)
+{
+ _outPlanInfo(str, (Plan *)node);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_ENUM_FIELD(operation, CmdType);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_NODE_FIELD(flattenedqual);
+}
+
+static void
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+ WRITE_NODE_TYPE("DELETESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(usingClause);
+ WRITE_NODE_FIELD(whereClause);
+ WRITE_NODE_FIELD(returningList);
}
static void
@@ -2906,6 +2954,18 @@ _outNode(StringInfo str, void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_MergeAction:
+ _outMergeAction(str, obj);
+ break;
+ case T_MergeStmt:
+ _outMergeStmt(str, obj);
+ break;
+ case T_MergeConditionAction:
+ _outMergeConditionAction(str,obj);
+ break;
+ case T_DeleteStmt:
+ _outDeleteStmt(str,obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0a2edcb..a8581a0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -219,6 +219,10 @@ _readQuery(void)
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
+ READ_BOOL_FIELD(isMergeAction);
+ READ_BOOL_FIELD(matched);
+ READ_BOOL_FIELD(replaced);
+ READ_NODE_FIELD(mergeActQry);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..2b7fd13 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -102,7 +102,12 @@ static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
int *ordNumCols,
AttrNumber **ordColIdx,
Oid **ordOperators);
-
+static ModifyTable *merge_action_planner(PlannerGlobal *glob,
+ Query *parse,
+ Plan *top_plan);
+static void merge_action_list_planner(PlannerGlobal *glob,
+ Query *parse,
+ ModifyTable *mainplan);
/*****************************************************************************
*
@@ -565,6 +570,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
returningLists,
rowMarks,
SS_assign_special_param(root));
+
+ /*do a simple plan for each actions in the merge command.
+ *put them in mergeActPlan list;
+ */
+ merge_action_list_planner(glob, parse, (ModifyTable *)plan);
}
}
@@ -584,6 +594,137 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
return plan;
}
+static void
+merge_action_list_planner(PlannerGlobal *glob, Query *parse, ModifyTable *mainplan)
+{
+ ListCell *l;
+
+ /*this is a function for MERGE command only*/
+ if(parse->commandType != CMD_MERGE ||
+ mainplan->operation != CMD_MERGE)
+ return;
+
+ /*if the merge actions are already there, no need to do it again*/
+ if(mainplan->mergeActPlan != NIL)
+ return;
+
+ /*plan each action query*/
+ foreach(l, parse->mergeActQry)
+ {
+ Plan *actplan = (Plan *)merge_action_planner(glob,
+ (Query *)lfirst(l),
+ (Plan *)linitial(mainplan->plans)
+ );
+
+ mainplan->mergeActPlan = lappend(mainplan->mergeActPlan, actplan);
+ }
+
+ return;
+}
+
+/*create plan for a single merge action*/
+static ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+ Plan *top_plan)
+{
+ PlannerInfo *root;
+ MergeAction *actplan;
+ ModifyTable *result;
+
+ List *returningLists;
+ List *rowMarks;
+
+ /*
+ * no having clause in a merge action
+ */
+ Assert(parse->havingQual == NULL);
+
+
+ /* Create a PlannerInfo data structure for this subquery */
+ root = makeNode(PlannerInfo);
+ root->parse = parse;
+ root->glob = glob;
+ root->query_level = 1;
+ root->parent_root = NULL;
+ root->planner_cxt = CurrentMemoryContext;
+ root->init_plans = NIL;
+ root->cte_plan_ids = NIL;
+ root->eq_classes = NIL;
+ root->append_rel_list = NIL;
+ root->hasPseudoConstantQuals = false;
+ root->hasRecursion = false;
+ root->wt_param_id = -1;
+ root->non_recursive_plan = NULL;
+
+
+ /*
+ * Create the action plan node
+ */
+ actplan = makeNode(MergeAction);
+ actplan->operation = parse->commandType;
+ actplan->replaced = parse->replaced;
+ actplan->matched = parse->matched;
+
+ /*
+ * Do expression preprocessing on targetlist and quals.
+ */
+ parse->targetList = (List *)
+ preprocess_expression(root, (Node *) parse->targetList,
+ EXPRKIND_TARGET);
+
+ preprocess_qual_conditions(root, (Node *) parse->jointree);
+
+
+ /*
+ * we need a flat qual for explaining
+ */
+ actplan->flattenedqual = flatten_join_alias_vars(root, parse->jointree->quals);
+
+ /*copy the cost from the top_plan*/
+ actplan->plan.startup_cost = top_plan->startup_cost;
+ actplan->plan.total_cost = top_plan->total_cost;
+ actplan->plan.plan_rows = top_plan->plan_rows;
+ actplan->plan.plan_width = top_plan->plan_width;
+
+ /*
+ * prepare the result
+ */
+ if(parse->targetList)
+ actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+ actplan->plan.qual = (List *)parse->jointree->quals;
+ push_up_merge_action_vars(actplan, parse);
+
+ if (parse->returningList)
+ {
+ List *rlist;
+
+ Assert(parse->resultRelation);
+ rlist = set_returning_clause_references(root->glob,
+ parse->returningList,
+ &actplan->plan,
+ parse->resultRelation);
+ returningLists = list_make1(rlist);
+ }
+ else
+ returningLists = NIL;
+
+
+ if (parse->rowMarks)
+ rowMarks = NIL;
+ else
+ rowMarks = root->rowMarks;
+
+ result = make_modifytable(parse->commandType,
+ copyObject(root->resultRelations),
+ list_make1(actplan),
+ returningLists,
+ rowMarks,
+ SS_assign_special_param(root));
+
+ return result;
+}
+
/*
* preprocess_expression
* Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..b4514b8 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,23 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
result_relation, range_table);
/*
- * for "update" and "delete" queries, add ctid of the result relation into
- * the target list so that the ctid will propagate through execution and
- * ExecutePlan() will be able to identify the right tuple to replace or
- * delete. This extra field is marked "junk" so that it is not stored
+ * for "update" , "delete" and "merge" queries, add ctid of the result
+ * relation into the target list so that the ctid will propagate through
+ * execution and ExecutePlan() will be able to identify the right tuple
+ * to replace or delete.
+ * This extra field is marked "junk" so that it is not stored
* back into the tuple.
+ *
+ * BUT, if the query node is a merge action,
+ * we don't need to expend the ctid attribute in tlist.
+ * The tlist of the merge top level plan already contains
+ * a "ctid" junk attr of the target relation.
*/
- if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+
+ if(!parse->isMergeAction &&
+ (command_type == CMD_UPDATE ||
+ command_type == CMD_DELETE ||
+ command_type == CMD_MERGE))
{
TargetEntry *tle;
Var *var;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92c2208..1775554 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -67,6 +67,16 @@ typedef struct
bool inserted_sublink; /* have we inserted a SubLink? */
} flatten_join_alias_vars_context;
+typedef struct
+{
+ int varno_source;
+ int varno_target;
+ int varno_join;
+
+ int offset_source;
+ int offset_target;
+} push_up_merge_action_vars_context;
+
static bool pull_varnos_walker(Node *node,
pull_varnos_context *context);
static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
@@ -83,6 +93,8 @@ static bool pull_var_clause_walker(Node *node,
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static bool push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context);
/*
@@ -677,6 +689,91 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
(void *) context);
}
+/*
+* When prepare for the MERGE command, we have made a
+* left join between the Source table and target table as the
+* main plan.
+*
+* In this case, the range table contains ONLY THREE range table entries:
+* 1. the source table, which may be a subquery or a plain table
+* 2. the entry of the targe table, which is a plain table
+* 3. join expression with the sourse table and target table as its parameters.
+*
+* Each merge action of the command has its own query and
+* plan nodes as well. And, the vars in its target list and qual
+* expressions may refers to the attribute in any one of the above 3
+* range table entries.
+*
+* However, since the result tuple slots of merge actions are
+* projected from the returned tuple of the join, we need to
+* mapping the vars of source table and target table to their
+* corresponding attributes in the third range table entry.
+*
+* This function does the opposit of the flatten_join_alias_vars()
+* function. It walks through the target list and qual of a
+* MergeAction plan, changes the vars' varno and varattno to the
+* corresponding position in the upper level join RTE.
+*/
+void
+push_up_merge_action_vars(MergeAction *actplan, Query *actqry)
+{
+ push_up_merge_action_vars_context context;
+ RangeTblEntry *source_rte = rt_fetch(1,actqry->rtable);
+
+
+ /*
+ * We are supposed to do a more careful assingment
+ * of the values in context
+ * But lets take a shortcut for simple.
+ */
+ context.varno_source = 1;
+ context.varno_target = 2;
+ context.varno_join = 3;
+
+ context.offset_source = 0;
+
+
+ context.offset_target = list_length(source_rte->eref->colnames);
+
+ push_up_merge_action_vars_walker(actplan->plan.targetlist, &context);
+
+ push_up_merge_action_vars_walker(actplan->plan.qual, &context);
+
+}
+
+static bool
+push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *)node;
+
+ if(var->varno == context->varno_source)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_source;
+ return false;
+ }
+ else if(var->varno == context->varno_target)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_target;
+ return false;
+ }
+ else if(var->varno == context->varno_join)
+ return false;
+ else
+ elog(ERROR, "the vars in merge action tlist of qual should only belongs to the source table or targe table");
+
+
+ }
+
+ return expression_tree_walker(node, push_up_merge_action_vars_walker,
+ (void *) context);
+}
/*
* flatten_join_alias_vars
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..cd62acd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +165,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
* Optimizable statements
*/
case T_InsertStmt:
+ case T_MergeInsert:
result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
break;
case T_DeleteStmt:
+ case T_MergeDelete:
result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
break;
case T_UpdateStmt:
+ case T_MergeUpdate:
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
break;
+ case T_MergeStmt:
+ result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+ break;
+
case T_SelectStmt:
{
SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +290,28 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->commandType = CMD_DELETE;
- /* set up range table with just the result rel */
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_DELETE);
-
qry->distinctClause = NIL;
/*
- * The USING clause is non-standard SQL syntax, and is equivalent in
- * functionality to the FROM list that can be specified for UPDATE. The
- * USING keyword is used rather than FROM because FROM is already a
- * keyword in the DELETE syntax.
- */
- transformFromClause(pstate, stmt->usingClause);
-
+ * The input stmt could be a MergeDelete node.
+ * In this case, we don't need the process on range table.
+ */
+ if(IsA(stmt, DeleteStmt))
+ {
+ /* set up range table with just the result rel */
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_DELETE);
+ /*
+ * The USING clause is non-standard SQL syntax, and is equivalent in
+ * functionality to the FROM list that can be specified for UPDATE. The
+ * USING keyword is used rather than FROM because FROM is already a
+ * keyword in the DELETE syntax.
+ */
+ transformFromClause(pstate, stmt->usingClause);
+ }
+
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -347,6 +361,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* VALUES list, or general SELECT input. We special-case VALUES, both for
* efficiency and so we can handle DEFAULT specifications.
*/
+
+ /*a MergeInsert statment is always a VALUE clause*/
isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
/*
@@ -382,7 +398,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, ACL_INSERT);
/* Validate stmt->cols list, or build default list if no list given */
@@ -1730,16 +1747,19 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_UPDATE);
+ if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+ {
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_UPDATE);
- /*
- * the FROM clause is non-standard SQL syntax. We used to be able to do
- * this with REPLACE in POSTQUEL so we keep the feature.
- */
- transformFromClause(pstate, stmt->fromClause);
+ /*
+ * the FROM clause is non-standard SQL syntax. We used to be able to do
+ * this with REPLACE in POSTQUEL so we keep the feature.
+ */
+ transformFromClause(pstate, stmt->fromClause);
+ }
qry->targetList = transformTargetList(pstate, stmt->targetList);
@@ -2241,3 +2261,390 @@ applyLockingClause(Query *qry, Index rtindex,
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
+
+/*
+transform an action of merge command into a query.
+No change of the pstate range table is allowed in this function.
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+ Query *actqry;
+
+ /*
+ * firstly, we need to make sure that DELETE and UPDATE
+ * actions are only taken in MATCHED condition,
+ * and INSERTs are only takend when not MATCHED
+ */
+
+ switch(condact->action->type)
+ {
+ case T_MergeDelete:/*a delete action*/
+ {
+ MergeDelete *deleteact = (MergeDelete *)(condact->action);
+ Assert(IsA(deleteact,MergeDelete));
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+
+ /*put new right code to the result relaion.
+ This line chages the RTE in range table directly*/
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+
+ deleteact->relation = stmt->relation;
+ deleteact->usingClause = stmt->source;
+ deleteact->whereClause = condact->condition;;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)deleteact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_DELETE ||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper DELETE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeUpdate:/*an update action*/
+ {
+ MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+ Assert(IsA(updateact,MergeUpdate));
+
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+
+
+ /*the "targetlist" of the updateact is filled in the parser */
+ updateact->relation = stmt->relation;
+ updateact->fromClause = stmt->source;
+ updateact->whereClause = condact->condition;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)updateact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_UPDATE||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper UPDATE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeInsert:/*an insert action*/
+ {
+ MergeInsert *insertact = (MergeInsert *)(condact->action);
+ Assert(IsA(insertact,MergeInsert));
+
+ if(condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;
+
+ /*the "cols" and "selectStmt" of the insertact is filled in the parser */
+ insertact->relation = stmt->relation;
+
+ /*
+ the merge insert action has a strange feature.
+ In an ordinary INSERT, the VALUES list can only
+ contains constants and DEFAULT. (am I right??)
+ But in the INSERT action of MERGE command,
+ the VALUES list can have expressions with
+ variables(attributes of the targe and source tables).
+ Besides, in the ordinary INSERT, a VALUES list can
+ never be followed by a WHERE clause.
+ But in MERGE INSERT action, there are matching conditions.
+
+ Thus, the output qry of this function is an INSERT
+ query in the style of "INSERT...VALUES...",
+ except that we have other range tables and a WHERE clause.
+ Note that it is also different from the "INSERT ... SELECT..."
+ query, in which the whole SELECT is a subquery.
+ (We don't have subquery here).
+
+ We construct this novel query structure in order
+ to keep consitency with other merge action types
+ (DELETE, UPDATE). In this way, all the merge action
+ queries are in fact share the very same Range Table,
+ They only differs in their target lists and join trees
+ */
+
+
+ /*parse the action query, this will call
+ transformInsertStmt() which analyzes the VALUES list.*/
+ actqry = transformStmt(pstate, (Node *)insertact);
+
+ /*do the WHERE clause here, Since the
+ transformInsertStmt() function only analyzes
+ the VALUES list but not the WHERE clause*/
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_INSERT||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper INSERT action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeDoNothing:
+ {
+ MergeDoNothing *nothingact = (MergeDoNothing *)(condact->action);
+
+ Assert(IsA(nothingact,MergeDoNothing));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_DONOTHING;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeError:
+ {
+ MergeError *erract = (MergeError *)(condact->action);
+ Assert(IsA(erract,MergeError));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_RAISEERR;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ default:
+ elog(ERROR, "unknown MERGE action type %d", condact->action->type);
+ break;
+
+ }
+
+ /*never comes here*/
+ return NULL;
+}
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+ Query *qry;
+
+ ColumnRef *starRef;
+ ResTarget *starResTarget;
+ ListCell *act;
+ ListCell *l;
+ JoinExpr *joinexp;
+ int rtindex;
+ MergeConditionAction *lastaction;
+
+ /*The source list has only one element*/
+ if(list_length(stmt->source) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("now we only accept merge command with only ONE source table")));
+
+ /*now, do the real tranformation of the merge command. */
+ qry = makeNode(Query);
+ qry->commandType = CMD_MERGE;
+
+ /*
+ What we are doing here is to create a query like
+ "SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+
+ Note:
+ 1. we set the "match condition" as the join qualification.
+ The left join will scan both the matched and non-matched tuples.
+
+ 2. a normal SELECT query has no "target relation".
+ But here we need to set the targe relation in query,
+ like the UPDATE/DELETE/INSERT queries.
+ So this is a left join SELECT with a "target table" in its range table.
+
+ 3. We don't have a specific ACL level for Merge, here we just use
+ ACL_SELECT.
+ But we will add other ACL levels when handle each merge actions.
+ */
+
+
+ /*before analyze the FROM clause, we need to set the target table.
+ We cannot call setTargetTable() function directly.
+ We only need the lock target relation, without adding it to Range table.
+ */
+ setTargetTableLock(pstate, stmt->relation);
+
+ /*create the FROM clause. Make the join expression first*/
+ joinexp = makeNode(JoinExpr);
+ joinexp->jointype = JOIN_LEFT;
+ joinexp->isNatural = FALSE;
+ /*source list has only one element*/
+ joinexp->larg = linitial(stmt->source);
+ joinexp->rarg = (Node *)stmt->relation;
+ /*match condtion*/
+ joinexp->quals = stmt->matchCondition;
+
+ /*transform the FROM clause. The target relation and
+ source relation will be add to Rtable here. */
+ transformFromClause(pstate, list_make1(joinexp));
+
+ /*the targetList of the main query is "*" */
+ starRef = makeNode(ColumnRef);
+ starRef->fields = list_make1(makeNode(A_Star));
+ starRef->location = 1;
+
+ starResTarget = makeNode(ResTarget);
+ starResTarget->name = NULL;
+ starResTarget->indirection = NIL;
+ starResTarget->val = (Node *)starRef;
+ starResTarget->location = 1;
+
+ qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+ /*we don't need the WHERE clause here. Set it null. */
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ /*now , we find out the RTE for the target relation,
+ and do some unfinished jobs*/
+ rtindex = 1;
+ foreach(l, pstate->p_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+ if(rte->relid == pstate->p_target_relation->rd_id)
+ {
+ /*find the RTE*/
+ pstate->p_target_rangetblentry = rte;
+ rte->requiredPerms = ACL_SELECT;
+ rte->inh = false;
+ qry->resultRelation = rtindex;
+ break;
+ }
+ rtindex++;
+ }
+
+ if(pstate->p_target_rangetblentry == NULL)
+ elog(ERROR, "cannot find the RTE for target table");
+
+
+ qry->rtable = pstate->p_rtable;
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ /*
+ * Top-level aggregates are simply disallowed in MERGE
+ */
+ if (pstate->p_hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in top level of MERGE"),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in MERGE"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
+
+#if 0
+ /*
+ the main query is done. Ready for tansform actions.
+
+ Firstly, we check the last action of the action list.
+ If it is not a DO NOTING action, we need to generate
+ an INSERT DEFAULT VALUES action and append it to action list.
+ */
+ lastaction = (MergeConditionAction *)llast(stmt->actions);
+
+ if(lastaction->action == NULL)
+ {
+ /*
+ we have a do nothing action here,
+ What we need to do is just delete it from action list
+ */
+ stmt->actions = list_truncate(stmt->actions,
+ list_length(stmt->actions) - 1);
+ }
+ else
+ {
+ /*
+ The last action is no the DO NOTHING action,
+ we need to generate an INSERT action.
+ */
+ lastaction = makeNode(MergeConditionAction);
+
+ lastaction->condition = NULL;
+ lastaction->match = NOT;
+ lastaction->action = makeNode(MergeInsert);
+
+ /*nothing need to be filled into the node*/
+
+ stmt->actions = lappend(stmt->actions, lastaction);
+ }
+#endif
+
+ /*
+ For each actions ,we transform it to a seperate query.
+ the action queries shares the exactly same
+ range table with the main query.
+
+ In other words, in the extra condtions of the sub actions,
+ we don't allow involvement of new tables
+ */
+ qry->mergeActQry = NIL;
+
+ foreach(act,stmt->actions)
+ {
+ MergeConditionAction *mca = (MergeConditionAction *)lfirst(act);
+ Query *actqry;
+
+ /*transform the act (and its condition) as a single query. */
+ actqry = transformMergeActions(pstate, stmt, mca);
+
+ /*since we don't invoke setTargetTable() in transformMergeActions(),
+ we need to set actqry->resultRelation here
+ */
+ actqry->resultRelation = qry->resultRelation;
+
+ /*put it into the list*/
+ qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+ }
+
+ return qry;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aab7789..277725b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,10 @@ static TypeName *TableFuncTypeName(List *columns);
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+%type <node> MergeStmt opt_and_condition merge_condition_action merge_action
+%type <boolean> opt_not
+%type <list> merge_condition_action_list
+
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -505,6 +509,8 @@ static TypeName *TableFuncTypeName(List *columns);
MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MATCHED MERGE RAISE ERROR_P
+
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -726,6 +732,7 @@ stmt :
| ListenStmt
| LoadStmt
| LockStmt
+ | MergeStmt
| NotifyStmt
| PrepareStmt
| ReassignOwnedStmt
@@ -6986,6 +6993,7 @@ ExplainableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt
+ | MergeStmt
| DeclareCursorStmt
| CreateAsStmt
| ExecuteStmt /* by default all are $$=$1 */
@@ -7331,6 +7339,114 @@ set_target_list:
/*****************************************************************************
*
* QUERY:
+ * MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt:
+ MERGE INTO relation_expr_opt_alias
+ USING table_ref
+ ON a_expr
+ merge_condition_action_list
+ {
+ MergeStmt *m = makeNode(MergeStmt);
+
+ m->relation = $3;
+
+ /*although we have only one USING table,
+ we still make it a list, maybe in future
+ we will allow mutliple USING tables.*/
+ m->matchCondition = $7;
+ m->source = list_make1($5);
+ m->actions = $8;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+merge_condition_action_list:
+ merge_condition_action
+ { $$ = list_make1($1); }
+ | merge_condition_action_list merge_condition_action
+ { $$ = lappend($1,$2); }
+ ;
+
+merge_condition_action:
+ WHEN opt_not MATCHED opt_and_condition THEN merge_action
+ {
+ MergeConditionAction *m = makeNode(MergeConditionAction);
+
+ m->match = $2;
+ m->condition = $4;
+ m->action = $6;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+
+opt_and_condition:
+ AND a_expr {$$ = $2;}
+ | /*EMPTY*/ {$$ = NULL;}
+ ;
+
+opt_not:
+ NOT {$$ = false;}
+ | /*EMPTY*/ {$$ = true;}
+ ;
+
+merge_action:
+ DELETE_P
+ {
+ $$ = (Node *)makeNode(MergeDelete);
+ }
+ | UPDATE SET set_clause_list
+ {
+ UpdateStmt *n = makeNode(MergeUpdate);
+ n->targetList = $3;
+ $$ = (Node *)n;
+ }
+ | INSERT values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = $2;
+
+ $$ = (Node *)n;
+ }
+
+ | INSERT '(' insert_column_list ')' values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = $3;
+ n->selectStmt = $5;
+
+ $$ = (Node *)n;
+ }
+ | INSERT DEFAULT VALUES
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = NULL;
+
+ $$ = (Node *)n;
+ }
+ | DO NOTHING
+ {
+ $$ = makeNode(MergeDoNothing);
+ }
+ | RAISE ERROR_P
+ {
+ $$ = makeNode(MergeError);
+ }
+ ;
+
+
+
+/*****************************************************************************
+ *
+ * QUERY:
* CURSOR STATEMENTS
*
*****************************************************************************/
@@ -10952,6 +11068,7 @@ unreserved_keyword:
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EXCLUDE
| EXCLUDING
@@ -11005,7 +11122,9 @@ unreserved_keyword:
| LOGIN_P
| MAPPING
| MATCH
+ | MATCHED
| MAXVALUE
+ | MERGE
| MINUTE_P
| MINVALUE
| MODE
@@ -11048,6 +11167,7 @@ unreserved_keyword:
| PROCEDURAL
| PROCEDURE
| QUOTE
+ | RAISE
| RANGE
| READ
| REASSIGN
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 16ca583..8f2ec6b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -214,6 +214,25 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
return rtindex;
}
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+
+ /* Close old target; this could only happen for multi-action rules */
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, NoLock);
+
+ /*
+ * Open target rel and grab suitable lock (which we will hold till end of
+ * transaction).
+ *
+ * free_parsestate() will eventually do the corresponding heap_close(),
+ * but *not* release the lock.
+ */
+ pstate->p_target_relation = parserOpenTable(pstate, relation,
+ RowExclusiveLock);
+}
+
/*
* Simplify InhOption (yes/no/default) into boolean yes/no.
*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..3ee0428 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1836,6 +1836,41 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
return rewritten;
}
+/*if the merge action type has already been processed by rewriter*/
+#define insert_rewrite (1<<0)
+#define delete_rewrite (1<<1)
+#define update_rewrite (1<<2)
+
+/*if the merge action type is fully replace by rules.*/
+#define insert_instead (1<<3)
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+#define merge_action_already_rewrite(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+ (acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+ (acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+#define set_action_rewrite(acttype, flag) \
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_rewrite;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_rewrite;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_rewrite;}
+
+#define merge_action_instead(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_instead)) || \
+ (acttype == CMD_UPDATE && (flag & update_instead)) || \
+ (acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_instead;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_instead;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_instead;}
/*
* QueryRewrite -
@@ -1861,7 +1896,151 @@ QueryRewrite(Query *parsetree)
*
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = RewriteQuery(parsetree, NIL);
+ if(parsetree->commandType == CMD_MERGE)
+ {
+ /*
+ *for merge query, we have a set of action queries (not subquery).
+ *each of these action queries should be applied to RewriteQuery().
+ */
+ ListCell *l;
+
+ int flag = 0;
+
+ List *pre_qry = NIL;
+ List *post_qry = NIL;
+
+
+ querylist = NIL;
+
+
+ /*rewrite the merge action queries one by one.*/
+ foreach(l, parsetree->mergeActQry)
+ {
+ List *queryList4action = NIL;
+ Query *actionqry;
+ Query *q;
+
+
+ actionqry = lfirst(l);
+
+ /*
+ * no rewriting for DO NOTHING or ERROR
+ */
+ if(actionqry->commandType == CMD_DONOTHING ||
+ actionqry->commandType == CMD_RAISEERR)
+ continue;
+
+
+ /*
+ *if this kind of actions are fully replaced by rules,
+ *we mark it as "replaced"
+ */
+ if(merge_action_instead(actionqry->commandType, flag))
+ {
+ /*
+ *Still need to call RewriteQuery(),
+ *since we need the process on target list and so on.
+ *BUT, the returned list is discarded
+ */
+ RewriteQuery(actionqry, NIL);
+ actionqry->replaced = true;
+ continue;
+ }
+
+
+ /*if this kind of actions are already processed by rewriter, skip it.*/
+ if(merge_action_already_rewrite(actionqry->commandType, flag))
+ {
+ RewriteQuery(actionqry, NIL);
+ continue;
+ }
+
+ /*ok this action has not been processed before, let's do it now.*/
+ queryList4action = RewriteQuery(actionqry, NIL);
+
+ /*this kind of actions has been processed, set the flag*/
+ set_action_rewrite(actionqry->commandType,flag);
+
+ /*if the returning list is nil, this merge action
+ is replaced by a do-nothing rule*/
+ if(queryList4action == NIL)
+ {
+ /*set the flag for other merge actions of the same type*/
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ continue;
+ }
+
+ /*
+ * if the rewriter return a non-NIL list, the merge action query
+ *could be one element in it.
+ *if so, it must be the head (for INSERT acton)
+ *or tail (for UPDATE/DELETE action).
+ */
+
+ /*test the list head*/
+ q = (Query *)linitial(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*
+ *the merge action is the head, the remaining part
+ *of the list are the queries generated by rules
+ *we put them in the post_qry list.
+ */
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+
+ queryList4action = list_delete_first(queryList4action);
+ post_qry = list_concat(post_qry,queryList4action);
+
+ continue;
+
+ }
+
+ /*test the list tail*/
+ q = (Query *)llast(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*the merge action is the tail.
+ Put the rule queries in pre_qry list*/
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+ queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+ pre_qry = list_concat(pre_qry,queryList4action);
+ continue;
+
+ }
+
+ /*here, the merge action query is not in the rewriten query list,
+ *which means the action is replaced by INSTEAD rule(s).
+ *We need to mark it as "replaced".
+
+ For a INSERT action, we put the rule queries in the post list
+ otherwise, in the pre list
+ */
+ if(actionqry->commandType == CMD_INSERT)
+ post_qry = list_concat(post_qry,queryList4action);
+ else
+ pre_qry = list_concat(pre_qry,queryList4action);
+
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ }
+
+ /*finally, put the 3 lists into one.
+ *If all the merge actions are replaced by rules,
+ *the original merge query
+ *will not be involved in the querylist.
+ */
+ querylist = list_concat(pre_qry,querylist);
+ querylist = list_concat(querylist, post_qry);
+
+ }
+ else
+ querylist = RewriteQuery(parsetree, NIL);
/*
* Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"DELETE %u", queryDesc->estate->es_processed);
break;
+ case CMD_MERGE:
+ snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+ "MERGE %u", queryDesc->estate->es_processed);
+ break;
default:
strcpy(completionTag, "???");
break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1815539..e0dc7c3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
+ case CMD_MERGE:
return false;
default:
elog(WARNING, "unrecognized commandType: %d",
@@ -1405,6 +1406,10 @@ CreateCommandTag(Node *parsetree)
tag = "SELECT";
break;
+ case T_MergeStmt:
+ tag = "MERGE";
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -2242,6 +2247,7 @@ GetCommandLogLevel(Node *parsetree)
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
+ case T_MergeStmt:
lev = LOGSTMT_MOD;
break;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 267a08e..51d0f11 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -164,6 +164,8 @@ extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
+extern void ExecBSMergeTriggers(ModifyTableState *mt_state);
+extern void ExecASMergeTriggers(ModifyTableState *mt_state);
extern void AfterTriggerBeginXact(void);
extern void AfterTriggerBeginQuery(void);
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..422e3ce 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+extern MergeActionState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7442d2d..64e20bb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1032,9 +1032,22 @@ typedef struct ModifyTableState
int mt_whichplan; /* which one is being executed (0..n-1) */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
+ List *mergeActPstates; /*the list of the planstate of meger command actions.
+ NIL if this is not a merge command.
+ The elements if it are still ModifyTableState nodes*/
} ModifyTableState;
/* ----------------
+ * MergeActionState information
+ * ----------------
+ */
+typedef struct MergeActionState
+{
+ PlanState ps; /* its first field is NodeTag */
+ CmdType operation;
+} MergeActionState;
+
+/* ----------------
* AppendState information
*
* nplans how many plans are in the array
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..a840349 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
T_Plan = 100,
T_Result,
T_ModifyTable,
+ T_MergeAction,
T_Append,
T_RecursiveUnion,
T_BitmapAnd,
@@ -86,6 +87,7 @@ typedef enum NodeTag
T_PlanState = 200,
T_ResultState,
T_ModifyTableState,
+ T_MergeActionState,
T_AppendState,
T_RecursiveUnionState,
T_BitmapAndState,
@@ -347,6 +349,13 @@ typedef enum NodeTag
T_AlterUserMappingStmt,
T_DropUserMappingStmt,
T_AlterTableSpaceOptionsStmt,
+ T_MergeStmt,
+ T_MergeConditionAction,
+ T_MergeUpdate,
+ T_MergeDelete,
+ T_MergeInsert,
+ T_MergeDoNothing,
+ T_MergeError,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -511,6 +520,9 @@ typedef enum CmdType
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
CMD_DELETE,
+ CMD_MERGE, /*merge stmt*/
+ CMD_DONOTHING,
+ CMD_RAISEERR,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
CMD_NOTHING /* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d31cf6c..ef5520f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -149,6 +149,13 @@ typedef struct Query
List *constraintDeps; /* a list of pg_constraint OIDs that the query
* depends on to be semantically valid */
+
+ /*the fileds for MERGE command*/
+ bool isMergeAction; /*if this query is a merge action. */
+ bool matched; /*this is a MATCHED action or NOT*/
+ bool replaced; /*is this merge action replaced by rules*/
+ List *mergeActQry; /* the list of all the merge actions.
+ * used only for merge query statment*/
} Query;
@@ -993,6 +1000,58 @@ typedef struct SelectStmt
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;
+/*The structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+ NodeTag type;
+ RangeVar *relation; /*targe relation for merge */
+
+ /* source relations for the merge.
+ *Currently, we only allwo single-source merge,
+ *so the length of this list should always be 1
+ */
+ List *source;
+ Node *matchCondition; /* qualifications of the merge*/
+
+ /*list of MergeConditionAction structure.
+ *It stores all the matched / not-matched
+ *conditions and the corresponding actions
+ *The elments of this list are MergeConditionAction
+ *nodes
+ */
+ List *actions;
+
+}MergeStmt;
+
+/* the structure for the actions of MERGE command.
+* Holds info of the clauses like
+* "WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+ NodeTag type;
+ bool match; /*match or not match*/
+ Node *condition;/*the AND condition for this action*/
+ Node *action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+/*
+* The merge action nodes are in fact the
+* ordinary nodes of UPDATE,DELETE and INSERT
+*/
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
+
+typedef struct MergeDoNothing
+{
+ NodeTag type;
+}MergeDoNothing;
+
+typedef struct MergeError
+{
+ NodeTag type;
+}MergeError;
/* ----------------------
* Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..a020051 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,9 +169,25 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *mergeActPlan; /*the plans for merge actions,
+ which are also ModifyTable nodes*/
} ModifyTable;
/* ----------------
+ * MergeAction node -
+ * The plan node for the actions of MERGE command
+ * ----------------
+ */
+typedef struct MergeAction
+{
+ Plan plan;
+ bool replaced; /*if this action is replaced by INSTEAD rules*/
+ CmdType operation;/* INSERT, UPDATE, or DELETE */
+ bool matched;
+ List *flattenedqual; /*the flattened qual expression of action*/
+}MergeAction;
+
+/* ----------------
* Append node -
* Generate the concatenation of the results of sub-plans.
* ----------------
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index b0e04a0..4d6c9e8 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -15,6 +15,7 @@
#define VAR_H
#include "nodes/relation.h"
+#include "nodes/plannodes.h"
typedef enum
{
@@ -32,5 +33,5 @@ extern int locate_var_of_relation(Node *node, int relid, int levelsup);
extern int find_minimum_var_level(Node *node);
extern List *pull_var_clause(Node *node, PVCPlaceHolderBehavior behavior);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
-
+extern void push_up_merge_action_vars(MergeAction * actplan,Query * actqry);
#endif /* VAR_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 974bb7a..208c3e4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -141,6 +141,7 @@ PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD)
PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
@@ -229,7 +230,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
@@ -296,6 +299,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
+PG_KEYWORD("raise", RAISE, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..b54f530 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,7 @@
extern void transformFromClause(ParseState *pstate, List *frmList);
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
extern bool interpretInhOption(InhOption inhOpt);
extern bool interpretOidsOption(List *defList);
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
new file mode 100644
index 0000000..18e3891
--- /dev/null
+++ b/src/test/regress/expected/merge.out
@@ -0,0 +1,279 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 20
+ 4 | 40
+(3 rows)
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 25
+ 3 | 50
+(3 rows)
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ 4 | 40
+(4 rows)
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 25
+ 3 | 50
+ 4 | 40
+(4 rows)
+
+ROLLBACK;
+--
+-- Non-standard functionality
+--
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ DELETE
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+(1 row)
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 3 | 50
+ 4 | 40
+(3 rows)
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 5
+ 3 | 20
+ 4 | 40
+(4 rows)
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+-- fails according to standard
+-- fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ERROR: multiple actions on single target row
+
+ROLLBACK;
+
+-- This next SQL statement
+-- fails according to standard
+-- suceeds in PostgreSQL implementation by simply ignoring the second
+-- matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 20
+ 4 | 5
+ 4 | 40
+(4 rows)
+
+-- This next SQL statement
+-- suceeds according to standard (yes, it is inconsistent)
+-- suceeds in PostgreSQL implementation, though could easily fail if
+-- there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ 4 | 5
+ 4 | 40
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ |
+ |
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+ DO NOTHING
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+ INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+ UPDATE SET balance = t.balance + s.balance
+;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 191d1fe..2551b2a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml merge
# run stats by itself because its delay may be insufficient under heavy load
test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 80a9881..e7d7fae 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -123,4 +123,5 @@ test: returning
test: largeobject
test: with
test: xml
+test: merge
test: stats
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
new file mode 100644
index 0000000..7ecd02e
--- /dev/null
+++ b/src/test/regress/sql/merge.sql
@@ -0,0 +1,200 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Non-standard functionality
+--
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ DELETE
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+-- fails according to standard
+-- fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ERROR: multiple actions on single target row
+
+ROLLBACK;
+
+-- This next SQL statement
+-- fails according to standard
+-- suceeds in PostgreSQL implementation by simply ignoring the second
+-- matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+
+-- This next SQL statement
+-- suceeds according to standard (yes, it is inconsistent)
+-- suceeds in PostgreSQL implementation, though could easily fail if
+-- there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+ DO NOTHING
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+ INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+ UPDATE SET balance = t.balance + s.balance
+;
On 10/08/10 06:03, Boxuan Zhai wrote:
I have put everything in one patch, against the latest git repository. The
program is tested on my machine.
Thanks! I get a few compiler warnings:
analyze.c: In function �transformMergeStmt�:
analyze.c:2476: warning: unused variable �lastaction�
gram.y: In function �base_yyparse�:
gram.y:7437: warning: assignment from incompatible pointer type
gram.y:7441: warning: assignment from incompatible pointer type
trigger.c: In function �ExecBSMergeTriggers�:
trigger.c:2360: warning: assignment from incompatible pointer type
trigger.c: In function �ExecASMergeTriggers�:
trigger.c:2411: warning: assignment from incompatible pointer type
planner.c: In function �merge_action_planner�:
planner.c:681: warning: assignment from incompatible pointer type
var.c: In function �push_up_merge_action_vars�:
var.c:738: warning: passing argument 1 of
�push_up_merge_action_vars_walker� from incompatible pointer type
var.c:96: note: expected �struct Node *� but argument is of type �struct
List *�
var.c:740: warning: passing argument 1 of
�push_up_merge_action_vars_walker� from incompatible pointer type
var.c:96: note: expected �struct Node *� but argument is of type �struct
List *�
The merge.sgml file should be in doc/src/sgml/ref, not doc/src/sgml.
After moving it there, I get a few errors from compiling the docs:
openjade:ref/merge.sgml:128:55:X: reference to non-existent ID
"SQL-SELECT-TITLE"
openjade:ref/merge.sgml:129:55:X: reference to non-existent ID
"SQL-VALUES-TITLE"
openjade:ref/merge.sgml:185:42:X: reference to non-existent ID
"SQL-INSERT-TITLE"
openjade:ref/merge.sgml:170:42:X: reference to non-existent ID
"SQL-UPDATE-TITLE"
Those can be fixed by simply removing the endterm attributes from those
lines, they're not needed.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Tue, Aug 10, 2010 at 2:49 PM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:
On 10/08/10 06:03, Boxuan Zhai wrote:
I have put everything in one patch, against the latest git repository. The
program is tested on my machine.Thanks! I get a few compiler warnings:
analyze.c: In function ‘transformMergeStmt’:
analyze.c:2476: warning: unused variable ‘lastaction’
gram.y: In function ‘base_yyparse’:
gram.y:7437: warning: assignment from incompatible pointer type
gram.y:7441: warning: assignment from incompatible pointer type
trigger.c: In function ‘ExecBSMergeTriggers’:
trigger.c:2360: warning: assignment from incompatible pointer type
trigger.c: In function ‘ExecASMergeTriggers’:
trigger.c:2411: warning: assignment from incompatible pointer type
planner.c: In function ‘merge_action_planner’:
planner.c:681: warning: assignment from incompatible pointer type
var.c: In function ‘push_up_merge_action_vars’:
var.c:738: warning: passing argument 1 of
‘push_up_merge_action_vars_walker’ from incompatible pointer type
var.c:96: note: expected ‘struct Node *’ but argument is of type ‘struct
List *’
var.c:740: warning: passing argument 1 of
‘push_up_merge_action_vars_walker’ from incompatible pointer type
var.c:96: note: expected ‘struct Node *’ but argument is of type ‘struct
List *’The merge.sgml file should be in doc/src/sgml/ref, not doc/src/sgml. After
moving it there, I get a few errors from compiling the docs:openjade:ref/merge.sgml:128:55:X: reference to non-existent ID
"SQL-SELECT-TITLE"
openjade:ref/merge.sgml:129:55:X: reference to non-existent ID
"SQL-VALUES-TITLE"
openjade:ref/merge.sgml:185:42:X: reference to non-existent ID
"SQL-INSERT-TITLE"
openjade:ref/merge.sgml:170:42:X: reference to non-existent ID
"SQL-UPDATE-TITLE"Those can be fixed by simply removing the endterm attributes from those
lines, they're not needed.
Thanks for your feedback. I fixed all the above waring bugs. Find the new
patch in attachement.
Show quoted text
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
merge_no_warning.tarapplication/x-tar; name=merge_no_warning.tarDownload
final_merge_no_warning.patch 0000644 0000764 0001040 00000300764 11430213026 017640 0 ustar bxzhai Administrators diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 7518c84..3aa9aab 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -119,6 +119,7 @@ Complete list of usable sgml source files in this directory.
<!entity listen system "listen.sgml">
<!entity load system "load.sgml">
<!entity lock system "lock.sgml">
+<!entity merge system "merge.sgml">
<!entity move system "move.sgml">
<!entity notify system "notify.sgml">
<!entity prepare system "prepare.sgml">
diff --git a/doc/src/sgml/ref/merge.sgml b/doc/src/sgml/ref/merge.sgml
new file mode 100644
index 0000000..a4a0849
--- /dev/null
+++ b/doc/src/sgml/ref/merge.sgml
@@ -0,0 +1,409 @@
+<!--
+$PostgreSQL$
+-->
+
+<refentry id="SQL-MERGE">
+ <refmeta>
+ <refentrytitle id="SQL-MERGE-TITLE">MERGE</refentrytitle>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>MERGE</refname>
+ <refpurpose>update, insert or delete rows of a table based upon source data</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-merge">
+ <primary>MERGE</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+MERGE INTO <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
+USING <replaceable class="PARAMETER">source-query</replaceable>
+ON <replaceable class="PARAMETER">join_condition</replaceable>
+[<replaceable class="PARAMETER">when_clause</replaceable> [...]]
+
+where <replaceable class="PARAMETER">when_clause</replaceable> is
+
+{ WHEN MATCHED [ AND <replaceable class="PARAMETER">condition</replaceable> ] THEN { <replaceable class="PARAMETER">merge_update</replaceable> | DELETE | DO NOTHING | RAISE ERROR}
+ WHEN NOT MATCHED [ AND <replaceable class="PARAMETER">condition</replaceable> ] THEN { <replaceable class="PARAMETER">merge_insert</replaceable> | DO NOTHING | RAISE ERROR} }
+
+where <replaceable class="PARAMETER">merge_update</replaceable> is
+
+UPDATE SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
+
+and <replaceable class="PARAMETER">merge_insert</replaceable> is
+
+INSERT [( <replaceable class="PARAMETER">column</replaceable> [, ...] )] { VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) | DEFAULT VALUES }
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>MERGE</command> performs at most one action on each row from
+ the target table, driven by the rows from the source query. This
+ provides a way to specify a single SQL statement that can conditionally
+ <command>UPDATE</command> or <command>INSERT</command> rows, a task
+ that would otherwise require multiple procedural language statements.
+ </para>
+
+ <para>
+ First, the <command>MERGE</command> command performs a left outer join
+ from source query to target table, producing zero or more merged rows. For
+ each merged row, <literal>WHEN</> clauses are evaluated in the
+ specified order until one of them is activated. The corresponding action
+ is then applied and processing continues for the next row.
+ </para>
+
+ <para>
+ <command>MERGE</command> actions have the same effect as
+ regular <command>UPDATE</command>, <command>INSERT</command>, or
+ <command>DELETE</command> commands of the same names, though the syntax
+ is slightly different.
+ </para>
+
+ <para>
+ If no <literal>WHEN</> clause activates then an implicit action of
+ <literal>RAISE ERROR</> is performed for that row. If that
+ implicit action is not desirable an explicit action of
+ <literal>DO NOTHING</> may be specified instead.
+ </para>
+
+ <para>
+ <command>MERGE</command> will only affect rows only in the specified table.
+ </para>
+
+ <para>
+ There is no <literal>RETURNING</> clause with <command>MERGE</command>.
+ </para>
+
+ <para>
+ There is no MERGE privilege.
+ You must have the <literal>UPDATE</literal> privilege on the table
+ if you specify an update action, the <literal>INSERT</literal> privilege if
+ you specify an insert action and/or the <literal>DELETE</literal> privilege
+ if you wish to delete. You will also require the
+ <literal>SELECT</literal> privilege to any table whose values are read
+ in the <replaceable class="parameter">expressions</replaceable> or
+ <replaceable class="parameter">condition</replaceable>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the table to merge into.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">alias</replaceable></term>
+ <listitem>
+ <para>
+ A substitute name for the target table. When an alias is
+ provided, it completely hides the actual name of the table. For
+ example, given <literal>MERGE foo AS f</>, the remainder of the
+ <command>MERGE</command> statement must refer to this table as
+ <literal>f</> not <literal>foo</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">source-query</replaceable></term>
+ <listitem>
+ <para>
+ A query (<command>SELECT</command> statement or <command>VALUES</command>
+ statement) that supplies the rows to be merged into the target table.
+ Refer to the <xref linkend="sql-select">
+ statement or <xref linkend="sql-values">
+ statement for a description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">join_condition</replaceable></term>
+ <listitem>
+ <para>
+ <replaceable class="parameter">join_condition</replaceable> is
+ an expression resulting in a value of type
+ <type>boolean</type> (similar to a <literal>WHERE</literal>
+ clause) that specifies which rows in the join are considered to
+ match. You should ensure that the join produces at most one output
+ row for each row to be modified. An attempt to modify any row of the
+ target table more than once will result in an error. This behaviour
+ requires the user to take greater care in using <command>MERGE</command>,
+ though is required explicitly by the SQL Standard.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">condition</replaceable></term>
+ <listitem>
+ <para>
+ An expression that returns a value of type <type>boolean</type>.
+ If this expression returns <literal>true</> then the <literal>WHEN</>
+ clause will be activated and the corresponding action will occur for
+ that row.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">merge_update</replaceable></term>
+ <listitem>
+ <para>
+ The specification of an <literal>UPDATE</> action. Do not include
+ the table name, as you would normally do with an
+ <xref linkend="sql-update"> command.
+ For example, <literal>UPDATE tab SET col = 1</> is invalid. Also,
+ do not include a <literal>WHERE</> clause, since only the current
+ can be updated. For example,
+ <literal>UPDATE SET col = 1 WHERE key = 57</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">merge_insert</replaceable></term>
+ <listitem>
+ <para>
+ The specification of an <literal>INSERT</> action. Do not include
+ the table name, as you would normally do with an
+ <xref linkend="sql-insert"> command.
+ For example, <literal>INSERT INTO tab VALUES (1, 50)</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">column</replaceable></term>
+ <listitem>
+ <para>
+ The name of a column in <replaceable
+ class="PARAMETER">table</replaceable>.
+ The column name can be qualified with a subfield name or array
+ subscript, if needed. Do not include the table's name in the
+ specification of a target column — for example,
+ <literal>UPDATE SET tab.col = 1</> is invalid.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">expression</replaceable></term>
+ <listitem>
+ <para>
+ An expression to assign to the column. The expression can use the
+ old values of this and other columns in the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DEFAULT</literal></term>
+ <listitem>
+ <para>
+ Set the column to its default value (which will be NULL if no
+ specific default expression has been assigned to it).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Outputs</title>
+
+ <para>
+ On successful completion, a <command>MERGE</> command returns a command
+ tag of the form
+<screen>
+MERGE <replaceable class="parameter">total-count</replaceable>
+</screen>
+ The <replaceable class="parameter">total-count</replaceable> is the number
+ of rows changed (either updated, inserted or deleted).
+ If <replaceable class="parameter">total-count</replaceable> is 0, no rows
+ were changed (this is not considered an error).
+ </para>
+
+ <para>
+ The number of rows updated, inserted or deleted is not available as part
+ of the command tag. An optional NOTIFY message can be generated to
+ present this information, if desired.
+<screen>
+NOTIFY: 34 rows processed: 11 updated, 5 deleted, 15 inserted, 3 default inserts, 0 no action
+</screen>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ What essentially happens is that the target table is left outer-joined to
+ the tables mentioned in the <replaceable>source-query</replaceable>, and
+ each output row of the join may then activate at most one when-clause.
+ The row will be matched only once per statement, so the status of
+ <literal>MATCHED</> or <literal>NOT MATCHED</> cannot change once testing
+ of <literal>WHEN</> clauses has begun. <command>MERGE</command> will not
+ invoke Rules.
+ </para>
+
+ <para>
+ The following steps take place during the execution of
+ <command>MERGE</command>.
+ <orderedlist>
+ <listitem>
+ <para>
+ Perform any BEFORE STATEMENT triggers for actions specified, whether or
+ not they actually occur.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform left outer join from source to target table. Then for each row:
+ <orderedlist>
+ <listitem>
+ <para>
+ Evaluate whether each row is MATCHED or NOT MATCHED.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Test each WHEN condition in the order specified until one activates.
+ Identify the action and its event type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any BEFORE ROW triggers that fire for the action's event type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Apply the action specified.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any AFTER ROW triggers that fire for the action's event type.
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any AFTER STATEMENT triggers for actions specified, whether or
+ not they actually occur.
+ </para>
+ </listitem>
+ </orderedlist>
+ In summary, statement triggers for an event type (say, INSERT) will
+ be fired whenever we <emphasis>specify</> an action of that kind. Row-level
+ triggers will fire only for event type <emphasis>activated</>.
+ So a <command>MERGE</command> might fire statement triggers for both
+ <literal>UPDATE</> and <literal>INSERT</>, even though only
+ <literal>UPDATE</> row triggers were fired.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Attempt to insert a new stock item along with the quantity of stock. If
+ the item already exists, instead update the stock count of the existing
+ item.
+<programlisting>
+MERGE INTO wines w
+USING (VALUES('Chateau Lafite 2003', '24')) v
+ON v.column1 = w.winename
+WHEN NOT MATCHED THEN
+ INSERT VALUES(v.column1, v.column2)
+WHEN MATCHED THEN
+ UPDATE SET stock = stock + v.column2;
+</programlisting>
+ </para>
+
+ <para>
+ Perform maintenance on CustomerAccounts based upon new Transactions.
+ The following statement will fail if any accounts have had more than
+ one transaction
+
+<programlisting>
+MERGE CustomerAccount CA
+
+USING (SELECT CustomerId, TransactionValue,
+ FROM Transactions
+ WHERE TransactionId > 35345678) AS T
+
+ON T.CustomerId = CA.CustomerId
+
+WHEN MATCHED THEN
+ UPDATE SET Balance = Balance - TransactionValue
+
+WHEN NOT MATCHED THEN
+ INSERT (CustomerId, Balance)
+ VALUES (T.CustomerId, T.TransactionValue)
+;
+</programlisting>
+
+ so the right way to do this is to pre-aggregate the data
+
+<programlisting>
+MERGE CustomerAccount CA
+
+USING (SELECT CustomerId, Sum(TransactionValue) As TransactionSum
+ FROM Transactions
+ WHERE TransactionId > 35345678
+ GROUP BY CustomerId) AS T
+
+ON T.CustomerId = CA.CustomerId
+
+WHEN MATCHED THEN
+ UPDATE SET Balance = Balance - TransactionSum
+
+WHEN NOT MATCHED THEN
+ INSERT (CustomerId, Balance)
+ VALUES (T.CustomerId, T.TransactionSum)
+;
+</programlisting>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ This command conforms to the <acronym>SQL</acronym> standard, except
+ that the <literal>DELETE</literal> and <literal>DO NOTHING</> actions
+ are <productname>PostgreSQL</productname> extensions.
+ </para>
+
+ <para>
+ According to the standard, the column-list syntax for an <literal>UPDATE</>
+ action should allow a list of columns to be assigned from a single
+ row-valued expression.
+ This is not currently implemented — the source must be a list
+ of independent expressions.
+ </para>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 5268794..677390c 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -327,6 +327,9 @@ UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- continue with other operations, and eventually
COMMIT;
</programlisting>
+
+ This operation can be executed in a single statement using
+ <xref linkend="sql-merge" endterm="sql-merge-title">.
</para>
<para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index c33d883..5068235 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -147,6 +147,7 @@
&listen;
&load;
&lock;
+ &merge;
&move;
¬ify;
&prepare;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..1a9e39a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -73,6 +73,8 @@ static void show_sort_keys(SortState *sortstate, List *ancestors,
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
+static void ExplainMergeActions(ModifyTableState *mt_planstate,
+ List *ancestors, ExplainState *es);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
@@ -636,6 +638,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case CMD_DELETE:
pname = operation = "Delete";
break;
+ case CMD_MERGE:
+ pname = operation = "Merge";
+ break;
default:
pname = "???";
break;
@@ -1190,6 +1195,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
switch (nodeTag(plan))
{
case T_ModifyTable:
+ ExplainMergeActions((ModifyTableState *)planstate, ancestors, es);
+
ExplainMemberNodes(((ModifyTable *) plan)->plans,
((ModifyTableState *) planstate)->mt_plans,
ancestors, es);
@@ -1482,6 +1489,64 @@ explain_get_index_name(Oid indexId)
return result;
}
+static void
+ExplainMergeActions(ModifyTableState *mt_planstate, List *ancestors, ExplainState *es)
+{
+ ListCell *l;
+ StringInfo buf = makeStringInfo();
+
+ if(mt_planstate->operation != CMD_MERGE || mt_planstate->mergeActPstates == NIL)
+ return;
+
+ foreach(l,mt_planstate->mergeActPstates)
+ {
+ ModifyTableState *mt_state = (ModifyTableState *)lfirst(l);
+
+ MergeActionState *act_pstate = (MergeActionState *)mt_state->mt_plans[0];
+
+ MergeAction *act_plan = (MergeAction *)act_pstate->ps.plan;
+
+ resetStringInfo(buf);
+
+ /*prepare the string for printing*/
+ switch(act_pstate->operation)
+ {
+ case CMD_INSERT:
+ appendStringInfoString(buf, "INSERT WHEN ");
+ break;
+ case CMD_UPDATE:
+ appendStringInfoString(buf, "UPDATE WHEN ");
+ break;
+ case CMD_DELETE:
+ appendStringInfoString(buf, "DELETE WHEN ");
+ break;
+ case CMD_DONOTHING:
+ appendStringInfoString(buf, "DO NOTHING WHEN ");
+ break;
+ case CMD_RAISEERR:
+ appendStringInfoString(buf, "RAISE ERROR WHEN ");
+ break;
+ default:
+ elog(ERROR, "unknown merge action type when explain");
+ }
+
+ if(act_plan->matched)
+ appendStringInfoString(buf, "MATCHED ");
+ else
+ appendStringInfoString(buf, "NOT MATCHED ");
+
+ if(act_plan->flattenedqual)
+ appendStringInfoString(buf, "AND ");
+
+ /*print it*/
+ ExplainPropertyText("ACTION", buf->data, es);
+
+ show_qual(act_plan->flattenedqual, " qual", &act_pstate->ps, ancestors, true, es);
+
+ }
+
+}
+
/*
* Show the target of a Scan node
*/
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8b017ae..cbf59df 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2342,6 +2342,107 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
false, NULL, NULL, NIL, NULL);
}
+void
+ExecBSMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTableState *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecBSUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecBSInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecBSDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
+
+void
+ExecASMergeTriggers(ModifyTableState *mt_state)
+{
+ ListCell *l;
+
+ bool doUpdateTriggers = false;
+ bool doInsertTriggers = false;
+ bool doDeleteTriggers = false;
+
+ foreach(l, mt_state->mergeActPstates)
+ {
+ ModifyTableState *actmtstate;
+ MergeActionState *actPstate;
+ MergeAction *actplan;
+
+ actmtstate = (ModifyTableState *)lfirst(l);
+
+ actPstate = (MergeActionState *)actmtstate->mt_plans[0];
+
+ actplan = (MergeAction *)actPstate->ps.plan;
+ /*the replace action does not fire triggers*/
+ if(actplan->replaced)
+ continue;
+
+ if(actplan->operation == CMD_UPDATE)
+ doUpdateTriggers = true;
+ else if(actplan->operation == CMD_INSERT)
+ doInsertTriggers = true;
+ else if(actplan->operation == CMD_DELETE)
+ doDeleteTriggers = true;
+
+ }
+
+ /*fire the triggers*/
+ if(doUpdateTriggers)
+ ExecASUpdateTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doInsertTriggers)
+ ExecASInsertTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+
+ if(doDeleteTriggers)
+ ExecASDeleteTriggers(mt_state->ps.state,
+ mt_state->ps.state->es_result_relations);
+
+ return;
+}
static HeapTuple
GetTupleForTrigger(EState *estate,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b34a154..3a943a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -170,6 +170,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
+ case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_MergeAction:
+ result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+ estate, eflags);
+ break;
+
case T_Append:
result = (PlanState *) ExecInitAppend((Append *) node,
estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..8ab81ae 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -582,6 +582,143 @@ lreplace:;
return NULL;
}
+static TupleTableSlot *
+MergeRaiseErr(void)
+{
+ elog(NOTICE, "one tuple is ERROR");
+ return NULL;
+}
+
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot,
+ ModifyTableState *node,
+ EState *estate)
+{
+
+ TupleTableSlot *actslot = NULL;
+ TupleTableSlot *res = NULL;
+ ListCell *each;
+
+ /*
+ * try the merge actions one by one
+ */
+ foreach(each, node->mergeActPstates)
+ {
+ ModifyTableState *mt_pstate;
+
+ MergeActionState *action_pstate;
+
+ ExprContext *econtext;
+
+ bool matched;
+
+
+ mt_pstate = (ModifyTableState *)lfirst(each);
+
+ /*
+ * mt_pstate is supposed to have only ONE mt_plans,
+ * which is a MergeActionState
+ */
+ Assert(mt_pstate->mt_nplans == 1);
+
+ action_pstate = (MergeActionState *)mt_pstate->mt_plans[0];
+
+ matched = ((MergeAction *)action_pstate->ps.plan)->matched;
+
+
+ /*
+ * If tupleid == NULL, it is a NOT MATCHED case,
+ * else, it is a MATCHED case,
+ */
+ if((tupleid == NULL && matched) || (tupleid != NULL && !matched))
+ {
+ continue;
+ }
+
+ /*Setup the expression context*/
+ econtext = action_pstate->ps.ps_ExprContext;
+
+ /*
+ If the action has an additional qual,
+ which is not satisfied, skip it
+ */
+ if(action_pstate->ps.qual)
+ {
+ ResetExprContext(econtext);
+
+ econtext->ecxt_scantuple = slot;
+ econtext->ecxt_outertuple = planSlot;
+
+ if(!ExecQual(action_pstate->ps.qual, econtext,false))
+ {
+ continue;
+ }
+ }
+
+
+ /*
+ * OK, the input tuple is caugth by current action.
+ * If this action is "replaced" by rules, we will skip it
+ * AND THE REMAINING ACTIONS.
+ */
+ Assert(IsA(action_pstate->ps.plan, MergeAction));
+ if(((MergeAction *)action_pstate->ps.plan)->replaced)
+ return NULL;
+
+
+ /*Now we start to exec this action.
+ We have 5 action types*/
+
+ /*1. do nothing for a DO NOTHING action*/
+ if(action_pstate->operation == CMD_DONOTHING)
+ return NULL;
+
+ /*2. throw an error for a RAISE ERROR action*/
+ if(action_pstate->operation == CMD_RAISEERR)
+ return MergeRaiseErr();
+
+ /*3. project the result tuple slot, for INSERT/UPDATE action*/
+ if(action_pstate->operation != CMD_DELETE)
+ actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo,
+ slot, planSlot);
+
+ switch (action_pstate->operation)
+ {
+ case CMD_INSERT:
+ res = ExecInsert(actslot, planSlot, estate);
+ return res;
+ break;
+ case CMD_UPDATE:
+ res = ExecUpdate(tupleid,
+ actslot,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ case CMD_DELETE:
+ res = ExecDelete(tupleid,
+ planSlot,
+ &mt_pstate->mt_epqstate,
+ estate);
+ return res;
+ break;
+ default:
+ elog(ERROR, "unknown merge action type for excute");
+ break;
+ }
+
+ }
+
+ /*
+ * Here, no action is taken. Let's do the default thing,
+ * which is Raise Error in crrent edition
+ */
+ return MergeRaiseErr();
+
+}
/*
* Process BEFORE EACH STATEMENT triggers
@@ -603,6 +740,9 @@ fireBSTriggers(ModifyTableState *node)
ExecBSDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecBSMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -629,6 +769,9 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state,
node->ps.state->es_result_relations);
break;
+ case CMD_MERGE:
+ ExecASMergeTriggers(node);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -708,20 +851,34 @@ ExecModifyTable(ModifyTableState *node)
/*
* extract the 'ctid' junk attribute.
*/
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
Datum datum;
bool isNull;
datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
&isNull);
- /* shouldn't ever get a null result... */
+
if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
- tupleid = &tuple_ctid;
+ {
+ /*
+ * shouldn't ever get a null result for update and delete.
+ * Merge command will get a null ctid in "NOT MATCHED" case
+ */
+ if(operation != CMD_MERGE)
+ elog(ERROR, "ctid is NULL");
+ else
+ tupleid = NULL;
+ }
+ else
+ {
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+
+ tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
+ tupleid = &tuple_ctid;
+
+ }
}
/*
@@ -744,6 +901,10 @@ ExecModifyTable(ModifyTableState *node)
slot = ExecDelete(tupleid, planSlot,
&node->mt_epqstate, estate);
break;
+ case CMD_MERGE:
+ slot = ExecMerge(tupleid, slot, planSlot,
+ node, estate);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -771,6 +932,74 @@ ExecModifyTable(ModifyTableState *node)
return NULL;
}
+/*
+* When init a merge plan, we also need init its action plans.
+* These action plans are "MergeAction" plans .
+*
+* This function mainly handles the tlist and qual in the plan.
+* The returning result is a "MergeActionState".
+*/
+MergeActionState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+ MergeActionState *result;
+
+ /*
+ * do nothing when we get to the end of a leaf on tree.
+ */
+ if (node == NULL)
+ return NULL;
+
+ /*
+ * create state structure
+ */
+ result = makeNode(MergeActionState);
+ result->operation = node->operation;
+ result->ps.plan = (Plan *)node;
+ result->ps.state = estate;
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &result->ps);
+
+ /*
+ * initialize tuple type
+ */
+ ExecAssignResultTypeFromTL(&result->ps);
+
+
+ /*
+ * create expression context for node
+ */
+
+ ExecAssignExprContext(estate, &result->ps);
+
+
+ /*
+ * initialize child expressions
+ */
+ result->ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->plan.targetlist, &result->ps);
+
+
+ result->ps.qual = (List *)
+ ExecInitExpr((Expr *) node->plan.qual, &result->ps);
+
+
+ /*
+ * init the projection information
+ */
+ ExecAssignProjectionInfo(&result->ps, NULL);
+
+ /*
+ do we need a check for the plan output here ?
+ (by calling the ExecCheckPlanOutput() function
+ */
+
+ return result;
+}
+
/* ----------------------------------------------------------------
* ExecInitModifyTable
* ----------------------------------------------------------------
@@ -786,6 +1015,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ bool isMergeAction = false;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -826,6 +1056,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
+
+ /*
+ * test if this subplan node is a MergeAction.
+ * We need this information for setting the junckfilter.
+ * juckfiler is necessary for an ordinary UPDATE/DELETE plan,
+ * but not for an UPDATE/DELETE merge action
+ */
+ if(IsA(subplan, MergeAction))
+ isMergeAction = true;
+
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
estate->es_result_relation_info++;
i++;
@@ -955,7 +1195,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
- junk_filter_needed = true;
+ case CMD_MERGE:
+ if(!isMergeAction)
+ junk_filter_needed = true;
+ break;
+ case CMD_DONOTHING:
+ case CMD_RAISEERR:
break;
default:
elog(ERROR, "unknown operation");
@@ -978,9 +1223,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
ExecInitExtraTupleSlot(estate));
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
{
- /* For UPDATE/DELETE, find the ctid junk attr now */
+ /* For UPDATE/DELETE/MERGE, find the ctid junk attr now */
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
@@ -1006,6 +1251,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (estate->es_trig_tuple_slot == NULL)
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ /*
+ * for the merge actions, we need to do similar things as above
+ */
+ foreach(l, node->mergeActPlan)
+ {
+ PlanState *actpstate = ExecInitNode((Plan *)lfirst(l), estate, 0);
+ /*
+ * put the pstates of each action into ModifyTableState
+ */
+ mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+
+ }
+
return mtstate;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 69262d6..1379686 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
+ COPY_NODE_FIELD(mergeActPlan);
return newnode;
}
@@ -2274,6 +2275,11 @@ _copyQuery(Query *from)
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
+ COPY_SCALAR_FIELD(isMergeAction);
+ COPY_SCALAR_FIELD(replaced);
+ /*merge action list*/
+ COPY_NODE_FIELD(mergeActQry);
+
return newnode;
}
@@ -2344,6 +2350,59 @@ _copySelectStmt(SelectStmt *from)
return newnode;
}
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+ MergeStmt *newnode = makeNode(MergeStmt);
+
+ COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(source);
+ COPY_NODE_FIELD(matchCondition);
+ COPY_NODE_FIELD(actions);
+
+ return newnode;
+
+}
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+ MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+ COPY_SCALAR_FIELD(match);
+ COPY_NODE_FIELD(condition);
+ COPY_NODE_FIELD(action);
+
+ return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+ MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+ newNode->type = T_MergeUpdate;
+
+ return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+ MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+ newNode->type = T_MergeInsert;
+
+ return newNode;
+}
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+ MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+ newNode->type = T_MergeDelete;
+
+ return newNode;
+}
+
static SetOperationStmt *
_copySetOperationStmt(SetOperationStmt *from)
{
@@ -3905,6 +3964,21 @@ copyObject(void *from)
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
+ case T_MergeStmt:
+ retval = _copyMergeStmt(from);
+ break;
+ case T_MergeConditionAction:
+ retval = _copyMergeConditionAction(from);
+ break;
+ case T_MergeUpdate:
+ retval = _copyMergeUpdate(from);
+ break;
+ case T_MergeInsert:
+ retval = _copyMergeInsert(from);
+ break;
+ case T_MergeDelete:
+ retval = _copyMergeDelete(from);
+ break;
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 667057b..f726a48 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -879,6 +879,9 @@ _equalQuery(Query *a, Query *b)
COMPARE_NODE_FIELD(setOperations);
COMPARE_NODE_FIELD(constraintDeps);
+ COMPARE_SCALAR_FIELD(isMergeAction);
+ COMPARE_SCALAR_FIELD(replaced);
+ COMPARE_NODE_FIELD(mergeActQry);
return true;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 04a6647..c88d422 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
+ WRITE_NODE_FIELD(mergeActPlan);
}
static void
@@ -2021,6 +2022,53 @@ _outQuery(StringInfo str, Query *node)
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
+ WRITE_BOOL_FIELD(isMergeAction);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_NODE_FIELD(mergeActQry);
+}
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+ WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+ WRITE_BOOL_FIELD(match);
+
+ WRITE_NODE_FIELD(condition);
+ WRITE_NODE_FIELD(action);
+}
+
+static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+ WRITE_NODE_TYPE("MERGESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(source);
+ WRITE_NODE_FIELD(matchCondition);
+ WRITE_NODE_FIELD(actions);
+}
+
+static void
+_outMergeAction(StringInfo str, MergeAction*node)
+{
+ _outPlanInfo(str, (Plan *)node);
+ WRITE_BOOL_FIELD(replaced);
+ WRITE_ENUM_FIELD(operation, CmdType);
+ WRITE_BOOL_FIELD(matched);
+ WRITE_NODE_FIELD(flattenedqual);
+}
+
+static void
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+ WRITE_NODE_TYPE("DELETESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(usingClause);
+ WRITE_NODE_FIELD(whereClause);
+ WRITE_NODE_FIELD(returningList);
}
static void
@@ -2906,6 +2954,18 @@ _outNode(StringInfo str, void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_MergeAction:
+ _outMergeAction(str, obj);
+ break;
+ case T_MergeStmt:
+ _outMergeStmt(str, obj);
+ break;
+ case T_MergeConditionAction:
+ _outMergeConditionAction(str,obj);
+ break;
+ case T_DeleteStmt:
+ _outDeleteStmt(str,obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0a2edcb..a8581a0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -219,6 +219,10 @@ _readQuery(void)
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
+ READ_BOOL_FIELD(isMergeAction);
+ READ_BOOL_FIELD(matched);
+ READ_BOOL_FIELD(replaced);
+ READ_NODE_FIELD(mergeActQry);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..c36572e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -102,7 +102,12 @@ static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
int *ordNumCols,
AttrNumber **ordColIdx,
Oid **ordOperators);
-
+static ModifyTable *merge_action_planner(PlannerGlobal *glob,
+ Query *parse,
+ Plan *top_plan);
+static void merge_action_list_planner(PlannerGlobal *glob,
+ Query *parse,
+ ModifyTable *mainplan);
/*****************************************************************************
*
@@ -565,6 +570,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
returningLists,
rowMarks,
SS_assign_special_param(root));
+
+ /*do a simple plan for each actions in the merge command.
+ *put them in mergeActPlan list;
+ */
+ merge_action_list_planner(glob, parse, (ModifyTable *)plan);
}
}
@@ -584,6 +594,137 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
return plan;
}
+static void
+merge_action_list_planner(PlannerGlobal *glob, Query *parse, ModifyTable *mainplan)
+{
+ ListCell *l;
+
+ /*this is a function for MERGE command only*/
+ if(parse->commandType != CMD_MERGE ||
+ mainplan->operation != CMD_MERGE)
+ return;
+
+ /*if the merge actions are already there, no need to do it again*/
+ if(mainplan->mergeActPlan != NIL)
+ return;
+
+ /*plan each action query*/
+ foreach(l, parse->mergeActQry)
+ {
+ Plan *actplan = (Plan *)merge_action_planner(glob,
+ (Query *)lfirst(l),
+ (Plan *)linitial(mainplan->plans)
+ );
+
+ mainplan->mergeActPlan = lappend(mainplan->mergeActPlan, actplan);
+ }
+
+ return;
+}
+
+/*create plan for a single merge action*/
+static ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+ Plan *top_plan)
+{
+ PlannerInfo *root;
+ MergeAction *actplan;
+ ModifyTable *result;
+
+ List *returningLists;
+ List *rowMarks;
+
+ /*
+ * no having clause in a merge action
+ */
+ Assert(parse->havingQual == NULL);
+
+
+ /* Create a PlannerInfo data structure for this subquery */
+ root = makeNode(PlannerInfo);
+ root->parse = parse;
+ root->glob = glob;
+ root->query_level = 1;
+ root->parent_root = NULL;
+ root->planner_cxt = CurrentMemoryContext;
+ root->init_plans = NIL;
+ root->cte_plan_ids = NIL;
+ root->eq_classes = NIL;
+ root->append_rel_list = NIL;
+ root->hasPseudoConstantQuals = false;
+ root->hasRecursion = false;
+ root->wt_param_id = -1;
+ root->non_recursive_plan = NULL;
+
+
+ /*
+ * Create the action plan node
+ */
+ actplan = makeNode(MergeAction);
+ actplan->operation = parse->commandType;
+ actplan->replaced = parse->replaced;
+ actplan->matched = parse->matched;
+
+ /*
+ * Do expression preprocessing on targetlist and quals.
+ */
+ parse->targetList = (List *)
+ preprocess_expression(root, (Node *) parse->targetList,
+ EXPRKIND_TARGET);
+
+ preprocess_qual_conditions(root, (Node *) parse->jointree);
+
+
+ /*
+ * we need a flat qual for explaining
+ */
+ actplan->flattenedqual = (List *)flatten_join_alias_vars(root, parse->jointree->quals);
+
+ /*copy the cost from the top_plan*/
+ actplan->plan.startup_cost = top_plan->startup_cost;
+ actplan->plan.total_cost = top_plan->total_cost;
+ actplan->plan.plan_rows = top_plan->plan_rows;
+ actplan->plan.plan_width = top_plan->plan_width;
+
+ /*
+ * prepare the result
+ */
+ if(parse->targetList)
+ actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+ actplan->plan.qual = (List *)parse->jointree->quals;
+ push_up_merge_action_vars(actplan, parse);
+
+ if (parse->returningList)
+ {
+ List *rlist;
+
+ Assert(parse->resultRelation);
+ rlist = set_returning_clause_references(root->glob,
+ parse->returningList,
+ &actplan->plan,
+ parse->resultRelation);
+ returningLists = list_make1(rlist);
+ }
+ else
+ returningLists = NIL;
+
+
+ if (parse->rowMarks)
+ rowMarks = NIL;
+ else
+ rowMarks = root->rowMarks;
+
+ result = make_modifytable(parse->commandType,
+ copyObject(root->resultRelations),
+ list_make1(actplan),
+ returningLists,
+ rowMarks,
+ SS_assign_special_param(root));
+
+ return result;
+}
+
/*
* preprocess_expression
* Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..b4514b8 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,23 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
result_relation, range_table);
/*
- * for "update" and "delete" queries, add ctid of the result relation into
- * the target list so that the ctid will propagate through execution and
- * ExecutePlan() will be able to identify the right tuple to replace or
- * delete. This extra field is marked "junk" so that it is not stored
+ * for "update" , "delete" and "merge" queries, add ctid of the result
+ * relation into the target list so that the ctid will propagate through
+ * execution and ExecutePlan() will be able to identify the right tuple
+ * to replace or delete.
+ * This extra field is marked "junk" so that it is not stored
* back into the tuple.
+ *
+ * BUT, if the query node is a merge action,
+ * we don't need to expend the ctid attribute in tlist.
+ * The tlist of the merge top level plan already contains
+ * a "ctid" junk attr of the target relation.
*/
- if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+
+ if(!parse->isMergeAction &&
+ (command_type == CMD_UPDATE ||
+ command_type == CMD_DELETE ||
+ command_type == CMD_MERGE))
{
TargetEntry *tle;
Var *var;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92c2208..ffff863 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -67,6 +67,16 @@ typedef struct
bool inserted_sublink; /* have we inserted a SubLink? */
} flatten_join_alias_vars_context;
+typedef struct
+{
+ int varno_source;
+ int varno_target;
+ int varno_join;
+
+ int offset_source;
+ int offset_target;
+} push_up_merge_action_vars_context;
+
static bool pull_varnos_walker(Node *node,
pull_varnos_context *context);
static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
@@ -83,6 +93,8 @@ static bool pull_var_clause_walker(Node *node,
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static bool push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context);
/*
@@ -677,6 +689,91 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
(void *) context);
}
+/*
+* When prepare for the MERGE command, we have made a
+* left join between the Source table and target table as the
+* main plan.
+*
+* In this case, the range table contains ONLY THREE range table entries:
+* 1. the source table, which may be a subquery or a plain table
+* 2. the entry of the targe table, which is a plain table
+* 3. join expression with the sourse table and target table as its parameters.
+*
+* Each merge action of the command has its own query and
+* plan nodes as well. And, the vars in its target list and qual
+* expressions may refers to the attribute in any one of the above 3
+* range table entries.
+*
+* However, since the result tuple slots of merge actions are
+* projected from the returned tuple of the join, we need to
+* mapping the vars of source table and target table to their
+* corresponding attributes in the third range table entry.
+*
+* This function does the opposit of the flatten_join_alias_vars()
+* function. It walks through the target list and qual of a
+* MergeAction plan, changes the vars' varno and varattno to the
+* corresponding position in the upper level join RTE.
+*/
+void
+push_up_merge_action_vars(MergeAction *actplan, Query *actqry)
+{
+ push_up_merge_action_vars_context context;
+ RangeTblEntry *source_rte = rt_fetch(1,actqry->rtable);
+
+
+ /*
+ * We are supposed to do a more careful assingment
+ * of the values in context
+ * But lets take a shortcut for simple.
+ */
+ context.varno_source = 1;
+ context.varno_target = 2;
+ context.varno_join = 3;
+
+ context.offset_source = 0;
+
+
+ context.offset_target = list_length(source_rte->eref->colnames);
+
+ push_up_merge_action_vars_walker((Node *)actplan->plan.targetlist, &context);
+
+ push_up_merge_action_vars_walker((Node *)actplan->plan.qual, &context);
+
+}
+
+static bool
+push_up_merge_action_vars_walker(Node *node,
+ push_up_merge_action_vars_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *)node;
+
+ if(var->varno == context->varno_source)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_source;
+ return false;
+ }
+ else if(var->varno == context->varno_target)
+ {
+ var->varno = context->varno_join;
+ var->varattno += context->offset_target;
+ return false;
+ }
+ else if(var->varno == context->varno_join)
+ return false;
+ else
+ elog(ERROR, "the vars in merge action tlist of qual should only belongs to the source table or targe table");
+
+
+ }
+
+ return expression_tree_walker(node, push_up_merge_action_vars_walker,
+ (void *) context);
+}
/*
* flatten_join_alias_vars
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..4bd62bf 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +165,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
* Optimizable statements
*/
case T_InsertStmt:
+ case T_MergeInsert:
result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
break;
case T_DeleteStmt:
+ case T_MergeDelete:
result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
break;
case T_UpdateStmt:
+ case T_MergeUpdate:
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
break;
+ case T_MergeStmt:
+ result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+ break;
+
case T_SelectStmt:
{
SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +290,28 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->commandType = CMD_DELETE;
- /* set up range table with just the result rel */
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_DELETE);
-
qry->distinctClause = NIL;
/*
- * The USING clause is non-standard SQL syntax, and is equivalent in
- * functionality to the FROM list that can be specified for UPDATE. The
- * USING keyword is used rather than FROM because FROM is already a
- * keyword in the DELETE syntax.
- */
- transformFromClause(pstate, stmt->usingClause);
-
+ * The input stmt could be a MergeDelete node.
+ * In this case, we don't need the process on range table.
+ */
+ if(IsA(stmt, DeleteStmt))
+ {
+ /* set up range table with just the result rel */
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_DELETE);
+ /*
+ * The USING clause is non-standard SQL syntax, and is equivalent in
+ * functionality to the FROM list that can be specified for UPDATE. The
+ * USING keyword is used rather than FROM because FROM is already a
+ * keyword in the DELETE syntax.
+ */
+ transformFromClause(pstate, stmt->usingClause);
+ }
+
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -347,6 +361,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* VALUES list, or general SELECT input. We special-case VALUES, both for
* efficiency and so we can handle DEFAULT specifications.
*/
+
+ /*a MergeInsert statment is always a VALUE clause*/
isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
/*
@@ -382,7 +398,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, ACL_INSERT);
/* Validate stmt->cols list, or build default list if no list given */
@@ -1730,16 +1747,19 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- interpretInhOption(stmt->relation->inhOpt),
- true,
- ACL_UPDATE);
+ if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+ {
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ interpretInhOption(stmt->relation->inhOpt),
+ true,
+ ACL_UPDATE);
- /*
- * the FROM clause is non-standard SQL syntax. We used to be able to do
- * this with REPLACE in POSTQUEL so we keep the feature.
- */
- transformFromClause(pstate, stmt->fromClause);
+ /*
+ * the FROM clause is non-standard SQL syntax. We used to be able to do
+ * this with REPLACE in POSTQUEL so we keep the feature.
+ */
+ transformFromClause(pstate, stmt->fromClause);
+ }
qry->targetList = transformTargetList(pstate, stmt->targetList);
@@ -2241,3 +2261,389 @@ applyLockingClause(Query *qry, Index rtindex,
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
+
+/*
+transform an action of merge command into a query.
+No change of the pstate range table is allowed in this function.
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+ Query *actqry;
+
+ /*
+ * firstly, we need to make sure that DELETE and UPDATE
+ * actions are only taken in MATCHED condition,
+ * and INSERTs are only takend when not MATCHED
+ */
+
+ switch(condact->action->type)
+ {
+ case T_MergeDelete:/*a delete action*/
+ {
+ MergeDelete *deleteact = (MergeDelete *)(condact->action);
+ Assert(IsA(deleteact,MergeDelete));
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+
+ /*put new right code to the result relaion.
+ This line chages the RTE in range table directly*/
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+
+ deleteact->relation = stmt->relation;
+ deleteact->usingClause = stmt->source;
+ deleteact->whereClause = condact->condition;;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)deleteact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_DELETE ||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper DELETE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeUpdate:/*an update action*/
+ {
+ MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+ Assert(IsA(updateact,MergeUpdate));
+
+
+ if(!condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+
+
+ /*the "targetlist" of the updateact is filled in the parser */
+ updateact->relation = stmt->relation;
+ updateact->fromClause = stmt->source;
+ updateact->whereClause = condact->condition;
+
+ /*parse the action query*/
+ actqry = transformStmt(pstate, (Node *)updateact);
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_UPDATE||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper UPDATE action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeInsert:/*an insert action*/
+ {
+ MergeInsert *insertact = (MergeInsert *)(condact->action);
+ Assert(IsA(insertact,MergeInsert));
+
+ if(condact->match)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+
+
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;
+
+ /*the "cols" and "selectStmt" of the insertact is filled in the parser */
+ insertact->relation = stmt->relation;
+
+ /*
+ the merge insert action has a strange feature.
+ In an ordinary INSERT, the VALUES list can only
+ contains constants and DEFAULT. (am I right??)
+ But in the INSERT action of MERGE command,
+ the VALUES list can have expressions with
+ variables(attributes of the targe and source tables).
+ Besides, in the ordinary INSERT, a VALUES list can
+ never be followed by a WHERE clause.
+ But in MERGE INSERT action, there are matching conditions.
+
+ Thus, the output qry of this function is an INSERT
+ query in the style of "INSERT...VALUES...",
+ except that we have other range tables and a WHERE clause.
+ Note that it is also different from the "INSERT ... SELECT..."
+ query, in which the whole SELECT is a subquery.
+ (We don't have subquery here).
+
+ We construct this novel query structure in order
+ to keep consitency with other merge action types
+ (DELETE, UPDATE). In this way, all the merge action
+ queries are in fact share the very same Range Table,
+ They only differs in their target lists and join trees
+ */
+
+
+ /*parse the action query, this will call
+ transformInsertStmt() which analyzes the VALUES list.*/
+ actqry = transformStmt(pstate, (Node *)insertact);
+
+ /*do the WHERE clause here, Since the
+ transformInsertStmt() function only analyzes
+ the VALUES list but not the WHERE clause*/
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ if(!IsA(actqry, Query) ||
+ actqry->commandType != CMD_INSERT||
+ actqry->utilityStmt != NULL)
+ elog(ERROR, "improper INSERT action in merge stmt");
+
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeDoNothing:
+ {
+ MergeDoNothing *nothingact = (MergeDoNothing *)(condact->action);
+
+ Assert(IsA(nothingact,MergeDoNothing));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_DONOTHING;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ case T_MergeError:
+ {
+ MergeError *erract = (MergeError *)(condact->action);
+ Assert(IsA(erract,MergeError));
+
+ actqry = makeNode(Query);
+
+ actqry->jointree = makeFromExpr(pstate->p_joinlist,
+ transformWhereClause(pstate,
+ condact->condition,
+ "WHERE"));
+
+ actqry->rtable = pstate->p_rtable;
+
+ actqry->commandType = CMD_RAISEERR;
+ actqry->isMergeAction = true;
+ actqry->matched = condact->match;
+
+ return actqry;
+ }
+ break;
+ default:
+ elog(ERROR, "unknown MERGE action type %d", condact->action->type);
+ break;
+
+ }
+
+ /*never comes here*/
+ return NULL;
+}
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+ Query *qry;
+
+ ColumnRef *starRef;
+ ResTarget *starResTarget;
+ ListCell *act;
+ ListCell *l;
+ JoinExpr *joinexp;
+ int rtindex;
+
+ /*The source list has only one element*/
+ if(list_length(stmt->source) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("now we only accept merge command with only ONE source table")));
+
+ /*now, do the real tranformation of the merge command. */
+ qry = makeNode(Query);
+ qry->commandType = CMD_MERGE;
+
+ /*
+ What we are doing here is to create a query like
+ "SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+
+ Note:
+ 1. we set the "match condition" as the join qualification.
+ The left join will scan both the matched and non-matched tuples.
+
+ 2. a normal SELECT query has no "target relation".
+ But here we need to set the targe relation in query,
+ like the UPDATE/DELETE/INSERT queries.
+ So this is a left join SELECT with a "target table" in its range table.
+
+ 3. We don't have a specific ACL level for Merge, here we just use
+ ACL_SELECT.
+ But we will add other ACL levels when handle each merge actions.
+ */
+
+
+ /*before analyze the FROM clause, we need to set the target table.
+ We cannot call setTargetTable() function directly.
+ We only need the lock target relation, without adding it to Range table.
+ */
+ setTargetTableLock(pstate, stmt->relation);
+
+ /*create the FROM clause. Make the join expression first*/
+ joinexp = makeNode(JoinExpr);
+ joinexp->jointype = JOIN_LEFT;
+ joinexp->isNatural = FALSE;
+ /*source list has only one element*/
+ joinexp->larg = linitial(stmt->source);
+ joinexp->rarg = (Node *)stmt->relation;
+ /*match condtion*/
+ joinexp->quals = stmt->matchCondition;
+
+ /*transform the FROM clause. The target relation and
+ source relation will be add to Rtable here. */
+ transformFromClause(pstate, list_make1(joinexp));
+
+ /*the targetList of the main query is "*" */
+ starRef = makeNode(ColumnRef);
+ starRef->fields = list_make1(makeNode(A_Star));
+ starRef->location = 1;
+
+ starResTarget = makeNode(ResTarget);
+ starResTarget->name = NULL;
+ starResTarget->indirection = NIL;
+ starResTarget->val = (Node *)starRef;
+ starResTarget->location = 1;
+
+ qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+ /*we don't need the WHERE clause here. Set it null. */
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ /*now , we find out the RTE for the target relation,
+ and do some unfinished jobs*/
+ rtindex = 1;
+ foreach(l, pstate->p_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+ if(rte->relid == pstate->p_target_relation->rd_id)
+ {
+ /*find the RTE*/
+ pstate->p_target_rangetblentry = rte;
+ rte->requiredPerms = ACL_SELECT;
+ rte->inh = false;
+ qry->resultRelation = rtindex;
+ break;
+ }
+ rtindex++;
+ }
+
+ if(pstate->p_target_rangetblentry == NULL)
+ elog(ERROR, "cannot find the RTE for target table");
+
+
+ qry->rtable = pstate->p_rtable;
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ /*
+ * Top-level aggregates are simply disallowed in MERGE
+ */
+ if (pstate->p_hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in top level of MERGE"),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) qry, 0))));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in MERGE"),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) qry))));
+
+#if 0
+ /*
+ the main query is done. Ready for tansform actions.
+
+ Firstly, we check the last action of the action list.
+ If it is not a DO NOTING action, we need to generate
+ an INSERT DEFAULT VALUES action and append it to action list.
+ */
+ lastaction = (MergeConditionAction *)llast(stmt->actions);
+
+ if(lastaction->action == NULL)
+ {
+ /*
+ we have a do nothing action here,
+ What we need to do is just delete it from action list
+ */
+ stmt->actions = list_truncate(stmt->actions,
+ list_length(stmt->actions) - 1);
+ }
+ else
+ {
+ /*
+ The last action is no the DO NOTHING action,
+ we need to generate an INSERT action.
+ */
+ lastaction = makeNode(MergeConditionAction);
+
+ lastaction->condition = NULL;
+ lastaction->match = NOT;
+ lastaction->action = makeNode(MergeInsert);
+
+ /*nothing need to be filled into the node*/
+
+ stmt->actions = lappend(stmt->actions, lastaction);
+ }
+#endif
+
+ /*
+ For each actions ,we transform it to a seperate query.
+ the action queries shares the exactly same
+ range table with the main query.
+
+ In other words, in the extra condtions of the sub actions,
+ we don't allow involvement of new tables
+ */
+ qry->mergeActQry = NIL;
+
+ foreach(act,stmt->actions)
+ {
+ MergeConditionAction *mca = (MergeConditionAction *)lfirst(act);
+ Query *actqry;
+
+ /*transform the act (and its condition) as a single query. */
+ actqry = transformMergeActions(pstate, stmt, mca);
+
+ /*since we don't invoke setTargetTable() in transformMergeActions(),
+ we need to set actqry->resultRelation here
+ */
+ actqry->resultRelation = qry->resultRelation;
+
+ /*put it into the list*/
+ qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+ }
+
+ return qry;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aab7789..ebe39d9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,10 @@ static TypeName *TableFuncTypeName(List *columns);
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+%type <node> MergeStmt opt_and_condition merge_condition_action merge_action
+%type <boolean> opt_not
+%type <list> merge_condition_action_list
+
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -505,6 +509,8 @@ static TypeName *TableFuncTypeName(List *columns);
MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MATCHED MERGE RAISE ERROR_P
+
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -726,6 +732,7 @@ stmt :
| ListenStmt
| LoadStmt
| LockStmt
+ | MergeStmt
| NotifyStmt
| PrepareStmt
| ReassignOwnedStmt
@@ -6986,6 +6993,7 @@ ExplainableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt
+ | MergeStmt
| DeclareCursorStmt
| CreateAsStmt
| ExecuteStmt /* by default all are $$=$1 */
@@ -7331,6 +7339,114 @@ set_target_list:
/*****************************************************************************
*
* QUERY:
+ * MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt:
+ MERGE INTO relation_expr_opt_alias
+ USING table_ref
+ ON a_expr
+ merge_condition_action_list
+ {
+ MergeStmt *m = makeNode(MergeStmt);
+
+ m->relation = $3;
+
+ /*although we have only one USING table,
+ we still make it a list, maybe in future
+ we will allow mutliple USING tables.*/
+ m->matchCondition = $7;
+ m->source = list_make1($5);
+ m->actions = $8;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+merge_condition_action_list:
+ merge_condition_action
+ { $$ = list_make1($1); }
+ | merge_condition_action_list merge_condition_action
+ { $$ = lappend($1,$2); }
+ ;
+
+merge_condition_action:
+ WHEN opt_not MATCHED opt_and_condition THEN merge_action
+ {
+ MergeConditionAction *m = makeNode(MergeConditionAction);
+
+ m->match = $2;
+ m->condition = $4;
+ m->action = $6;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+
+opt_and_condition:
+ AND a_expr {$$ = $2;}
+ | /*EMPTY*/ {$$ = NULL;}
+ ;
+
+opt_not:
+ NOT {$$ = false;}
+ | /*EMPTY*/ {$$ = true;}
+ ;
+
+merge_action:
+ DELETE_P
+ {
+ $$ = (Node *)makeNode(MergeDelete);
+ }
+ | UPDATE SET set_clause_list
+ {
+ UpdateStmt *n = makeNode(MergeUpdate);
+ n->targetList = $3;
+ $$ = (Node *)n;
+ }
+ | INSERT values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = $2;
+
+ $$ = (Node *)n;
+ }
+
+ | INSERT '(' insert_column_list ')' values_clause
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = $3;
+ n->selectStmt = $5;
+
+ $$ = (Node *)n;
+ }
+ | INSERT DEFAULT VALUES
+ {
+ InsertStmt *n = makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = NULL;
+
+ $$ = (Node *)n;
+ }
+ | DO NOTHING
+ {
+ $$ = (Node *)makeNode(MergeDoNothing);
+ }
+ | RAISE ERROR_P
+ {
+ $$ = (Node *)makeNode(MergeError);
+ }
+ ;
+
+
+
+/*****************************************************************************
+ *
+ * QUERY:
* CURSOR STATEMENTS
*
*****************************************************************************/
@@ -10952,6 +11068,7 @@ unreserved_keyword:
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EXCLUDE
| EXCLUDING
@@ -11005,7 +11122,9 @@ unreserved_keyword:
| LOGIN_P
| MAPPING
| MATCH
+ | MATCHED
| MAXVALUE
+ | MERGE
| MINUTE_P
| MINVALUE
| MODE
@@ -11048,6 +11167,7 @@ unreserved_keyword:
| PROCEDURAL
| PROCEDURE
| QUOTE
+ | RAISE
| RANGE
| READ
| REASSIGN
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 16ca583..8f2ec6b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -214,6 +214,25 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
return rtindex;
}
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+
+ /* Close old target; this could only happen for multi-action rules */
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, NoLock);
+
+ /*
+ * Open target rel and grab suitable lock (which we will hold till end of
+ * transaction).
+ *
+ * free_parsestate() will eventually do the corresponding heap_close(),
+ * but *not* release the lock.
+ */
+ pstate->p_target_relation = parserOpenTable(pstate, relation,
+ RowExclusiveLock);
+}
+
/*
* Simplify InhOption (yes/no/default) into boolean yes/no.
*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..3ee0428 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1836,6 +1836,41 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
return rewritten;
}
+/*if the merge action type has already been processed by rewriter*/
+#define insert_rewrite (1<<0)
+#define delete_rewrite (1<<1)
+#define update_rewrite (1<<2)
+
+/*if the merge action type is fully replace by rules.*/
+#define insert_instead (1<<3)
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+#define merge_action_already_rewrite(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+ (acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+ (acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+#define set_action_rewrite(acttype, flag) \
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_rewrite;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_rewrite;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_rewrite;}
+
+#define merge_action_instead(acttype, flag) \
+ ((acttype == CMD_INSERT && (flag & insert_instead)) || \
+ (acttype == CMD_UPDATE && (flag & update_instead)) || \
+ (acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+ if(acttype == CMD_INSERT) \
+ {flag |= insert_instead;}\
+ else if(acttype == CMD_UPDATE) \
+ {flag |= update_instead;}\
+ else if(acttype == CMD_DELETE) \
+ {flag |= delete_instead;}
/*
* QueryRewrite -
@@ -1861,7 +1896,151 @@ QueryRewrite(Query *parsetree)
*
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = RewriteQuery(parsetree, NIL);
+ if(parsetree->commandType == CMD_MERGE)
+ {
+ /*
+ *for merge query, we have a set of action queries (not subquery).
+ *each of these action queries should be applied to RewriteQuery().
+ */
+ ListCell *l;
+
+ int flag = 0;
+
+ List *pre_qry = NIL;
+ List *post_qry = NIL;
+
+
+ querylist = NIL;
+
+
+ /*rewrite the merge action queries one by one.*/
+ foreach(l, parsetree->mergeActQry)
+ {
+ List *queryList4action = NIL;
+ Query *actionqry;
+ Query *q;
+
+
+ actionqry = lfirst(l);
+
+ /*
+ * no rewriting for DO NOTHING or ERROR
+ */
+ if(actionqry->commandType == CMD_DONOTHING ||
+ actionqry->commandType == CMD_RAISEERR)
+ continue;
+
+
+ /*
+ *if this kind of actions are fully replaced by rules,
+ *we mark it as "replaced"
+ */
+ if(merge_action_instead(actionqry->commandType, flag))
+ {
+ /*
+ *Still need to call RewriteQuery(),
+ *since we need the process on target list and so on.
+ *BUT, the returned list is discarded
+ */
+ RewriteQuery(actionqry, NIL);
+ actionqry->replaced = true;
+ continue;
+ }
+
+
+ /*if this kind of actions are already processed by rewriter, skip it.*/
+ if(merge_action_already_rewrite(actionqry->commandType, flag))
+ {
+ RewriteQuery(actionqry, NIL);
+ continue;
+ }
+
+ /*ok this action has not been processed before, let's do it now.*/
+ queryList4action = RewriteQuery(actionqry, NIL);
+
+ /*this kind of actions has been processed, set the flag*/
+ set_action_rewrite(actionqry->commandType,flag);
+
+ /*if the returning list is nil, this merge action
+ is replaced by a do-nothing rule*/
+ if(queryList4action == NIL)
+ {
+ /*set the flag for other merge actions of the same type*/
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ continue;
+ }
+
+ /*
+ * if the rewriter return a non-NIL list, the merge action query
+ *could be one element in it.
+ *if so, it must be the head (for INSERT acton)
+ *or tail (for UPDATE/DELETE action).
+ */
+
+ /*test the list head*/
+ q = (Query *)linitial(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*
+ *the merge action is the head, the remaining part
+ *of the list are the queries generated by rules
+ *we put them in the post_qry list.
+ */
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+
+ queryList4action = list_delete_first(queryList4action);
+ post_qry = list_concat(post_qry,queryList4action);
+
+ continue;
+
+ }
+
+ /*test the list tail*/
+ q = (Query *)llast(queryList4action);
+ if(q->querySource == QSRC_ORIGINAL)
+ {
+ /*the merge action is the tail.
+ Put the rule queries in pre_qry list*/
+ if(querylist == NIL)
+ querylist = list_make1(parsetree);
+
+ queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+ pre_qry = list_concat(pre_qry,queryList4action);
+ continue;
+
+ }
+
+ /*here, the merge action query is not in the rewriten query list,
+ *which means the action is replaced by INSTEAD rule(s).
+ *We need to mark it as "replaced".
+
+ For a INSERT action, we put the rule queries in the post list
+ otherwise, in the pre list
+ */
+ if(actionqry->commandType == CMD_INSERT)
+ post_qry = list_concat(post_qry,queryList4action);
+ else
+ pre_qry = list_concat(pre_qry,queryList4action);
+
+ set_action_instead(actionqry->commandType, flag);
+ actionqry->replaced = true;
+ }
+
+ /*finally, put the 3 lists into one.
+ *If all the merge actions are replaced by rules,
+ *the original merge query
+ *will not be involved in the querylist.
+ */
+ querylist = list_concat(pre_qry,querylist);
+ querylist = list_concat(querylist, post_qry);
+
+ }
+ else
+ querylist = RewriteQuery(parsetree, NIL);
/*
* Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"DELETE %u", queryDesc->estate->es_processed);
break;
+ case CMD_MERGE:
+ snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+ "MERGE %u", queryDesc->estate->es_processed);
+ break;
default:
strcpy(completionTag, "???");
break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1815539..e0dc7c3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
+ case CMD_MERGE:
return false;
default:
elog(WARNING, "unrecognized commandType: %d",
@@ -1405,6 +1406,10 @@ CreateCommandTag(Node *parsetree)
tag = "SELECT";
break;
+ case T_MergeStmt:
+ tag = "MERGE";
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -2242,6 +2247,7 @@ GetCommandLogLevel(Node *parsetree)
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
+ case T_MergeStmt:
lev = LOGSTMT_MOD;
break;
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 267a08e..51d0f11 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -164,6 +164,8 @@ extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
+extern void ExecBSMergeTriggers(ModifyTableState *mt_state);
+extern void ExecASMergeTriggers(ModifyTableState *mt_state);
extern void AfterTriggerBeginXact(void);
extern void AfterTriggerBeginQuery(void);
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..422e3ce 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+extern MergeActionState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7442d2d..64e20bb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1032,9 +1032,22 @@ typedef struct ModifyTableState
int mt_whichplan; /* which one is being executed (0..n-1) */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
+ List *mergeActPstates; /*the list of the planstate of meger command actions.
+ NIL if this is not a merge command.
+ The elements if it are still ModifyTableState nodes*/
} ModifyTableState;
/* ----------------
+ * MergeActionState information
+ * ----------------
+ */
+typedef struct MergeActionState
+{
+ PlanState ps; /* its first field is NodeTag */
+ CmdType operation;
+} MergeActionState;
+
+/* ----------------
* AppendState information
*
* nplans how many plans are in the array
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..a840349 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
T_Plan = 100,
T_Result,
T_ModifyTable,
+ T_MergeAction,
T_Append,
T_RecursiveUnion,
T_BitmapAnd,
@@ -86,6 +87,7 @@ typedef enum NodeTag
T_PlanState = 200,
T_ResultState,
T_ModifyTableState,
+ T_MergeActionState,
T_AppendState,
T_RecursiveUnionState,
T_BitmapAndState,
@@ -347,6 +349,13 @@ typedef enum NodeTag
T_AlterUserMappingStmt,
T_DropUserMappingStmt,
T_AlterTableSpaceOptionsStmt,
+ T_MergeStmt,
+ T_MergeConditionAction,
+ T_MergeUpdate,
+ T_MergeDelete,
+ T_MergeInsert,
+ T_MergeDoNothing,
+ T_MergeError,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -511,6 +520,9 @@ typedef enum CmdType
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
CMD_DELETE,
+ CMD_MERGE, /*merge stmt*/
+ CMD_DONOTHING,
+ CMD_RAISEERR,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
CMD_NOTHING /* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d31cf6c..ef5520f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -149,6 +149,13 @@ typedef struct Query
List *constraintDeps; /* a list of pg_constraint OIDs that the query
* depends on to be semantically valid */
+
+ /*the fileds for MERGE command*/
+ bool isMergeAction; /*if this query is a merge action. */
+ bool matched; /*this is a MATCHED action or NOT*/
+ bool replaced; /*is this merge action replaced by rules*/
+ List *mergeActQry; /* the list of all the merge actions.
+ * used only for merge query statment*/
} Query;
@@ -993,6 +1000,58 @@ typedef struct SelectStmt
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;
+/*The structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+ NodeTag type;
+ RangeVar *relation; /*targe relation for merge */
+
+ /* source relations for the merge.
+ *Currently, we only allwo single-source merge,
+ *so the length of this list should always be 1
+ */
+ List *source;
+ Node *matchCondition; /* qualifications of the merge*/
+
+ /*list of MergeConditionAction structure.
+ *It stores all the matched / not-matched
+ *conditions and the corresponding actions
+ *The elments of this list are MergeConditionAction
+ *nodes
+ */
+ List *actions;
+
+}MergeStmt;
+
+/* the structure for the actions of MERGE command.
+* Holds info of the clauses like
+* "WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+ NodeTag type;
+ bool match; /*match or not match*/
+ Node *condition;/*the AND condition for this action*/
+ Node *action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+/*
+* The merge action nodes are in fact the
+* ordinary nodes of UPDATE,DELETE and INSERT
+*/
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
+
+typedef struct MergeDoNothing
+{
+ NodeTag type;
+}MergeDoNothing;
+
+typedef struct MergeError
+{
+ NodeTag type;
+}MergeError;
/* ----------------------
* Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..a020051 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,9 +169,25 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *mergeActPlan; /*the plans for merge actions,
+ which are also ModifyTable nodes*/
} ModifyTable;
/* ----------------
+ * MergeAction node -
+ * The plan node for the actions of MERGE command
+ * ----------------
+ */
+typedef struct MergeAction
+{
+ Plan plan;
+ bool replaced; /*if this action is replaced by INSTEAD rules*/
+ CmdType operation;/* INSERT, UPDATE, or DELETE */
+ bool matched;
+ List *flattenedqual; /*the flattened qual expression of action*/
+}MergeAction;
+
+/* ----------------
* Append node -
* Generate the concatenation of the results of sub-plans.
* ----------------
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index b0e04a0..4d6c9e8 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -15,6 +15,7 @@
#define VAR_H
#include "nodes/relation.h"
+#include "nodes/plannodes.h"
typedef enum
{
@@ -32,5 +33,5 @@ extern int locate_var_of_relation(Node *node, int relid, int levelsup);
extern int find_minimum_var_level(Node *node);
extern List *pull_var_clause(Node *node, PVCPlaceHolderBehavior behavior);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
-
+extern void push_up_merge_action_vars(MergeAction * actplan,Query * actqry);
#endif /* VAR_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 974bb7a..208c3e4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -141,6 +141,7 @@ PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD)
PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
@@ -229,7 +230,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
@@ -296,6 +299,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
+PG_KEYWORD("raise", RAISE, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
PG_KEYWORD("real", REAL, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..b54f530 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,7 @@
extern void transformFromClause(ParseState *pstate, List *frmList);
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
extern bool interpretInhOption(InhOption inhOpt);
extern bool interpretOidsOption(List *defList);
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
new file mode 100644
index 0000000..18e3891
--- /dev/null
+++ b/src/test/regress/expected/merge.out
@@ -0,0 +1,279 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 20
+ 4 | 40
+(3 rows)
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 25
+ 3 | 50
+(3 rows)
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ 4 | 40
+(4 rows)
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 25
+ 3 | 50
+ 4 | 40
+(4 rows)
+
+ROLLBACK;
+--
+-- Non-standard functionality
+--
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ DELETE
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+(1 row)
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 3 | 50
+ 4 | 40
+(3 rows)
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 5
+ 3 | 20
+ 4 | 40
+(4 rows)
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+-- fails according to standard
+-- fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ERROR: multiple actions on single target row
+
+ROLLBACK;
+
+-- This next SQL statement
+-- fails according to standard
+-- suceeds in PostgreSQL implementation by simply ignoring the second
+-- matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+ id | balance
+----+---------
+ 2 | 5
+ 3 | 20
+ 4 | 5
+ 4 | 40
+(4 rows)
+
+-- This next SQL statement
+-- suceeds according to standard (yes, it is inconsistent)
+-- suceeds in PostgreSQL implementation, though could easily fail if
+-- there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ 4 | 5
+ 4 | 40
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+ |
+ |
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+ DO NOTHING
+;
+SELECT * FROM target;
+ id | balance
+----+---------
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+ INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+ UPDATE SET balance = t.balance + s.balance
+;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 191d1fe..2551b2a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml merge
# run stats by itself because its delay may be insufficient under heavy load
test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 80a9881..e7d7fae 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -123,4 +123,5 @@ test: returning
test: largeobject
test: with
test: xml
+test: merge
test: stats
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
new file mode 100644
index 0000000..7ecd02e
--- /dev/null
+++ b/src/test/regress/sql/merge.sql
@@ -0,0 +1,200 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+;
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Non-standard functionality
+--
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+ DELETE
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+-- fails according to standard
+-- fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ERROR: multiple actions on single target row
+
+ROLLBACK;
+
+-- This next SQL statement
+-- fails according to standard
+-- suceeds in PostgreSQL implementation by simply ignoring the second
+-- matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+
+-- This next SQL statement
+-- suceeds according to standard (yes, it is inconsistent)
+-- suceeds in PostgreSQL implementation, though could easily fail if
+-- there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+ DO NOTHING
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+ UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+ DELETE
+WHEN NOT MATCHED THEN
+ INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+ INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+ UPDATE SET balance = t.balance + s.balance
+;
On 10/08/10 12:08, Boxuan Zhai wrote:
Thanks for your feedback. I fixed all the above waring bugs. Find the new
patch in attachement.
Thanks.
I'm getting an assertion failure with this statement:
CREATE TABLE foo (id int4);
MERGE into foo t
USING (select id FROM generate_series(1,5) id) AS s
ON t.id = s.id
WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id);
TRAP: FailedAssertion("!(ActiveSnapshotSet())", File: "postgres.c",
Line: 749)
That's easily fixed - you need to add "case T_MergeStmt" to the list of
optimizable command types in analyze_requires_snapshot() function.
Unfortunately that doesn't get you far, the query then trips another
assertion:
TRAP: FailedAssertion("!(list_length(resultRelations) ==
list_length(subplans))", File: "createplan.c", Line: 3929)
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Tue, Aug 10, 2010 at 10:29 PM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:
On 10/08/10 12:08, Boxuan Zhai wrote:
Thanks for your feedback. I fixed all the above waring bugs. Find the new
patch in attachement.Thanks.
I'm getting an assertion failure with this statement:
CREATE TABLE foo (id int4);
MERGE into foo t
USING (select id FROM generate_series(1,5) id) AS s
ON t.id = s.id
WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id);The query works on my machine.
TRAP: FailedAssertion("!(ActiveSnapshotSet())", File: "postgres.c", Line:
749)That's easily fixed - you need to add "case T_MergeStmt" to the list of
optimizable command types in analyze_requires_snapshot() function.Unfortunately that doesn't get you far, the query then trips another
assertion:TRAP: FailedAssertion("!(list_length(resultRelations) ==
list_length(subplans))", File: "createplan.c", Line: 3929)
I just found that no Assert() works in my codes. I think it is because the
assertion is no enabled. How to enable assertion. To define
USE_ASSERT_CHECKING somewhere?
Show quoted text
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Boxuan Zhai wrote:
I just found that no Assert() works in my codes. I think it is because
the assertion is no enabled. How to enable assertion. To define
USE_ASSERT_CHECKING somewhere?
When you run "configure" before "make", use "--enable-cassert". The
normal trio for working on the PostgreSQL code is:
./configure --enable-depend --enable-cassert --enable-debug
Generally the only reason to build as a developer without asserts on is
to do performance testing. They will slow some portions of the code
down significantly.
--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us
On Wed, Aug 11, 2010 at 12:14 PM, Greg Smith <greg@2ndquadrant.com> wrote:
Boxuan Zhai wrote:
I just found that no Assert() works in my codes. I think it is because the
assertion is no enabled. How to enable assertion. To define
USE_ASSERT_CHECKING somewhere?When you run "configure" before "make", use "--enable-cassert". The normal
trio for working on the PostgreSQL code is:./configure --enable-depend --enable-cassert --enable-debug
Generally the only reason to build as a developer without asserts on is to
do performance testing. They will slow some portions of the code down
significantly.
Thanks. I will test MERGE under this new configuration. A new patch will be
submitted once I fix all the asserting bugs.
Show quoted text
--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us <http://www.2ndquadrant.us/>
On Wed, Aug 11, 2010 at 12:18 PM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:
On Wed, Aug 11, 2010 at 12:14 PM, Greg Smith <greg@2ndquadrant.com>wrote:
Boxuan Zhai wrote:
I just found that no Assert() works in my codes. I think it is because
the assertion is no enabled. How to enable assertion. To define
USE_ASSERT_CHECKING somewhere?When you run "configure" before "make", use "--enable-cassert". The
normal trio for working on the PostgreSQL code is:./configure --enable-depend --enable-cassert --enable-debug
Generally the only reason to build as a developer without asserts on is to
do performance testing. They will slow some portions of the code down
significantly.Thanks. I will test MERGE under this new configuration. A new patch will be
submitted once I fix all the asserting bugs.
The new patch is done. I named it as merge_v102. (1 means it is the
non-inheritance merge command, 02 means this is the second time of fixing
reported bugs)
Show quoted text
--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us <http://www.2ndquadrant.us/>
Attachments:
merge_v102.tar.gzapplication/x-gzip; name=merge_v102.tar.gzDownload
� gZbL �<is�F��j��Y�VLJ$E��udYNTe+��J�\�0���L�����H��e����$������g��J����^o�d��}�7|��������m���7�t��hw�����t67��[[�v��vgn~'�1��<�d"�w����A���?� ���I�5(�*?��E�92!���[K�o0��_�a�B�����]0`)�|�A�luv���VkC�=)oE����l6�XZ]]]��?��Ng��-V�kG���x8
U�DBq_���
� ��8O<%�"�����Dy �qkI�KEY��i����I�pk(��Q�|�N��/��)�h���r�wo&�� C4����4�a��*P��� Q���� �hz�(Q#��YS�#3g��K�%���R�I��:_�b�k���������G������(+����*�n�A���=P(d������8������/�6� x���X��2<j��\�t�B�l�2����K�����[�Ck��v�0H� ��G0M4�[
r �p�I�4LHi���hz"9T~�`�����.��Q���T�#P4�\R��E%�Wd`I����I�����T�"���3�( ��e�*��;$�L%C�G������I"YfbFI0���j. ��[,2G�(
RFp`.�7���8
���r/�iz�|������N���hEH�t$~��u����sA�aM@����N�}��������Ex�q��s��� �V/����8�n�8��,�����-��x�`v(�tj��V��{d0�K6� E �_~>9g����'�������XH����\4����uy�_���[�?.��E�g�_��{z}"N��.�>����[��7Mx�����5�3,�w��������bj�8���K=\<O}�P���lB����{��_(��=����Y�������i#�d��p�����'W=��g.�=�����N���Ew5
���5����/�e��!�����x�Z�^��`L�( �F�/��& �K1RI?N������EJH��O��w�QK��xH��;�d�qk? ���3
q8��tT#���; %�C�CB)���y�HG��,J�4��c���� ��d$�o��v\^��6���!k���lP N��� �c�������1Ha5��r]1��,�$)���'2�I(,�i�����>!�7A�f
bK�8�G,"T}�JqR`�"P�_�����T�P?��f �PI����4�V�o�.�#1�� ����� �M@�P2�a�>��@� �'>��C��6A���`��X8u�(N�jF(R������P� 9��X��OQ;P � ��`14��)�+�J�3�T�IH�Im!1��S2e%j��2���F��5�)e8~NK>��b0�KQe�|p���Q&?&�a0�����2W��e�i��*�Z��G"��0�������������"�� 3�d� j�`�'��4(� GN���+��:u�����r�D���|�><!�.�U�p��7X�Ddns��(��E:�;���:��k<�]%QOF�;d�P�
�4�k���nU�Rb��\S1 ���%���b����'AZJ�2dc3 }�7L�KZ���5m�����L�t��}������d*�4�>�u���&��h����;��zA�P5���cA�S��U����Wd�e�9�N���K5�Hd T��h{�[������{TH~zi��,�p+s��6k7yBQ�G&�����"����
�n'����� ������E1�X�&���5�/i?e�]���8 ?/��:7���/�U:���"��|��|��;J�X��a��k ��A���P�gM��L?�����c�O@&�_H4H���)�'��$��`��E)��,vDH1���p�� �I��O�7�q5�)��f��Ml��(��vt��p�S.�I:p��&�h&��e�]b���$� �W������-O#'R����\>�P�%���� e>�B��zD�"�c��99��&���b�<�cql<*�/�n�8TKD��4��bd+��WN|5 8C1����a��aj.�B",$_�`�0C�'kq:��Q���4�`,� /�\�B
4�3��R�[�ju�}
���d �QF� �)����{r���$�`Oq��l&��$NZ��Y� )U >���0��F�+1�4�8��r*f�\c���?���}��C�e7�������-l��G���\:�,O�tR�gj�uVTj �JV�p��gI�0�P�S�0uQ���By9h�mQ q=�U��z��<A-�(�
�+��_��i�6}O�,��J��`"�c
�A���_��M�0�h`�Mi;9��G����+"@a=�t(�U-���67l�2A#b�����Ptt�D�f��.� ����f8KS�5�w�).I=�kr�Da%�FL��*S8��8=dM� |�^��jkg��o�W�z�_T�L�U��W�d��]g�(C�2��NCl���^T������h���as�:�����B����[�iaa=�(H"��$�c�@B�Dc� �R>� e=)�e�`��3��d�����L��9��:0�pWk���*P�G�1RG�5�Li�lj�P��(S���c�����:`cT����c��R1�eYV4�h?��|?�+�*s����j�����S���&�8��-ij<�|v���;��&V��n��G��<�������E��O��
�X��}�~��u�T�[�vi��C�290����\�� ����Tg2lzqe���k��@����*D�o'{@3UD�XZ�T@J���9B;%�E���fh2$��CJ��-`DO��-l� #��v�<3M}Q�Y/�.p��� ���6� h�0<b�a Q�������O��*��H���c�@E`�YQR���� �VT���E�
]m�8��������W��)V�e���-����������h#���UI�\V>����LU��/XA �QP"s'G#����|.U����=lbe�����8�T`���z%���jc�T�s����\����(���T����&��-�h�xJ��naVM�t�6� ����wr&��l��O����^���J!O$#�
2�����Q��Q������
%@@� ������P�"���a?bu �{�r#�$ �<���Z���}��m$���.������(7��%o�Q���7P�\� �9;9�\' �����Q��d
4���$&������������6�>���=������9�LG���+o�f�1M
|N���e��]���R�����pm�4C�NK����-��1�7��m�b;�n���S}Hl��%B��|�x���������_
�$���]?���4��*�uG������������?�f����b-��:���t;���X�)��C<c�pO-�����Z*a(w
���"��ax)�8P�D� =�{��
���$�� ���*~l�0��Y
(��)��{+�)0���M��g��6�
�
�X�-�t���� ���T��A���7�[��v����u������+���[l'��
O�����"dCL��Z����� $�'al��g���1�p���aN����� |J�MfE�Mz�pP{ �@������mH"p�����q����tG�\��}�#�����
�r}�e�.��C�+��8�-��������x>)��0��NoXH�u={r��<�U���b�>��SK�������i(�%�h�q��*�z���=��R�%2J�K)RS7!3��������I�m�@�vS��I��Y���������K�Y��Gl����s��6���xsuqVZ�}��e���/���������n��{�t��*��p���5Y$�eH8���S4[8��%����������^]J�/�I,0x�&[tU����T}}.!��s(>�r0H���)���%ey�kST�n�2�|������.�]�u������,v+�#y@Q)�g�|uNf�m �i\7h���: �����8���8�5��5s���6s!~��(�AC����J!�|H�U
��>�m���a*��"�����R���,�tB��������h��0��&)���L������D�������A����+Ju�9BN` A��&��O��^����V��� �:L1�~�(;SB�D��������r�Lp��R�kk����L��5?��~�ok}{wgo�������k{^#uT������6�w�R��7���s�1+x�ojd�x63��_F���I��@<�����N��R��$�/��N{0�����-�Q�� 7���E`�:���"��\�V�s�I~`h��KEe3W�x�����TK����ww7Z��������|i��g�A�o���_�e��y?a__���?��}����
}���?��X}��v�/s���r�T��y�����h����lK��ju�����e�, ��Y0������w�PuO<������&����^���5��WO�
�$%k����V0���r|c�6�+@�0=��J��3�@uw�@~�_z,�\�
�5��v3PHp�m�v��?@� �w�k�g�]@�3:���v�F8�n`X��YZ}a>��Y�Z��Gm��+����nt���JkLQ����'�X�QjH+(�S�E����F����e���!�������� JJq|�����W|��H{�������������B���ZCy�+���PH��z�B���$
�?���$R�W���l��<� )D�N�"��������������q�7K+g�e������`hU3t����y�Z�yA
3��ha�1���Ib�O������klo"G�`� ��_�G��G�U�
/�>��������Ew���^ ����j����y�g(�Uq�F�������yTh��]�%���z�l���P�����<���2�������1w:��U�������r���ka���p���@
|3*@L=�����~?
NC��p D��3m�
�V3��
C�������>��#��Y��c���(��������py���m�~��T4��.�P\���%�N�4�S��@�I��Z
�8�� �k��@���y�+�'�-^bhzK����)��7�f�f����#���yt���~�F�q�e���qI�GV(0f������K2�X���T�������������C=�g��@�I�e�6�D��h�w���g������q���|��.�y�m�"���`������������6\�Tx���#yOd�X[�|E\�a�8:@��(g���C�����&|uvn��|��������n�[{~� �ybNv^�����I1��;�N3����%y��zz��vb��+
�W*�X��������2�C�x���{��M5Ty?^����#���7�����wT�8�����b�)mN�������'������8��d�2�]�|N�� T@ZA�j���y!� :4���@y9'����7>����+�*�����b���MZO����<J.�:���x��@��.Q���� 5�)��H��)�����k+}�n�G�erMuv�hD��BGA,�������Jo8�����I�w�e&8�������.s�A]~�%Wm�^J������'�+�;�qX�8���v����~;���:K����J�z�(���'���7q��O�v��,�� �8��n�1���76eg��x����\��hg��Nig��>����kG��i���DO��&Y��x^�6�X�����%�PPJ���������S�UMO4
��
��q�/��b�1o�s�~������4}�'J�)����)Fi �7=o��k�����T;O��g����$�-���N���Q���X��
1Y|L���!� =`���E���\����!T8�����_s� !*��VS�Z��8-�.U��^@���[X���}
�b�A8N\�V����j���yj���������A���(��`��-n��Wgs)����~����s�{K~�S�����*��J�:I�f{:{��q���}iwW����~��:� B�A��v�,aG7�� 'o��X%(I�@a
��;�����5 �����Z�1Pg�3����w����@ �~%;$c�^Ec��`;�u��Y8����jJ��L��M4���yep�_9�i
���.���R%;���S#O*U|�m��r��<d�@�S.@P[�
\�W��^��� �)$�d�*��+O� �%Y����Z����Xl(���w�d6����7�����,L�J��������'�]O� M:/�[F2O�@�@>r�
����
H��@�GV��������2o�%�N' �x�<U�4mm�6�u��.�D2�1 �Q���"��p��@��,F��%��Qc��I�a�D����d?K�1����o��+�����n��������u�$�-x�K��4q�>Pp]�Y<���]s_B<*�ubU�����d��L�*��������\��_$�n���ZQ�a�ff�������^0&��T�8����&RK����
`��Y��1H}�q�dti�d��&�@����7u��]
���e0y/���� ���x�S+��a���������T�t�x��'`����[��zut
��I�����{eL�4��A+�>