MERGE Specification

Started by Simon Riggsover 17 years ago76 messages
#1Simon Riggs
simon@2ndquadrant.com
2 attachment(s)

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

Attachments:

merge.out.spectext/x-vhdl; charset=UTF-8; name=merge.out.specDownload
sql-merge.htmltext/html; charset=UTF-8; name=sql-merge.htmlDownload
#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Simon Riggs (#1)
Re: MERGE Specification

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

#3A.M.
agentm@themactionfaction.com
In reply to: Simon Riggs (#1)
Re: MERGE Specification

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

#4Simon Riggs
simon@2ndquadrant.com
In reply to: Pavel Stehule (#2)
Re: MERGE Specification

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

#5Simon Riggs
simon@2ndquadrant.com
In reply to: A.M. (#3)
Re: MERGE Specification

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

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#5)
Re: MERGE Specification

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

#7Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#6)
Re: MERGE Specification

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

#8Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#6)
Re: MERGE Specification

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

#9Alvaro Herrera
alvherre@commandprompt.com
In reply to: Simon Riggs (#8)
Re: MERGE Specification

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

#10Gregory Stark
stark@enterprisedb.com
In reply to: Simon Riggs (#8)
Re: MERGE Specification

"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

#11Simon Riggs
simon@2ndquadrant.com
In reply to: Alvaro Herrera (#9)
Re: MERGE Specification

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

#12Martijn van Oosterhout
kleptog@svana.org
In reply to: Simon Riggs (#11)
Re: MERGE Specification

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.

#13Simon Riggs
simon@2ndquadrant.com
In reply to: Martijn van Oosterhout (#12)
Re: MERGE Specification

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

#14Simon Riggs
simon@2ndquadrant.com
In reply to: Gregory Stark (#10)
Re: MERGE Specification

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

#15Gregory Stark
stark@enterprisedb.com
In reply to: Simon Riggs (#14)
Re: MERGE Specification

"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!

#16A.M.
agentm@themactionfaction.com
In reply to: Simon Riggs (#14)
Re: MERGE Specification

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

#17Decibel!
decibel@decibel.org
In reply to: Gregory Stark (#15)
1 attachment(s)
Re: MERGE Specification

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:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#18Martijn van Oosterhout
kleptog@svana.org
In reply to: Decibel! (#17)
Re: MERGE Specification

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.

#19Decibel!
decibel@decibel.org
In reply to: Martijn van Oosterhout (#18)
1 attachment(s)
Re: MERGE Specification

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:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Decibel! (#19)
Re: MERGE Specification

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

#21Chris Browne
cbbrowne@acm.org
In reply to: Simon Riggs (#1)
Re: MERGE Specification

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)

#22Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#20)
Re: MERGE Specification

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

#23Robert Treat
xzilla@users.sourceforge.net
In reply to: Tom Lane (#20)
Re: MERGE Specification

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

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Treat (#23)
Re: MERGE Specification

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&#39;t_Gonna_Need_It

regards, tom lane

#25Hannu Krosing
hannu@krosing.net
In reply to: Simon Riggs (#5)
Re: MERGE Specification

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

#26Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#24)
Re: MERGE Specification

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&#39;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

#27Martijn van Oosterhout
kleptog@svana.org
In reply to: Tom Lane (#24)
Re: MERGE Specification

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.

#28Petr Jelinek
pjmodos@pjmodos.net
In reply to: Tom Lane (#24)
Re: MERGE Specification

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)

#29Simon Riggs
simon@2ndquadrant.com
In reply to: Simon Riggs (#22)
Re: MERGE Specification

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

#30Simon Riggs
simon@2ndquadrant.com
In reply to: Petr Jelinek (#28)
Re: MERGE Specification

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

#31Decibel!
decibel@decibel.org
In reply to: Simon Riggs (#26)
1 attachment(s)
Re: MERGE Specification

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&#39;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.

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:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#32Simon Riggs
simon@2ndquadrant.com
In reply to: Hannu Krosing (#25)
Re: MERGE Specification

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

#33Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Treat (#23)
Re: MERGE Specification

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

#34Alvaro Herrera
alvherre@commandprompt.com
In reply to: Simon Riggs (#30)
Re: MERGE Specification

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

#35Robert Treat
xzilla@users.sourceforge.net
In reply to: Tom Lane (#24)
Re: MERGE Specification

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

#36Hannes Dorbath
light@theendofthetunnel.de
In reply to: Simon Riggs (#32)
Re: MERGE Specification

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

#37Simon Riggs
simon@2ndquadrant.com
In reply to: Alvaro Herrera (#34)
Re: MERGE Specification

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

#38Marko Kreen
markokr@gmail.com
In reply to: Robert Treat (#35)
Re: MERGE Specification

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

#39Simon Riggs
simon@2ndquadrant.com
In reply to: Marko Kreen (#38)
Re: MERGE Specification

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

#40Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#1)
1 attachment(s)
Re: MERGE Specification

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:

merge.sgmltext/sgml; charset=UTF-8; name=merge.sgmlDownload
#41Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Simon Riggs (#40)
Re: MERGE Specification

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

#42Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#41)
1 attachment(s)
Re: MERGE Specification

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;
     &notify;
     &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>
#43Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Simon Riggs (#42)
Re: MERGE Specification

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/&gt;
PostgreSQL Development, 24x7 Support, Training and Services

#44David Fetter
david@fetter.org
In reply to: Boxuan Zhai (#43)
Re: MERGE Specification

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

#45Simon Riggs
simon@2ndQuadrant.com
In reply to: Boxuan Zhai (#43)
Re: MERGE Specification

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 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.

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

#46Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Simon Riggs (#45)
Re: MERGE Specification

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 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.

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

#47Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#46)
Re: MERGE Specification

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 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.

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

#48Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#47)
Re: MERGE Specification

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

#49Merlin Moncure
mmoncure@gmail.com
In reply to: Simon Riggs (#42)
Re: MERGE Specification

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

#50Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Merlin Moncure (#49)
Re: MERGE Specification

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

#51Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#50)
Re: MERGE Specification

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

#52Simon Riggs
simon@2ndQuadrant.com
In reply to: Boxuan Zhai (#50)
Re: MERGE Specification

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

#53Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Simon Riggs (#52)
Re: MERGE Specification

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

#54Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#53)
Re: MERGE Specification

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

#55Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#53)
Re: MERGE Specification

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

#56Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Simon Riggs (#54)
Re: MERGE Specification

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/&gt;
PostgreSQL Development, 24x7 Support, Training and Services

#57Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Simon Riggs (#55)
Re: MERGE Specification

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/&gt;
PostgreSQL Development, 24x7 Support, Training and Services

#58Simon Riggs
simon@2ndQuadrant.com
In reply to: Boxuan Zhai (#57)
Re: MERGE Specification

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

#59Peter Eisentraut
peter_e@gmx.net
In reply to: Simon Riggs (#47)
Re: MERGE Specification

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

#60Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Peter Eisentraut (#59)
1 attachment(s)
Re: MERGE Specification

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);
 
#61Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#60)
Re: MERGE Specification

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

#62Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Heikki Linnakangas (#61)
Re: MERGE Specification

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

#63Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Heikki Linnakangas (#62)
1 attachment(s)
Re: MERGE Specification

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.patch0000644000076400010400000030111011430140142015377 0ustar  bxzhaiAdministratorsdiff --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 &mdash; 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 &mdash; 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;
    &notify;
    &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
+;
#64Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#63)
Re: MERGE Specification

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

#65Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Heikki Linnakangas (#64)
1 attachment(s)
Re: MERGE Specification

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.patch0000644000076400010400000030076411430213026017640 0ustar  bxzhaiAdministratorsdiff --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 &mdash; 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 &mdash; 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;
    &notify;
    &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
+;
#66Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#65)
Re: MERGE Specification

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

#67Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Heikki Linnakangas (#66)
Re: MERGE Specification

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

#68Greg Smith
greg@2ndquadrant.com
In reply to: Boxuan Zhai (#67)
Re: MERGE Specification

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

#69Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Greg Smith (#68)
Re: MERGE Specification

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/&gt;

#70Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Boxuan Zhai (#69)
1 attachment(s)
Re: MERGE Specification

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/&gt;

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�qk?	 ���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���as�:�����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�aQ�������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��pD��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+�>|�z�|���x?ec����c��>S���;��>�MHk�Ud0cL��� �N�`�c�����;�4K*Z��d�U=J�{�M~���y�[EA�������*7��6�;F$YRsdx�����b���#�&�����$�FAm$�`2���;��=[�G��3)7����j���H�_�y��U&WE�fu��������
E���?y�y�L������w�K����
��$��J�J���(�$�sK��/��JO�N����E��@S�Uu����P<4���*���bx�T�@_���C���R,Xz;�,����x|*���ut�c�����bm�F�G �z~�@�j�-]2D�D������_�'�Q�bgY"e)�pP��H�[o����f\��E��=�s����D^���I��SS������W5���S�4�E��,����Y���|����>9�������m:�����5��6�T�����d j�s?�mf.8M�_��'�l>��
l�,�~k|>A
CqP����p�z�G2�"�0�������~�����G��C}G=�����K�����g
�As(����q�ZUt���.	wC�����	�
��M�d8���%�k�'��8W6�b�OU��8�aC�f���)�~����$Q���|#{���;������L0K��Q�+�Mh`��"������	-���YG^L:���&��$���U��*�cg!D��>�~���&����F����{�����������j��	���z�������I���B�1z?ArX:M�d@����F�u�&�?K�*g&B1����[����=��W��l6*{�D��D8i��A��D�l0�6���rp�g��*p����]��s�CZw��u~^���F������'F>��bs�>Aph�w���K������?�8� Q���K��Qx���"�g&��[�\����t�by�=Dj��t�J4��!�w��Rk7�]��*�$�$YD�@�1o�h�yYC���t�{�jh�3{0�E�Er,�j����y�Vc�]������"�����XN�u�wf��������5�v�8ew���*���E�������q����4u���>3�:-J�+
m���{�'��~�zw���'vW����4�!�m���5�����d���iYQ+��O�#_@R����#C���$����Pk,z��yi��u���1�3�	#C�n�^������@��G���]��`8��<��ch`N!��%��dl��3���4��'��h�����<~r�x���GK�V�������;g5�',(s����LX���(�4R�/c0t�����Pz5�������e�N8J���i �;Ul��:��8;z�}~t�������S�����?�J����������w?�Z8�N�h����W���*�b �����X(���x_$u/U���$@2��c�f����1K��������A|��"�>�0�
�ob���#��:��'Q��'�IjF@��L���ML���Q��J������X�o��R����=��ml`�|����!��z���M�s�J	��"�C��.�d�"��+����`��oU��Y�[�h�w���hHN��d3��x�m���x�}�e/��[�o>��m�����`���Y�.�#��Z����f�M�$��F�V}���/��h���\:�L�mE�$�:��F�����"��n�;�v@���/U���B7��1�3+n8)\�oL�s�|y��{��^Kf�}���N�h�8B+/��.;���|�p������*����R��� Y1&D�gQ1�EP1y'���!,hp�OGS���[c��n�;�I�P�\���X��Z���QI
�Y_g���D��Q�����j�{"�Z���8T��RQf�������u22��&N�V�V/�<Y�,�R�l��{��m�w��zs�`w7��)�C��)]�	��R�]�������������I����uvR4L<�t���D����[�����������f���N����HE��qxO|���)j���J��]�
.��2�R�0q,2'���SUu"��d��d�;�0������%��|����C	��2B�{-Es�V�90Kq����f�Y�~�������������<):e����S�2%��R���pE�q&TP��l���c���Y�1��,'�-�x��M�"Ur!QX�n�\�T��UJTt��+�z��{+��g���Fj)H��������|��������/aw���h�������M�������R��,�sfA�,�J�+O�B�'g!�i��fa��2���,H����E�^yr�<9yN��_4�}�YP���+�2v\��5o��B�v0����v������qZ�ZD'��	���=J����������R�>,�4+��E�����D6K;�s�����`�v�ggi7��0���qw����K@Oz��l!7mn��v�j;{����^c7��_F�y]��m^%�c��A|���5<2'�(�vM�����vk����C�P��dOF5���x�G�X����P��|�h�l1�Xm;�����V{�������Ty��TA��D�|���]"�	�����R%l���i����N�q�n*==�d���I��u�BWT�2k
���lTv�29����'��BhMUI��L����Y.�����uf����F�������[���S0�M��������e���������S���8L�e�Q����th�T�%L�.��Y1?�l*W�W�y�AV,o���4s�3�x���g9��fp�P��]�����:��K�����G}�r�
��T��*V4c�LN�%�*		?a[1��1�;��pz�E<����H��8������!7��<Q����%+��t
��)�xY�
=��	�a�=K��`!	{N���iZ�����3��B..�^vj!��������&�IXF@��E��S.�@��=M��;�����p{�#�:_u����,��P�.M��S�v��$�rL���d����,��5\D�,�PU��	�N�wrq�*�)��G�;5��e��<�g��(��j�`�\oW�������YjnG����J��j�)]�
A�H�����M4����F�x'�^8�Q�jo����*���"
��5�������8�nhHH9��PE����pk@�A,"���1���K��v�
�)�}9��5�U���	���&�4�����7�&����D��@"��/���9��01B������
��E���^�%�����O�_8���'����'��������J�a���Jb�P�*�#������d[�Y���F���U(��fu\�%���Y���4{��D�N�+2PI��>
�w�]����fF�mY��6	�>f�]NtC��k_	Aq3�|u%�	�f�-�����0~s^L�T�����&�slC%;
D'�G�x�V�L�g_��7�>����S���&��<k�zq��&�cy���2p��Q�_.*L��sf��?�w��1�YG%�Y���*�����UfFsrM�tw�`�P|�qx�':w�>;\��ln�&o8��-���
e�r��	eA}���=���s�c<yw=����������h��1<���1�t��	B�Hk��Jw��zG���Q��:=���.�P�/e���a��S��	�an)Hh�u�g]~��s���C�p�*E���u����'���f!t�TY���IR�T	:�\u���+���8���c [��v�?f����|{��Rz?�K�������5xO���P����8���e������U�E1Y���Yq�Tc{U���5��W�B\h����'�k�!��+�X�[�b�����:�L���F����}I�h�"�� nv��N�j�z�������9��P���/��k�RqN��@��K�m�&� � or,�@X�$�n�fe=��3c~���u5'�����<��H�h��,���g]�g�=%i����?�t��SSs��[p�j�������t<��z�ma��4��gw�����r��L�nr�,ca+1)�����!�A��[bc���0��w]����������[����YW�$��\f�)����|�q���<q��wM�]���&;A"]��PY�JT��["�P�E���e f�B�X/���j1�q�t��P�����fKh�5PXs<QwGHk��J���\o������v)�\�
�������B��y�W��r��� [���%����q�����oM�L�~A�>GJ�;���.�$*J���~s��_�^o�����Vt�P���L���T�����l��!�!���N�8���5���]���xB�m��
��N���x3��s�:�C4R���t��O2�K�h I�9��)�Wd��a�Y��6	8m��|���R��p�!L�X2���l��n�a���nZ��nM�'v�%_BtBW7�p���#
���^��u3\�����f�����T���"���F]yK���F}yk���F=ykg���>a������^J���i<��(#c=R\N�����H4�]�pV�Ix�d*�6�rM\(bo�����i�-��l)���s/����U�7���E�h�K�KI��Rk���x��/�iby�Ta�;�N��V!���5��J-H^�'Dq�/�o��&ZX�XL�av��+/@��
���F���i�|���w���g��E�����@0����\�`5��
3�z�����~W�i���0f�}��2*���t����A��]�O�E��(�Q��8���~>�����
L2b�����������r�Q;@kZ�Nw��p��7���`�6�2�������V���
����^��^���lL��U�l*�^�����l^�B�f
�������g��HO���p��5��	'4X���>��:������m^�Z�o��{C��CtR���TP��|�SUR�p���,)��������'�F<����4h6of
vG����0$����$�r�O���9�A~Ja��*8�$�b4�
T�5�����o��c����!�&�`w���t+T8x�(x��F��2X2PM��Q�8�����o�D�f�������0�x��@��4�0�nQ����I�w��hR@��F��B��pXUGc�*�	�;�t�'
zp��q���5�w="rwC�V:&�&��
[e�T��C��
�{���F�C�����H����vfBV]���`[<*��D�oqA ��B(�����M�X' �����0:8
��~
P��:0�4� ���PXy��A�W�)$���
���{��/��K
���w����|Ow!��{B �8p����|2	�L��hwZU���K��/@����lo��I�Z$�1��^��6��s=d���;E'���{B��z�^��l*|�!�|�������l�=]v3bv��-��E	�(fUF(�qa�����pF��G�E���G�MU�U��q��K��f�/����2����I4��yt�.�J��D
�5
R���vvW����,��R�!�N��K�X���+�;7���w��1��\�: 	@�b��1D�"��	T�|z�fJFl����/�)~��0k����6��i�o0�w]r�u�����0vj^�pj����\(�dY8<���?<K�������NL�1���������\#�����Zwn��$����r���q��y�d`�J��]`XH������� -���9�V>{�<�t+���3$������ �����uo�z;��Mu�fiSU�H�$���[��f�v�!��cy	�������]��<*�zr���zr]�=�D���
Fk�PeD������s�������o v�a����5b�z�ch�d9���\'�%=���.��}j_)���C����Jlww*
0g������hP%.��`	`U 6b�zdY��h^c^~�,p��$�%���TT�Aq��Y���ce���9��v���z�8�e��qt5o�^s����0�{R�K��Qr_a����<�K�����FV�g���m��l�4	�1�N�w��v7}#��Y�����;�=���J��<L,2�z�Qi4��^i�{���8��~�N���R��!�	��������}{�����AA61�>�T�$&G3�j���� ~����
�������9�[�����4�
1������L�`U�3�k��9��3��Uz�
��D��-�X�7cN���~>S��x�QA���4���`��3f\5e0{��E������T�8���Y!�Wa�
�m�pM�ms��4n���9���n�������b����}�G��6����|����;�l"��1�$����6(�r=�1�2���%�5���k r@����K��s�*<�u�Oa�=�=�:����b����)��Y����d@�nC�F���
�&~���o��V���K35o~l�[��#ghq(�.�nA
h|'0t�%"�����8���2iT9:{��R$N� ��XsCu����Cg�����`���5���H�v�,�������W����kWo����g������J���R#����D��rf$��K6��p���~��;e�
�/���U�A������<8�,���
���j��<BU<��=	+���~_��|���$�x�P�4	z��"vrQ��S��UF�~�9IKz���-���q����(�S��@�M�"C7��Yh��C�ZI��k6��a�����pkF��5P�n����9�Z����.%,[�C=.��$���.G�7��K'q����j���z�
)r����+���8	����i���e���L��9��Hi�[�gG�-��W��_��y�
C��v`�����$��2��c����91��J���MG�4��gc�Sc�^iB6}�6�������,��5�8����Q�t�"7t���#<����	��*���\���S<g��5��>z���f6���(;=W6��
H�	��y�:+��qHC���kt��u��������8cq�P�*����`����~O�u����;����U��*���;�)>X
�Q���n%��X	fl2 ��*�B�@7K�U�"��M����������|F���R�����C�q^N3�����zQ5~��������gZW��%�H��U����m��F����e�� ��t����]�[G�����z}yy���N�R��t��;�F7A1��B�0�����RJ�|� �sx�6�0&!�����.��y8C0�@t�z�0���SM.�����W5!�.V�]Q�
��������������J�R���w\u�%u���0'���Mr�:|��h[
�����[���u],�C�>�.j�%s�I4����g�/�@�:�	4aGh��e��;�|��	}�`&���LAaOX�u;V�>7���b=����&V.tV`���pg!�Mf!#�����T��
&0�
X���8M���/� ��c5xY?5���������gd�*�������`8�l)�LjSs��w�Z�'��;U���|���?������>E_��gE{��M{��K�N���z�3}�������q�����'��Q>I|�Q��&O9���J����;���������>����l��H>�7�t�e��y�m�#�C9����X���s9�I������H�e��|��0�g���^�����OrhB��S"�H���P`q��!�OI�h
>�R����>���S��FB
�����~��te�2�,+����z+P���;��A7~�	:�io8��;1�u- ����@
���'���s��T�U����.���',���%��K���.���I����f%�4������ [$	7a��qS�V��EC#E�a�B��.�E��)���S�7!/x6��#����6��OG8��H$m3�V#���6H��[�$Y'X,T5���@�P��T�g
!1����������=6iN��C��.��ZZx��L][e�D4bS������o��_9d����BeH�K�����h��H2��I�����.L*��rel�
��#\93�l�$y��o�'b���-V�k�^>"�OhY�=�*2ZNn����O�K������H��}��^Ev�D��
�IZ�"E`��_<��-���R�����w�`S�91A9h�R��E��o$RP3*z�1�L�I�t���;�������B����u����F�S��m�,����Z*>?D�(��83Q������tX�`���~��%o������l
g�t3��V�9"��MM<����z�6���jS�F���+��B�

���?�Xq���@HI95�/~������c-��z��q��������s�p�#%�����fp�)��������]���A%�*��g�@�C��W����-�71")������"��H����D+$n2���U�����Gp�!�2@���#�y8x���:��e��C�Y��3u�z�Q�qqz��!�6<����l��G��q�J`r�zW�9`u�'��o$��`|c�D�3���H��hq�g��v�Pi�ot���qhT!!;���ra�"B�|�_�\r�:�(�q�N4��u;\���nJ�9&��3�7\�^�";K"BY`G�%n����&jn*0F7 �`G4������������0C'Kp��C\
��Lo1Ih��'L:k���5g��!H�<��������Y�`�;�-	������k��Q�m2�B�\s�0Y84L������,���=�1Xp�A�Fls�]U�����,�C�u�B]� �_��Lb������U�� KB+�GgW(��*���M'C=ut~��*�^7�������[e��<�4������o�-���^�N:��x���	y�G�k���J;��p�	��C��d����`�@����:�G���n���K ����0�J"��it�������L3
�"��K�)���y�y���o>�4	R�SE_�x��`W�S���Xp!R$��7"�g'���������qJFQ�,Q�+�sp3��q���<(���4�4����8I����
�25-��c�rV)�\��S]�Vn���n���(\L<-���Q.��d�-��zc�5�w}"����*���z�,�Y,�G�eq���������T�����y&i�R��d��0K�[�d��T�O�Jw�;}�l����#��XRG�r�!{������d�h���v�bJR��7l�J��=�k�C���8����xY��Q��}������G�[������#@;��D;�n:!]MW�%~��/����{�F7���O+���JL���J��_@*��g�������JS���|?a��t�M�$Z�����*�UO_Y+j��F�|T�_c�������K��@v���DP���%����,P'��7��8�"Y��HJl5)b��_~*@�*�I����a��\Y9I3KC���O`S�0�jz�B�7
�+��08��&��PI�:7�yL�;��
XG�L��(T�l�A��y���'�q�4�����u�]��9u���H^��?�;����M��T�j��m�*I�v����U ��)�4Uf^�x/�����H���@�(:d6\�'��I�[�&���Fv�o����Q9��d~��^bK,3�kjB�M
99���TY��"^=�#��TXx&�|t!���1s���c��P��������GC����[�����9�&`H��5��L�@����j���`-P�}�G�`6 R��[�,S���"Z?�b+))2����&i=�g������[2/�8Cfa��i��Y]�R�t#���'�[���Tz�2�����n9*&�t��zv�NQ�h�����6eA���p;
F���x	T���zoo��Z
���A�`i@n����)3S�23583��Ax'����<�������O����~�Q,��o�k�@���,�v�4x�n�NJ[.8�W���Z�m�E�T��I�����A!-���9�G�P���cz�04a0~�=idZ0%�T!��.]}k�Y��n��{ f'�.���#b��1��u���
���r����������WG�@������Wr{{uz����^�~�iG��y�Gxz�K�����T�@QHVv/������tW�_L�x_^�������:o���w���q�u�i�<����}q�2%������k~���]�<=��A*~~q�����J�A�\�@���
>����o����m�������z2���������
	F�!�������XDA���{�?7��Q.?� �����:O�G����v8t@����[O�?gA~`����uVN��T�P7
���8�:�)�Q�<�p�����2�8�[N���������n�S����J���9l� <QO��LT������oO�6���{��:�^��;����,����K����`,�.�2��������o�/�U����B��d���/��){E�SXuYy���6m�V9������~��F;s����g �D����r<\���m�l}� �5���1�tK �/�����Ra���}�S:T���D���v��~��G�%�o$Y��O,c�s�f7H���!j�G}05����(qL�M�~�.��+�6�7��jvOO���[�rG������j�Ek��	rH�U�,Ya�D}�8�\����D�a���uK���@���/�O����>�z���=�aJjL�zu����e��O�
���^q�S_i�K����M_\�fTxh78���8�^������v���dEx����U��8���"�&{w]���5{��%'����2�9N
��z��J����O��e�y,�np�S'4��Jc,$��}�{����������kf��ej;�N��lQ��c�Y��3L��=���]j�I
x�.�8+���1�]C����'����QA���WmK]	��9�8B�`��z���O��|���p�>�w9���������'����������x��4��EW�G�-������������f�������1�����2��m(5��J	qXR��.b������S�_2��}Z�����e�l_�N��J<�~~}��� �
�qtnF���������K����)/��_����^����V�oao�z�<#�K�\#Q���$���4v��p�RY�m���x�n0I1$�;��N�R3�6V��m��(��0�5}=�����F,�C�5##�r
6�����#C[�P�z��wa0�����'�c��k$1;�bT}�N�k�$�EK�"����S�o�#�n�/
���r�����;����1��p<�k~�A�������P��^�3���L7a��0B'�_��q��)���!�=D�������R-������@�ioLxU|��q���t���,�RTXE<�{(���t0��G4�J�3���G��s����W��0�m7d���(}:s�"��o��?�1eC��Qzx7�*�=H�T��������pl3dd�%�Z���``�!fn�0p�������Mo
8�3��E�X��?j`k ��3���n9��z������Q�G��C����5���Q��?fz>�s3cdn�v)50�x&���h2�M�P+�����S���<��=L1~��*B-�]b1K�gM���h8M�u\��������J�,���9f����t�7��+)��B��|����� ��3'���?�e�p��~��3g�"������.s?�V=b�Wi����q�NgO��7�����^���m�q��#^eL�6���8�?���w��R��B�QL�#�w�2`0�fkc�K&Q�u��WgpG� ��������7p�O�p�?E��)T)w�g��H���`H1B�l\m�$@��<|u����(y	�Q�M�8C�m���5�J��L�,QH�i�8�3ph�=�d$3�{o2
����y�3��zrT��"T.���#3)�5
{Y��DOK��F��0
�_����0�V�����D��1u@��Xb!{�c�s�l�c�!�HUI�LG�:�� �F�����������7~0_"��x���o� ��XT`�(����+�hM0 �|�(a���T[��dzy�<c��h�,��*���|�t�(��H�iQXs������9q�lX�2f]�2Q���{����/���!�iY�o��a����7��������1�`����`�7�jm�����0��	�t��4��.I��7E�|�5�5��L2���!��3W����b|4`���l�,k�p����O����!{����6+��X�m���A�����8��?C��-�#X�$����. �M��C�G�}9)�v�J<����d�^����A�&0���A�F|�a���P6��k��������z`�Dm�0t0�
�C�r		���rn�E��@������q=I�,/+�9���B���w����q��}�����,�>]��h�i
��6��LE}Rf� t�q�rkF{7p#�z'd�?21e�8�f��
 �@���K=8
KK:#3`�A���Y��C�`#
��`V����v)XNC���7�;���k����.���ufS�<�l�g���]��]2&��
�T7�>�u��������4wS{�F�*S���F����������9Ye�c�(<��5����NpG�qI0�7lh���X�&�L���1��r4�Muo���-J�4�������~��2i�>nc��#.�%w��a4+���Y�&G�A	*z �uz�Y���4�M�(�����2�T��h���l��2��1-O����R\�����A�v���G������Y8Q�|9��M�&d:�.{e,Q�����j���5���|���:-F��)�2���d�zId$M�R�Ejs]���x2��1�����V����KMC�^�w;G/��_��:��-G^�3����z�6�$�{��BV�q���������x F,����G�eC�Be+e�
�����M{���������4����
S(:����Nl�5\���NO4��[%Y����c�4�1x]��n���I�e��h�'�CB���O2w��O_�^}��{s�>?=��@�a/������ �`$��6��]�{���Y�����J�a}��Z�����>���VY��;��$r3�w!G��)L/����y��:V������d���|�/���,�=Cw���f�i{LR����a
��z=�.^^u^�����B��`�����f�5�W�m8��1 �W���^P����z�vS����.��YPI��X��7��\�XaRJ��}~�a"�C��b��!{�TJ�7�z���C�Y�D�N��}�^7�c�]3]����<t�|;���}V����V���n���Z>5����k����}�&][\5�B4���[�~��,N�%���u�����F#lj�&�t���	���0���q3�����5~���;k����S��;%�FI��0]�B 6cg��d������B�2�D���5��}�������Kk��������Wj�x��I�{���lo{��o������Q��0���!
]�:di�s�
�l40���5�U��;S�������2��@!�]F�"� ��������ju��^��Z�?c_�G8y�0�]�\*��U-����!
����_9�.���)*B@��o����^�P�|Sg�?��
jY4�!:�Kvad��C��2y`
�[
���	�#��	�H����V��
~�,�����V���5�:���T�!�(�|����1}P6���l��)	�n�
���s� �'Qt�00
�����?!y�9wI^6�Ro8$����*����M��8��q�����M4��@B��Lc:
���E'�?����N�F�m��v����t��:����u	
���{P��|$��OF���Ej�
�����;`\Si��T��TZ��S��z=6���Q09�*0&r���[aL�OOU���P+���$V�7D����Y�u
������o��Ro.9�Y�����`2�X	(Z��73�3g���X-�r3������S"9����+��3�9U��y���Oj���+����.��W-�i��Y��u�H���W"��:���4jd�-h��e+��Z1�o��:�8������0�?���D�%������(�+2����i��
��7��r���U���l=@�����}���GNN<��8�U���2��|4z0��gb���2r����]�L��V`��o�{7��j5���{t��x]���q��D'_H�#���q-)Hx����n�L��a'�][�.NOb������}?�CO*q���z�g����\�6�%��P��H{n�|$"� �p���md��'�d�l��
�j�������r�f
#d[��Dj�������X��X��3�*+�Z�����I����Mt�����$��i
�u���]�i������6�6��lYsc���������v�����0*"?���~����P��}���<r�\x�Na�^(@5��r����CE�R���.�Y�h`X��������P���>�H����b@B��c+���$&�
f�(N��a�n���Q+p�@
w�-��q�`�^h���}j&����MY��fKh����#Ib`�Cn�3�@��M���,iSt\�K�1Q��b�90h���[\����0��4fa�5���S960__���hOYEr��%�k�2�������?����S�,PN�5��h�Ia��Dt�\����. �n���YPZc�b���,��~T�]�@�?4��5^X�NRa�[�en����I0BU��3��$%�����t��C[��8���N�;�!�pY�fH1o�����Rn7H�-�+����T���v���>�Vl������t��P-�Ul9�*���u�v����fvf�=�R*N���dC�S�`�%V���$R�K�Ix����nu^��A�Hi��������M���iK�"�
)�)�,!�J8ywL�5<=���?	i�&Jr�,�1���A�8�Z����fV�5p�s<�0q����I%�@_e��Gt�x}�bOVaE�;�����9F<�<T�>�M*�hja��>\��;�C��������"Og.������)A���E���}Na�p'���&:O�_��"�����[����&Q�������Z�������Sy��� Yi�vDp�~���? '%�B�� }#Y�`��<��"�[�Q�Q�fe�Q�	�p�zh�R��Z8���Z�+E��O�4t�I���x0�������K����}>bM"O�w^�r|	���T���H��_����+���(,�s��SV2E�uG��k� Qsw>�z����0��Vlrf�)���Q.�������������K���(e�<�������Fm����a3�>
���M��������e���oo.�'��p���^�o�n�5��j��:����d���d�������Z�t/+ji��+�gyv�^}�L�y��2��LBh�.����lk�����eg���]�����R��{O��r/���ug�?�4�at;c��_������
{���F�;�B3+l�S�b%�`�
lD����	�64AQ�
o��3l�1VkcF��Vi!4@4��p�����A��N����ax������/�g����^�6G}H�-%.�#Z�N�U&�n��b���mb�������X�|�m���	��j���|�u1���������~�
n��fT��;�7;���� �K�����7�Y�jq/���Ya������8�� >>�� u;�U��
d�]QG��+�����Ic����`g�c��5�<31�6:���6��cnC+��8Y�4�9�&
o�2��z��w��� '��*�� ..��`����Z������:B�V?|���Y���Z�������km8'����_5Ru�������8�@����h����A�
�.��a�n�������zE/��:��j,����&��S&�$7��C���
������������^��
����M���?�M���=�yU��E����i8��]�x�	�aS5��p��m�K�t9�����+r|�2���ZGWJ���\����A�U����A���M'�����n%�7tS��)p���"���B���8���W;��JTifmg��vEmgl����|;���o�h;���vj;�x$��
����h�%jp=k�[�~;��������|;��j_��=?:��p���v����������
dA��]��_�ji�����E�T3�C��AO����4h��<
��.8.�y4����`�7=0���e�-b�^�~��,�a}V���d�����P�J���M
�I&h�8O�~����|��.���%_\iP������Kj/���^��>9��%����$�s�m����~0�� T
�
��H�����	4�e��N���4k�j���)X��'�+P�uv��%7�|2
z��������#��x�)g�7��u3k����j�hb�S�Q���$Ac�����6M�<��I��>6�d��D��<l{#�I�1��������>�!)a�0����V�;J/���B�O�����/k�K]�|f��e�&A��C��-��`��V��v�3��@>:�_�
P��D�#ghc����h���g��x����
h�e��q4��Q?.�+�}2��D�1��8R�L���������\��>�1x1/A�
�hWP��_�.��D��M�����R�,+N~�J!���+��	�)��	�4���9* 	��P]��sh�eftPR=A73J����jM����x�n�zW�t�_�e��N����(��[
AJ	}�$���:�������D#uf*�;�K/c5����� i�p"��;p�R�
{� 3����O !�{�4h��F�*j�9S�9�����`p0]L����[���4Z�}yP������P����c��U0�
���I�!J�r�LSSUH�h��~�e���a�@�)������l��xL\�q?�n2k�{��*��A!�PBNt���mZ�&L��0�\ZJ)p�C
9��w�8|���\���or=m��HO�[�������-���C�f6�
w�0��$�����.�_�eG+�������J���=N"�X���:}��a7mD��P��*I��z�~V�����u#Hk"�t��NJWD�oz�6*����f{?�!R�t����`�QD�W�"���F8�=��yO]�?V4����hM�M�?��M���!��>�������9��-i(�����F�o�pB���E�N���k����XY��8Vm�4306�=�����'�0,�j�(�1�&j�Abh	u����ZN=:
ch�'a�42��%3�X!��$>���D�zH
��b7� ���w\ <�
�6�;���O����Vc`3
H7����h?j�g<P��x~s3���3����j�5�E�m�q�CM�/>l�*�������p��wdx�/�$��%�Q��fe���� M�,���Y_y�,���E�"��"@(k�n����{a�_k��i���]Ev��U��U��_�q��������F�[���
����/S;��v}��������/�4}��}�[��N��U�P�_b��b_uU_�.}����R��R���T�?_���}�����������_���}�����F�k�
#71Peter Eisentraut
peter_e@gmx.net
In reply to: Heikki Linnakangas (#53)
Re: MERGE Specification

On fre, 2010-08-06 at 10:28 +0300, Heikki Linnakangas wrote:

IMO the UPDATE/DELETE/INSERT actions should fire the respective
statement level triggers, but the MERGE itself should not.

Yes, SQL defines the triggering of triggers as part of the modification
of rows, not as part of any particular statement that causes the
modification.

#72Peter Eisentraut
peter_e@gmx.net
In reply to: Simon Riggs (#52)
Re: MERGE Specification

On fre, 2010-08-06 at 08:12 +0100, Simon Riggs wrote:

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.

I'm not actually attending any (further) meetings, because no one has
agreed to fund it yet.

#73Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#70)
1 attachment(s)
Re: MERGE Specification

On 11/08/10 11:11, Boxuan Zhai wrote:

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)

Thanks. I went through this, fixing all the spurious end-of-line
whitespace first with "git apply --whitespace=fix", and then manually
fixing comment and whitespace style, typos, and doing some other small
comment editing. Resulting patch attached. It is also available at my
git repository at
git://git.postgresql.org/git/users/heikki/postgres.git, branch
'mergestmt'. Please base any further patch versions on this patch, so
that we don't need to redo this cleanup.

I'll continue reviewing this sometime next week, but here's few
miscellaneous issues for starters;

* Explain output of actions needs some work:

Merge (cost=246.37..272.96 rows=1770 width=38)
ACTION: UPDATE WHEN NOT MATCHED
ACTION: INSERT WHEN NOT MATCHED
-> Merge Left Join (cost=246.37..272.96 rows=1770 width=38)

Should print out more details of the action, like for normal
updates/inserts/deletes. And all uppercase doesn't fit the surrounding
style.

* Is the behavior now SQL-compliant? We had long discussions about the
default action being insert or do nothing or raise error, but I never
got a clear picture of what the SQL spec says and whether we're compliant.

* What does the "one tuple is error" notice mean? We'll have to do
something about that.. I don't think we've properly thought through the
meaning of RAISE ERROR. Let's cut it out from the patch until then. It's
only a few dozen lines to put back when we know what to do about it.

* Do you need the MergeInsert/MergeUpdate/MergeDelete alias nodes? Would
it be simpler to just use InsertStmt/UpdateStmt/DeleteStmt directly?

* Do you need the out-function for DeleteStmt? Why not for UpdateStmt
and InsertStmt?

* I wonder if it would be simpler to check the fact that you can only
have UPDATE/DELETE as a WHEN MATCHED action and only INSERT as a WHEN
NOT MATCHED action directly in the grammar?

* Regarding this speculation in the MergeStmt grammar rule:

/*although we have only one USING table,
we still make it a list, maybe in future
we will allow multiple USING tables.*/

I wonder what the semantics of having multiple USING tables would be? If
it would be like "USING (SELECT * FROM a UNION ALL SELECT * FROM b)",
then we don't ever need it because you can already achieve it with that
subquery. If it's something like "USING (SELECT * FROM a,b WHERE ...)",
then we again don't need it because you can write that instead. So I
think we should give up on the notion that source can be a list of
tables, and simplify the code everywhere accordingly.

* Instead of scanning the list of actions in ExecBS/ExecASMergeTriggers
every time, should set flags at plan time to mark what kind of actions
there is in the statement. Or should we defer firing the statement
triggers until we hit the first matching row and execute the action for
the first time?

* Do we need the 'replaced' field? Could you just replace the action
with a DO NOTHING action instead.

* This crashes:

postgres=# CREATE TABLE target AS (SELECT 1 AS id);
SELECT 1
postgres=# MERGE into target t
USING (select 1 AS id) AS s
ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET id = (SELECT COUNT(*) FROM generate_series(1,10))
;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

merge_v102-cleanedup.patchtext/x-diff; name=merge_v102-cleanedup.patchDownload
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..7c73623
--- /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 &mdash; 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 &mdash; 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;
    &notify;
    &prepare;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..84edeba 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,7 @@ 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 +1488,65 @@ 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");
+		}
+
+		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..df2a2ab 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2342,6 +2342,95 @@ 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;
+
+	/* Scan the actions to see what kind of statements there is */
+	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;
+	}
+
+	/* And 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);
+}
+
+void
+ExecASMergeTriggers(ModifyTableState *mt_state)
+{
+	ListCell *l;
+
+	bool doUpdateTriggers = false;
+	bool doInsertTriggers = false;
+	bool doDeleteTriggers = false;
+
+	/* Scan the actions to see what kind of statements there is */
+	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;
+	}
+
+	/* And 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);
+}
 
 static HeapTuple
 GetTupleForTrigger(EState *estate,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b34a154..2b7ceb1 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..ff691c7 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..c5404d0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -582,6 +582,123 @@ 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;
+	ListCell *each;
+
+	/*
+	 * Try the merge actions one by one until we have a match.
+	 */
+	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;
+
+		/*
+		 * Check that additional quals match, if any.
+		 */
+		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 caught 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;
+
+		/* Ok, we have a match. Perform the action */
+
+		/* First project any RETURNING result tuple slot, if needed */
+		if (action_pstate->operation == CMD_INSERT ||
+			action_pstate->operation == CMD_UPDATE)
+			actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo,
+										   slot, planSlot);
+
+		switch (action_pstate->operation)
+		{
+			case CMD_INSERT:
+				return ExecInsert(actslot, planSlot, estate);
+
+			case CMD_UPDATE:
+				return ExecUpdate(tupleid,
+								  actslot,
+								  planSlot,
+								  &mt_pstate->mt_epqstate,
+								  estate);
+
+			case CMD_DELETE:
+				return ExecDelete(tupleid,
+								  planSlot,
+								  &mt_pstate->mt_epqstate,
+								  estate);
+
+			case CMD_DONOTHING:
+				return NULL;
+
+			case CMD_RAISEERR:
+				return MergeRaiseErr();
+
+			default:
+				elog(ERROR, "unknown merge action type for excute");
+				break;
+		}
+	}
+
+	/*
+	 * No matching action found. Perform the default action, which is
+	 * RAISE ERROR.
+	 */
+	return MergeRaiseErr();
+}
 
 /*
  * Process BEFORE EACH STATEMENT triggers
@@ -603,6 +720,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 +749,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 +831,32 @@ 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");
+				{
+					/*
+					 * Shouldn't ever get a null result for UPDATE or DELETE.
+					 * MERGE gets 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;
+					tupleid = (ItemPointer) DatumGetPointer(datum);
+					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
+					tupleid = &tuple_ctid;
+				}
 			}
 
 			/*
@@ -744,6 +879,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 +910,69 @@ 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);
+
+	/*
+	 * XXX: do we need a check for the plan output here ?
+	 * (by calling the ExecCheckPlanOutput() function
+	 */
+
+	return result;
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -786,6 +988,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 +1029,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 junkfilter.
+		 *	junkfilter 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 +1168,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 +1196,10 @@ 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 +1225,18 @@ 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..daebaf4 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,14 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 	COPY_NODE_FIELD(constraintDeps);
+	COPY_SCALAR_FIELD(isMergeAction);
+	COPY_SCALAR_FIELD(replaced);
+	COPY_NODE_FIELD(mergeActQry);
+
+	COPY_SCALAR_FIELD(isMergeAction);
+	COPY_SCALAR_FIELD(replaced);
+	/*merge action list*/
+	COPY_NODE_FIELD(mergeActQry);
 
 	return newnode;
 }
@@ -2344,6 +2353,58 @@ _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 +3966,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..fb27e16 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,7 +878,13 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
 	COMPARE_NODE_FIELD(constraintDeps);
+	COMPARE_SCALAR_FIELD(isMergeAction);
+	COMPARE_SCALAR_FIELD(replaced);
+	COMPARE_NODE_FIELD(mergeActQry);
 
+	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..cb4580c 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..ead65cd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -102,6 +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 +571,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											 returningLists,
 											 rowMarks,
 											 SS_assign_special_param(root));
+
+			/*
+			 * Create a simple plan for each action in the merge command.
+			 * Put them in mergeActPlan list;
+			 */
+			merge_action_list_planner(glob, parse, (ModifyTable *) plan);
 		}
 	}
 
@@ -584,6 +596,133 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	return plan;
 }
 
+static void
+merge_action_list_planner(PlannerGlobal *glob, Query *parse,
+						  ModifyTable *mainplan)
+{
+	ListCell *l;
+	Plan *topplan;
+
+	/* 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;
+
+	topplan = (Plan *) linitial(mainplan->plans);
+
+	/* plan each action query */
+	foreach(l, parse->mergeActQry)
+	{
+		Query *actqry = (Query *) lfirst(l);
+		Plan *actplan;
+
+		actplan = (Plan *) merge_action_planner(glob, actqry, topplan);
+
+		mainplan->mergeActPlan = lappend(mainplan->mergeActPlan, actplan);
+	}
+}
+
+/* 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,
+							  list_make1_int(parse->resultRelation),
+							  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..5ddc9bb 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,22 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 								  result_relation, range_table);
 
 	/*
-	 * for "update" and "delete" queries, add ctid of the result relation into
+	 * 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 expand 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..45df458 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,86 @@ 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 opposite 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 assigmment
+	 *	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..c05ee61 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;
@@ -245,6 +253,7 @@ analyze_requires_snapshot(Node *parseTree)
 		case T_DeleteStmt:
 		case T_UpdateStmt:
 		case T_SelectStmt:
+		case T_MergeStmt:
 			result = true;
 			break;
 
@@ -282,21 +291,27 @@ 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.
+	 * The input stmt could be a MergeDelete node.
+	 * In this case, we don't need the process on range table.
 	 */
-	transformFromClause(pstate, stmt->usingClause);
+	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");
 
@@ -347,6 +362,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 statement is always a VALUES clause*/
 	isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
 	/*
@@ -382,7 +399,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,10 +1748,13 @@ 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
@@ -2241,3 +2262,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;
+
+	/*
+	 * First, 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 transformation 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 transforming the FROM clause, acquire write lock on the
+	 * target relation. We don't want to add it to the range table yet,
+	 * so we use setTargetTableLock() instead of setTargetTable().
+	 */
+	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 condition */
+	joinexp->quals = stmt->matchCondition;
+
+	/*
+	 * transform the FROM clause. The target relation and
+	 * source relation will be added to the range table 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 a 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)
+		{
+			/* found it */
+			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. Now transform the actions.
+	 *
+	 * First, we check the last action of the action list.
+	 * If it is not a DO NOTHING 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 action, transform it to a seperate query.
+	 * The action queries share the range table with the main query.
+	 *
+	 * In other words, in the extra conditions 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..d619649 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -211,6 +211,11 @@ static TypeName *TableFuncTypeName(List *columns);
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
+		MergeStmt
+
+%type <node>	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
@@ -480,7 +485,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
 	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
 
-	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
+	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
@@ -503,7 +508,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
 	LOCATION LOCK_P LOGIN_P
 
-	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAPPING MATCH MATCHED MAXVALUE MERGE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
@@ -518,7 +523,7 @@ static TypeName *TableFuncTypeName(List *columns);
 
 	QUOTE
 
-	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
+	RAISE RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
 	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
 	RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
 
@@ -726,6 +731,7 @@ stmt :
 			| ListenStmt
 			| LoadStmt
 			| LockStmt
+			| MergeStmt
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -6986,6 +6992,7 @@ ExplainableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
+			| MergeStmt
 			| DeclareCursorStmt
 			| CreateAsStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
@@ -7331,6 +7338,113 @@ 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 multiple 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 +11066,7 @@ unreserved_keyword:
 			| ENCODING
 			| ENCRYPTED
 			| ENUM_P
+			| ERROR_P
 			| ESCAPE
 			| EXCLUDE
 			| EXCLUDING
@@ -11005,7 +11120,9 @@ unreserved_keyword:
 			| LOGIN_P
 			| MAPPING
 			| MATCH
+			| MATCHED
 			| MAXVALUE
+			| MERGE
 			| MINUTE_P
 			| MINVALUE
 			| MODE
@@ -11048,6 +11165,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..c3e8038 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..b54004b 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, we have a set of action queries (not subquery).
+		 * each of these action queries should rewritten with 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 empty, 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 rewritten 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..0ee3074 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..4fe7dc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1032,9 +1032,20 @@ 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; /* list of ModifyTableStates of MERGE actions. */
 } 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..10b7b37 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..4bd613f 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 */
+
+	/* fields 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 statement */
 } Query;
 
 
@@ -993,6 +1000,62 @@ typedef struct SelectStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
+/* ----------------------
+ *		Merge Statement
+ * ----------------------
+ */
+typedef struct MergeStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* target 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;			/* WHEN MATCHED or WHEN NOT MATCHED? */
+	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..1187d61 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;		/* is this action replaced by INSTEAD rules? */
+	CmdType		operation;		/* INSERT, UPDATE, DELETE, DO_NOTHING or RAISE_ERR */
+	bool	 	matched;		/* is this a MATCHED or NOT MATCHED rule? */
+	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..ec773f4 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,6 @@ 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..bd49a1b
--- /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
+;
#74Alvaro Herrera
alvherre@commandprompt.com
In reply to: Peter Eisentraut (#72)
Re: MERGE Specification

Excerpts from Peter Eisentraut's message of mié ago 11 11:23:19 -0400 2010:

On fre, 2010-08-06 at 08:12 +0100, Simon Riggs wrote:

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.

I'm not actually attending any (further) meetings, because no one has
agreed to fund it yet.

Eh? Surely we have enough money for this in the SPI account.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#75Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Heikki Linnakangas (#73)
Re: MERGE Specification

On Sun, Aug 15, 2010 at 4:05 AM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

On 11/08/10 11:11, Boxuan Zhai wrote:

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)

Thanks. I went through this, fixing all the spurious end-of-line whitespace
first with "git apply --whitespace=fix", and then manually fixing comment
and whitespace style, typos, and doing some other small comment editing.
Resulting patch attached. It is also available at my git repository at
git://git.postgresql.org/git/users/heikki/postgres.git, branch
'mergestmt'. Please base any further patch versions on this patch, so that
we don't need to redo this cleanup.

Thanks for the cleanup. The codes looks better now. My problem is that I
have done some more modifications after merge_v102. Is there any way to
apply the cleanup patch without erasing my new codes?

I'll continue reviewing this sometime next week, but here's few
miscellaneous issues for starters;

* Explain output of actions needs some work:

Merge (cost=246.37..272.96 rows=1770 width=38)
ACTION: UPDATE WHEN NOT MATCHED
ACTION: INSERT WHEN NOT MATCHED
-> Merge Left Join (cost=246.37..272.96 rows=1770 width=38)

Should print out more details of the action, like for normal
updates/inserts/deletes.

Well, I think, in normal UPDATE/INSERT/DELETE explanation, there are no more
details than what we have here except the costs, rows and width, which is
print out at the MERGE command line.

For example:

Explain
update buy set volume = volume + 1;
QUERY PLAN
--------------------------------------------------------------
Update (cost=0.00..36.75 rows=2140 width=14)
-> Seq Scan on buy (cost=0.00..36.75 rows=2140 width=14)
(2 rows)

For the explanation of an UPDATE command, we only have a Update title
followed by the costs, then, after the arrow -> there comes the plan tree.
In a MERGE command, no action has its real private plan. They all share the
main plan. Thus, the costs for a merge command is that of the main plan.
What is useful for a merge action is only the target list and quals. So my
design is to show the merge command costs in first line. Then print out the
actions and their qualifications in a list, followed by the main plan tree.

Is there any other thing you suggest to print out for each action?

And all uppercase doesn't fit the surrounding style.

This will be fixed.

* Is the behavior now SQL-compliant? We had long discussions about the
default action being insert or do nothing or raise error, but I never got a
clear picture of what the SQL spec says and whether we're compliant.

* What does the "one tuple is error" notice mean? We'll have to do
something about that.. I don't think we've properly thought through the
meaning of RAISE ERROR. Let's cut it out from the patch until then. It's
only a few dozen lines to put back when we know what to do about it.

I find that we have not reached an agreement on MERGE's syntax yet.
Personally, I support Simon's idea, that is the default action should be
RAISE ERROR. However, I am no sure what RAISE ERROR should do when we
get a missing tuple. Here I just use a simple elog(NOTICE, "a tuple is
error"); for this situation.

I leave this for further extension when a more specific design for RAISE
ERROR is available.

Well, I have to say the current RAISE ERROR elog is a little bit ugly. Do
you want me to chage the default action back to DO NOTHING? Or any other
suggetions? (In fact, my personal thinking is to add a non-omissible "ELSE"
clause as the end of the action list which forces the user to specify the
default action for merge).

* Do you need the MergeInsert/MergeUpdate/MergeDelete alias nodes? Would it
be simpler to just use InsertStmt/UpdateStmt/DeleteStmt directly?

I need one flag in these statement to differentiate them from normal
InsertStmt/UpdateStmt/DeleteStmt. There are slight difference for the
process of these two kinds of statements in the transfromStmt() function. I
define a set of alias nodes which have different nodetags. This can make
the code simpler.

* Do you need the out-function for DeleteStmt? Why not for UpdateStmt and
InsertStmt?

Ah, I add this function because I want to have a look of the content of
DeleteStmt. I did this long ago, just after I started the project. If you
don't want this function, I will remove it.

* I wonder if it would be simpler to check the fact that you can only have
UPDATE/DELETE as a WHEN MATCHED action and only INSERT as a WHEN NOT MATCHED
action directly in the grammar?

We can do this, but, I think it is better to keep it as it is now.

* Regarding this speculation in the MergeStmt grammar rule:

/*although we have only one USING table,
we still make it a list, maybe in future
we will allow multiple USING tables.*/

I wonder what the semantics of having multiple USING tables would be? If it
would be like "USING (SELECT * FROM a UNION ALL SELECT * FROM b)", then we
don't ever need it because you can already achieve it with that subquery. If
it's something like "USING (SELECT * FROM a,b WHERE ...)", then we again
don't need it because you can write that instead. So I think we should give
up on the notion that source can be a list of tables, and simplify the code
everywhere accordingly.

Yes, we should give up it. I was considering to allow the user input
multiple source tables as a short way of "USING (SELECT * FROM a,b WHERE
...)". Now, this idea seems not interesting at all.

* Instead of scanning the list of actions in ExecBS/ExecASMergeTriggers
every time, should set flags at plan time to mark what kind of actions there
is in the statement.

First of all, I need to say that the functions of ExecBSMergeTriggers() and
ExecASMergeTriggers() are for per-statement triggers, and are invoked only
once in the whole process of a MERGE command. Setting flags at plan time can
save the scanning. I may add it to next patch. But don't expect it save
much execution time.

Or should we defer firing the statement triggers until we hit the first
matching row and execute the action for the first time?

No, we cannot do this. These are Per-statement triggers. They should be
fired before/after the main plan is executed. The statement triggers are
fired even no tuple is really matched with the actions.

* Do we need the 'replaced' field? Could you just replace the action with a
DO NOTHING action instead.

Yes, I think it is a good idea to replace them with DO NOTHING. The replaced
field was there before DO NOTHING is added to the system. But since we
have DO NOTING action now, we can turn all the actions replaced by
INSTEAD rules into DO NOTINGs.

* This crashes:

postgres=# CREATE TABLE target AS (SELECT 1 AS id);
SELECT 1
postgres=# MERGE into target t
USING (select 1 AS id) AS s

ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET id = (SELECT COUNT(*) FROM generate_series(1,10))
;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Oh, a really serious bug. I have not considered this case (user series
generator in actions) before. I will see what I can do for it.

Show quoted text

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#76Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#75)
Re: MERGE Specification

On 15/08/10 10:31, Boxuan Zhai wrote:

On Sun, Aug 15, 2010 at 4:05 AM, Heikki Linnakangas<
heikki.linnakangas@enterprisedb.com> wrote:

Thanks. I went through this, fixing all the spurious end-of-line whitespace
first with "git apply --whitespace=fix", and then manually fixing comment
and whitespace style, typos, and doing some other small comment editing.
Resulting patch attached. It is also available at my git repository at
git://git.postgresql.org/git/users/heikki/postgres.git, branch
'mergestmt'. Please base any further patch versions on this patch, so that
we don't need to redo this cleanup.

Thanks for the cleanup. The codes looks better now. My problem is that I
have done some more modifications after merge_v102. Is there any way to
apply the cleanup patch without erasing my new codes?

Yeah, there's multiple ways. You can create a patch of what you have
now, and run interdiff against your previous patch that I worked
against. That gives you a diff of changes since the last patch you sent
to the list. You can then apply that patch to the codetree from my git
repository.

Or you can just generate a new patch of what you have as usual, and I'll
incorporate the changes to the cleaned-up patch.

I'll continue reviewing this sometime next week, but here's few
miscellaneous issues for starters;

* Explain output of actions needs some work:

Merge (cost=246.37..272.96 rows=1770 width=38)
ACTION: UPDATE WHEN NOT MATCHED
ACTION: INSERT WHEN NOT MATCHED
-> Merge Left Join (cost=246.37..272.96 rows=1770 width=38)

Should print out more details of the action, like for normal
updates/inserts/deletes.

Well, I think, in normal UPDATE/INSERT/DELETE explanation, there are no more
details than what we have here except the costs, rows and width, which is
print out at the MERGE command line.

For example:

Explain
update buy set volume = volume + 1;
QUERY PLAN
--------------------------------------------------------------
Update (cost=0.00..36.75 rows=2140 width=14)
-> Seq Scan on buy (cost=0.00..36.75 rows=2140 width=14)
(2 rows)

For the explanation of an UPDATE command, we only have a Update title
followed by the costs, then, after the arrow -> there comes the plan tree.
In a MERGE command, no action has its real private plan. They all share the
main plan. Thus, the costs for a merge command is that of the main plan.
What is useful for a merge action is only the target list and quals. So my
design is to show the merge command costs in first line. Then print out the
actions and their qualifications in a list, followed by the main plan tree.

It's more more interesting with more complex statement:

postgres=# explain UPDATE target SET id = (SELECT COUNT(*) FROM
generate_series(1,10));
QUERY PLAN

--------------------------------------------------------------------------------------
Update (cost=12.51..52.52 rows=2400 width=6)
InitPlan 1 (returns $0)
-> Aggregate (cost=12.50..12.51 rows=1 width=0)
-> Function Scan on generate_series (cost=0.00..10.00
rows=1000 width=0)
-> Seq Scan on target (cost=0.00..40.00 rows=2400 width=6)
(5 rows)

Is there any other thing you suggest to print out for each action?

It should match the output of a normal Update/Insert as closely as possible.

* Is the behavior now SQL-compliant? We had long discussions about the
default action being insert or do nothing or raise error, but I never got a
clear picture of what the SQL spec says and whether we're compliant.

* What does the "one tuple is error" notice mean? We'll have to do
something about that.. I don't think we've properly thought through the
meaning of RAISE ERROR. Let's cut it out from the patch until then. It's
only a few dozen lines to put back when we know what to do about it.

I find that we have not reached an agreement on MERGE's syntax yet.
Personally, I support Simon's idea, that is the default action should be
RAISE ERROR. However, I am no sure what RAISE ERROR should do when we
get a missing tuple. Here I just use a simple elog(NOTICE, "a tuple is
error"); for this situation.

I leave this for further extension when a more specific design for RAISE
ERROR is available.

Well, I have to say the current RAISE ERROR elog is a little bit ugly. Do
you want me to chage the default action back to DO NOTHING? Or any other
suggetions? (In fact, my personal thinking is to add a non-omissible "ELSE"
clause as the end of the action list which forces the user to specify the
default action for merge).

Whatever the spec says is what we should do.

* Do you need the MergeInsert/MergeUpdate/MergeDelete alias nodes? Would it
be simpler to just use InsertStmt/UpdateStmt/DeleteStmt directly?

I need one flag in these statement to differentiate them from normal
InsertStmt/UpdateStmt/DeleteStmt. There are slight difference for the
process of these two kinds of statements in the transfromStmt() function. I
define a set of alias nodes which have different nodetags. This can make
the code simpler.

Ok. You might also consider just adding a "isMergeAction" boolean to
InsertStmt/UpdateStmt/DeleteStmt instead, or if the difference between
the regular statements and merge actions grow big, create a completely
separate node struct for them.

* Do you need the out-function for DeleteStmt? Why not for UpdateStmt and
InsertStmt?

Ah, I add this function because I want to have a look of the content of
DeleteStmt. I did this long ago, just after I started the project. If you
don't want this function, I will remove it.

Ok.

* Instead of scanning the list of actions in ExecBS/ExecASMergeTriggers
every time, should set flags at plan time to mark what kind of actions there
is in the statement.

First of all, I need to say that the functions of ExecBSMergeTriggers() and
ExecASMergeTriggers() are for per-statement triggers, and are invoked only
once in the whole process of a MERGE command. Setting flags at plan time can
save the scanning. I may add it to next patch. But don't expect it save
much execution time.

Yeah, I'm not so much concerned about performance, it just seems like it
might make the code slightly simpler.

Or should we defer firing the statement triggers until we hit the first
matching row and execute the action for the first time?

No, we cannot do this. These are Per-statement triggers. They should be
fired before/after the main plan is executed. The statement triggers are
fired even no tuple is really matched with the actions.

Ok.

* This crashes:

postgres=# CREATE TABLE target AS (SELECT 1 AS id);
SELECT 1
postgres=# MERGE into target t
USING (select 1 AS id) AS s

ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET id = (SELECT COUNT(*) FROM generate_series(1,10))
;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Oh, a really serious bug. I have not considered this case (user series
generator in actions) before. I will see what I can do for it.

It's not specific to generate_series() but subqueries in general that
are the problem.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com